mercurial/localrepo.py
changeset 2775 b550cd82f92a
parent 2740 386f04d6ecb3
child 2778 fdc232d8a193
equal deleted inserted replaced
2774:8cd3e19bf4a5 2775:b550cd82f92a
  1691                 self.hook("incoming", node=hex(self.changelog.node(i)),
  1691                 self.hook("incoming", node=hex(self.changelog.node(i)),
  1692                           source=srctype, url=url)
  1692                           source=srctype, url=url)
  1693 
  1693 
  1694         return newheads - oldheads + 1
  1694         return newheads - oldheads + 1
  1695 
  1695 
  1696     def update(self, node, allow=False, force=False, choose=None,
       
  1697                moddirstate=True, forcemerge=False, wlock=None, show_stats=True):
       
  1698         pl = self.dirstate.parents()
       
  1699         if not force and pl[1] != nullid:
       
  1700             raise util.Abort(_("outstanding uncommitted merges"))
       
  1701 
       
  1702         err = False
       
  1703 
       
  1704         p1, p2 = pl[0], node
       
  1705         pa = self.changelog.ancestor(p1, p2)
       
  1706         m1n = self.changelog.read(p1)[0]
       
  1707         m2n = self.changelog.read(p2)[0]
       
  1708         man = self.manifest.ancestor(m1n, m2n)
       
  1709         m1 = self.manifest.read(m1n)
       
  1710         mf1 = self.manifest.readflags(m1n)
       
  1711         m2 = self.manifest.read(m2n).copy()
       
  1712         mf2 = self.manifest.readflags(m2n)
       
  1713         ma = self.manifest.read(man)
       
  1714         mfa = self.manifest.readflags(man)
       
  1715 
       
  1716         modified, added, removed, deleted, unknown = self.changes()
       
  1717 
       
  1718         # is this a jump, or a merge?  i.e. is there a linear path
       
  1719         # from p1 to p2?
       
  1720         linear_path = (pa == p1 or pa == p2)
       
  1721 
       
  1722         if allow and linear_path:
       
  1723             raise util.Abort(_("there is nothing to merge, just use "
       
  1724                                "'hg update' or look at 'hg heads'"))
       
  1725         if allow and not forcemerge:
       
  1726             if modified or added or removed:
       
  1727                 raise util.Abort(_("outstanding uncommitted changes"))
       
  1728 
       
  1729         if not forcemerge and not force:
       
  1730             for f in unknown:
       
  1731                 if f in m2:
       
  1732                     t1 = self.wread(f)
       
  1733                     t2 = self.file(f).read(m2[f])
       
  1734                     if cmp(t1, t2) != 0:
       
  1735                         raise util.Abort(_("'%s' already exists in the working"
       
  1736                                            " dir and differs from remote") % f)
       
  1737 
       
  1738         # resolve the manifest to determine which files
       
  1739         # we care about merging
       
  1740         self.ui.note(_("resolving manifests\n"))
       
  1741         self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
       
  1742                       (force, allow, moddirstate, linear_path))
       
  1743         self.ui.debug(_(" ancestor %s local %s remote %s\n") %
       
  1744                       (short(man), short(m1n), short(m2n)))
       
  1745 
       
  1746         merge = {}
       
  1747         get = {}
       
  1748         remove = []
       
  1749 
       
  1750         # construct a working dir manifest
       
  1751         mw = m1.copy()
       
  1752         mfw = mf1.copy()
       
  1753         umap = dict.fromkeys(unknown)
       
  1754 
       
  1755         for f in added + modified + unknown:
       
  1756             mw[f] = ""
       
  1757             mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
       
  1758 
       
  1759         if moddirstate and not wlock:
       
  1760             wlock = self.wlock()
       
  1761 
       
  1762         for f in deleted + removed:
       
  1763             if f in mw:
       
  1764                 del mw[f]
       
  1765 
       
  1766             # If we're jumping between revisions (as opposed to merging),
       
  1767             # and if neither the working directory nor the target rev has
       
  1768             # the file, then we need to remove it from the dirstate, to
       
  1769             # prevent the dirstate from listing the file when it is no
       
  1770             # longer in the manifest.
       
  1771             if moddirstate and linear_path and f not in m2:
       
  1772                 self.dirstate.forget((f,))
       
  1773 
       
  1774         # Compare manifests
       
  1775         for f, n in mw.iteritems():
       
  1776             if choose and not choose(f):
       
  1777                 continue
       
  1778             if f in m2:
       
  1779                 s = 0
       
  1780 
       
  1781                 # is the wfile new since m1, and match m2?
       
  1782                 if f not in m1:
       
  1783                     t1 = self.wread(f)
       
  1784                     t2 = self.file(f).read(m2[f])
       
  1785                     if cmp(t1, t2) == 0:
       
  1786                         n = m2[f]
       
  1787                     del t1, t2
       
  1788 
       
  1789                 # are files different?
       
  1790                 if n != m2[f]:
       
  1791                     a = ma.get(f, nullid)
       
  1792                     # are both different from the ancestor?
       
  1793                     if n != a and m2[f] != a:
       
  1794                         self.ui.debug(_(" %s versions differ, resolve\n") % f)
       
  1795                         # merge executable bits
       
  1796                         # "if we changed or they changed, change in merge"
       
  1797                         a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
       
  1798                         mode = ((a^b) | (a^c)) ^ a
       
  1799                         merge[f] = (m1.get(f, nullid), m2[f], mode)
       
  1800                         s = 1
       
  1801                     # are we clobbering?
       
  1802                     # is remote's version newer?
       
  1803                     # or are we going back in time?
       
  1804                     elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
       
  1805                         self.ui.debug(_(" remote %s is newer, get\n") % f)
       
  1806                         get[f] = m2[f]
       
  1807                         s = 1
       
  1808                 elif f in umap or f in added:
       
  1809                     # this unknown file is the same as the checkout
       
  1810                     # we need to reset the dirstate if the file was added
       
  1811                     get[f] = m2[f]
       
  1812 
       
  1813                 if not s and mfw[f] != mf2[f]:
       
  1814                     if force:
       
  1815                         self.ui.debug(_(" updating permissions for %s\n") % f)
       
  1816                         util.set_exec(self.wjoin(f), mf2[f])
       
  1817                     else:
       
  1818                         a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
       
  1819                         mode = ((a^b) | (a^c)) ^ a
       
  1820                         if mode != b:
       
  1821                             self.ui.debug(_(" updating permissions for %s\n")
       
  1822                                           % f)
       
  1823                             util.set_exec(self.wjoin(f), mode)
       
  1824                 del m2[f]
       
  1825             elif f in ma:
       
  1826                 if n != ma[f]:
       
  1827                     r = _("d")
       
  1828                     if not force and (linear_path or allow):
       
  1829                         r = self.ui.prompt(
       
  1830                             (_(" local changed %s which remote deleted\n") % f) +
       
  1831                              _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
       
  1832                     if r == _("d"):
       
  1833                         remove.append(f)
       
  1834                 else:
       
  1835                     self.ui.debug(_("other deleted %s\n") % f)
       
  1836                     remove.append(f) # other deleted it
       
  1837             else:
       
  1838                 # file is created on branch or in working directory
       
  1839                 if force and f not in umap:
       
  1840                     self.ui.debug(_("remote deleted %s, clobbering\n") % f)
       
  1841                     remove.append(f)
       
  1842                 elif n == m1.get(f, nullid): # same as parent
       
  1843                     if p2 == pa: # going backwards?
       
  1844                         self.ui.debug(_("remote deleted %s\n") % f)
       
  1845                         remove.append(f)
       
  1846                     else:
       
  1847                         self.ui.debug(_("local modified %s, keeping\n") % f)
       
  1848                 else:
       
  1849                     self.ui.debug(_("working dir created %s, keeping\n") % f)
       
  1850 
       
  1851         for f, n in m2.iteritems():
       
  1852             if choose and not choose(f):
       
  1853                 continue
       
  1854             if f[0] == "/":
       
  1855                 continue
       
  1856             if f in ma and n != ma[f]:
       
  1857                 r = _("k")
       
  1858                 if not force and (linear_path or allow):
       
  1859                     r = self.ui.prompt(
       
  1860                         (_("remote changed %s which local deleted\n") % f) +
       
  1861                          _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
       
  1862                 if r == _("k"):
       
  1863                     get[f] = n
       
  1864             elif f not in ma:
       
  1865                 self.ui.debug(_("remote created %s\n") % f)
       
  1866                 get[f] = n
       
  1867             else:
       
  1868                 if force or p2 == pa: # going backwards?
       
  1869                     self.ui.debug(_("local deleted %s, recreating\n") % f)
       
  1870                     get[f] = n
       
  1871                 else:
       
  1872                     self.ui.debug(_("local deleted %s\n") % f)
       
  1873 
       
  1874         del mw, m1, m2, ma
       
  1875 
       
  1876         if force:
       
  1877             for f in merge:
       
  1878                 get[f] = merge[f][1]
       
  1879             merge = {}
       
  1880 
       
  1881         if linear_path or force:
       
  1882             # we don't need to do any magic, just jump to the new rev
       
  1883             branch_merge = False
       
  1884             p1, p2 = p2, nullid
       
  1885         else:
       
  1886             if not allow:
       
  1887                 self.ui.status(_("this update spans a branch"
       
  1888                                  " affecting the following files:\n"))
       
  1889                 fl = merge.keys() + get.keys()
       
  1890                 fl.sort()
       
  1891                 for f in fl:
       
  1892                     cf = ""
       
  1893                     if f in merge:
       
  1894                         cf = _(" (resolve)")
       
  1895                     self.ui.status(" %s%s\n" % (f, cf))
       
  1896                 self.ui.warn(_("aborting update spanning branches!\n"))
       
  1897                 self.ui.status(_("(use 'hg merge' to merge across branches"
       
  1898                                  " or 'hg update -C' to lose changes)\n"))
       
  1899                 return 1
       
  1900             branch_merge = True
       
  1901 
       
  1902         xp1 = hex(p1)
       
  1903         xp2 = hex(p2)
       
  1904         if p2 == nullid: xxp2 = ''
       
  1905         else: xxp2 = xp2
       
  1906 
       
  1907         self.hook('preupdate', throw=True, parent1=xp1, parent2=xxp2)
       
  1908 
       
  1909         # get the files we don't need to change
       
  1910         files = get.keys()
       
  1911         files.sort()
       
  1912         for f in files:
       
  1913             if f[0] == "/":
       
  1914                 continue
       
  1915             self.ui.note(_("getting %s\n") % f)
       
  1916             t = self.file(f).read(get[f])
       
  1917             self.wwrite(f, t)
       
  1918             util.set_exec(self.wjoin(f), mf2[f])
       
  1919             if moddirstate:
       
  1920                 if branch_merge:
       
  1921                     self.dirstate.update([f], 'n', st_mtime=-1)
       
  1922                 else:
       
  1923                     self.dirstate.update([f], 'n')
       
  1924 
       
  1925         # merge the tricky bits
       
  1926         failedmerge = []
       
  1927         files = merge.keys()
       
  1928         files.sort()
       
  1929         for f in files:
       
  1930             self.ui.status(_("merging %s\n") % f)
       
  1931             my, other, flag = merge[f]
       
  1932             ret = self.merge3(f, my, other, xp1, xp2)
       
  1933             if ret:
       
  1934                 err = True
       
  1935                 failedmerge.append(f)
       
  1936             util.set_exec(self.wjoin(f), flag)
       
  1937             if moddirstate:
       
  1938                 if branch_merge:
       
  1939                     # We've done a branch merge, mark this file as merged
       
  1940                     # so that we properly record the merger later
       
  1941                     self.dirstate.update([f], 'm')
       
  1942                 else:
       
  1943                     # We've update-merged a locally modified file, so
       
  1944                     # we set the dirstate to emulate a normal checkout
       
  1945                     # of that file some time in the past. Thus our
       
  1946                     # merge will appear as a normal local file
       
  1947                     # modification.
       
  1948                     f_len = len(self.file(f).read(other))
       
  1949                     self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
       
  1950 
       
  1951         remove.sort()
       
  1952         for f in remove:
       
  1953             self.ui.note(_("removing %s\n") % f)
       
  1954             util.audit_path(f)
       
  1955             try:
       
  1956                 util.unlink(self.wjoin(f))
       
  1957             except OSError, inst:
       
  1958                 if inst.errno != errno.ENOENT:
       
  1959                     self.ui.warn(_("update failed to remove %s: %s!\n") %
       
  1960                                  (f, inst.strerror))
       
  1961         if moddirstate:
       
  1962             if branch_merge:
       
  1963                 self.dirstate.update(remove, 'r')
       
  1964             else:
       
  1965                 self.dirstate.forget(remove)
       
  1966 
       
  1967         if moddirstate:
       
  1968             self.dirstate.setparents(p1, p2)
       
  1969 
       
  1970         if show_stats:
       
  1971             stats = ((len(get), _("updated")),
       
  1972                      (len(merge) - len(failedmerge), _("merged")),
       
  1973                      (len(remove), _("removed")),
       
  1974                      (len(failedmerge), _("unresolved")))
       
  1975             note = ", ".join([_("%d files %s") % s for s in stats])
       
  1976             self.ui.status("%s\n" % note)
       
  1977         if moddirstate:
       
  1978             if branch_merge:
       
  1979                 if failedmerge:
       
  1980                     self.ui.status(_("There are unresolved merges,"
       
  1981                                     " you can redo the full merge using:\n"
       
  1982                                     "  hg update -C %s\n"
       
  1983                                     "  hg merge %s\n"
       
  1984                                     % (self.changelog.rev(p1),
       
  1985                                         self.changelog.rev(p2))))
       
  1986                 else:
       
  1987                     self.ui.status(_("(branch merge, don't forget to commit)\n"))
       
  1988             elif failedmerge:
       
  1989                 self.ui.status(_("There are unresolved merges with"
       
  1990                                  " locally modified files.\n"))
       
  1991 
       
  1992         self.hook('update', parent1=xp1, parent2=xxp2, error=int(err))
       
  1993         return err
       
  1994 
       
  1995     def merge3(self, fn, my, other, p1, p2):
       
  1996         """perform a 3-way merge in the working directory"""
       
  1997 
       
  1998         def temp(prefix, node):
       
  1999             pre = "%s~%s." % (os.path.basename(fn), prefix)
       
  2000             (fd, name) = tempfile.mkstemp(prefix=pre)
       
  2001             f = os.fdopen(fd, "wb")
       
  2002             self.wwrite(fn, fl.read(node), f)
       
  2003             f.close()
       
  2004             return name
       
  2005 
       
  2006         fl = self.file(fn)
       
  2007         base = fl.ancestor(my, other)
       
  2008         a = self.wjoin(fn)
       
  2009         b = temp("base", base)
       
  2010         c = temp("other", other)
       
  2011 
       
  2012         self.ui.note(_("resolving %s\n") % fn)
       
  2013         self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
       
  2014                               (fn, short(my), short(other), short(base)))
       
  2015 
       
  2016         cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
       
  2017                or "hgmerge")
       
  2018         r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=self.root,
       
  2019                         environ={'HG_FILE': fn,
       
  2020                                  'HG_MY_NODE': p1,
       
  2021                                  'HG_OTHER_NODE': p2,
       
  2022                                  'HG_FILE_MY_NODE': hex(my),
       
  2023                                  'HG_FILE_OTHER_NODE': hex(other),
       
  2024                                  'HG_FILE_BASE_NODE': hex(base)})
       
  2025         if r:
       
  2026             self.ui.warn(_("merging %s failed!\n") % fn)
       
  2027 
       
  2028         os.unlink(b)
       
  2029         os.unlink(c)
       
  2030         return r
       
  2031 
       
  2032     def verify(self):
  1696     def verify(self):
  2033         filelinkrevs = {}
  1697         filelinkrevs = {}
  2034         filenodes = {}
  1698         filenodes = {}
  2035         changesets = revisions = files = 0
  1699         changesets = revisions = files = 0
  2036         errors = [0]
  1700         errors = [0]