Mercurial > public > mercurial-scm > hg
comparison mercurial/localrepo.py @ 2775: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
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] |