comparison mercurial/localrepo.py @ 2799:b550cd82f92a

Move merge code to its own module Pull update and merge3 out of localrepo into merge.py s/self/repo/ Add temporary API function in hg.py Convert all users
author Matt Mackall <mpm@selenic.com>
date Thu, 03 Aug 2006 15:24:41 -0500
parents 386f04d6ecb3
children fdc232d8a193
comparison
equal deleted inserted replaced
2798:8cd3e19bf4a5 2799: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]