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] |