compat: temporarily move copies fix to compat.py
authorPulkit Goyal <7895pulkit@gmail.com>
Sat, 21 Jul 2018 02:42:29 +0530
changeset 3882 55b8c7e7e352
parent 3881 9cf45b94f977
child 3883 ed460e7ee8aa
compat: temporarily move copies fix to compat.py This patch moves a fixed version of copies._fullcopytracing() to compat.py as this was not fixed in core before release and also we need to do this anyway for the older versions of mercurial. This fix is that when base changeset is not ancestor of any of the merging commits, we don't throw an error. The phabricator differential which will fix this in core is https://phab.mercurial-scm.org/D3896. This fixes the traceback we get in previous changeset while relocating one of the content-divergent changeset.
hgext3rd/evolve/compat.py
tests/test-evolve-content-divergence.t
--- a/hgext3rd/evolve/compat.py	Thu Jun 07 18:18:30 2018 +0530
+++ b/hgext3rd/evolve/compat.py	Sat Jul 21 02:42:29 2018 +0530
@@ -10,6 +10,7 @@
 
 from mercurial import (
     context,
+    copies,
     mdiff,
     obsolete,
     obsutil,
@@ -209,3 +210,242 @@
     if updateresult is None:
         return bool(upres[-1])
     return bool(upres.unresolvedcount)
+
+# code imported from Mercurial core at ae17555ef93f + patch
+def fixedcopytracing(repo, c1, c2, base):
+    """A complete copy-patse of copies._fullcopytrace with a one line fix to
+    handle when the base is not parent of both c1 and c2. This should be
+    converted in a compat function once https://phab.mercurial-scm.org/D3896
+    gets in and once we drop support for 4.7, this should be removed."""
+
+    from mercurial import pathutil
+
+    # In certain scenarios (e.g. graft, update or rebase), base can be
+    # overridden We still need to know a real common ancestor in this case We
+    # can't just compute _c1.ancestor(_c2) and compare it to ca, because there
+    # can be multiple common ancestors, e.g. in case of bidmerge.  Because our
+    # caller may not know if the revision passed in lieu of the CA is a genuine
+    # common ancestor or not without explicitly checking it, it's better to
+    # determine that here.
+    #
+    # base.isancestorof(wc) is False, work around that
+    _c1 = c1.p1() if c1.rev() is None else c1
+    _c2 = c2.p1() if c2.rev() is None else c2
+    # an endpoint is "dirty" if it isn't a descendant of the merge base
+    # if we have a dirty endpoint, we need to trigger graft logic, and also
+    # keep track of which endpoint is dirty
+    dirtyc1 = not base.isancestorof(_c1)
+    dirtyc2 = not base.isancestorof(_c2)
+    graft = dirtyc1 or dirtyc2
+    tca = base
+    if graft:
+        tca = _c1.ancestor(_c2)
+
+    limit = copies._findlimit(repo, c1.rev(), c2.rev())
+    if limit is None:
+        # no common ancestor, no copies
+        return {}, {}, {}, {}, {}
+    repo.ui.debug("  searching for copies back to rev %d\n" % limit)
+
+    m1 = c1.manifest()
+    m2 = c2.manifest()
+    mb = base.manifest()
+
+    # gather data from _checkcopies:
+    # - diverge = record all diverges in this dict
+    # - copy = record all non-divergent copies in this dict
+    # - fullcopy = record all copies in this dict
+    # - incomplete = record non-divergent partial copies here
+    # - incompletediverge = record divergent partial copies here
+    diverge = {} # divergence data is shared
+    incompletediverge = {}
+    data1 = {'copy': {},
+             'fullcopy': {},
+             'incomplete': {},
+             'diverge': diverge,
+             'incompletediverge': incompletediverge,
+            }
+    data2 = {'copy': {},
+             'fullcopy': {},
+             'incomplete': {},
+             'diverge': diverge,
+             'incompletediverge': incompletediverge,
+            }
+
+    # find interesting file sets from manifests
+    addedinm1 = m1.filesnotin(mb)
+    addedinm2 = m2.filesnotin(mb)
+    bothnew = sorted(addedinm1 & addedinm2)
+    if tca == base:
+        # unmatched file from base
+        u1r, u2r = copies._computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
+        u1u, u2u = u1r, u2r
+    else:
+        # unmatched file from base (DAG rotation in the graft case)
+        u1r, u2r = copies._computenonoverlap(repo, c1, c2, addedinm1, addedinm2,
+                                             baselabel='base')
+        # unmatched file from topological common ancestors (no DAG rotation)
+        # need to recompute this for directory move handling when grafting
+        mta = tca.manifest()
+        u1u, u2u = copies._computenonoverlap(repo, c1, c2, m1.filesnotin(mta),
+                                             m2.filesnotin(mta),
+                                             baselabel='topological common ancestor')
+
+    for f in u1u:
+        copies._checkcopies(c1, c2, f, base, tca, dirtyc1, limit, data1)
+
+    for f in u2u:
+        copies._checkcopies(c2, c1, f, base, tca, dirtyc2, limit, data2)
+
+    copy = dict(data1['copy'])
+    copy.update(data2['copy'])
+    fullcopy = dict(data1['fullcopy'])
+    fullcopy.update(data2['fullcopy'])
+
+    if dirtyc1:
+        copies._combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
+                              incompletediverge)
+    else:
+        copies._combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
+                              incompletediverge)
+
+    renamedelete = {}
+    renamedeleteset = set()
+    divergeset = set()
+    for of, fl in list(diverge.items()):
+        if len(fl) == 1 or of in c1 or of in c2:
+            del diverge[of] # not actually divergent, or not a rename
+            if of not in c1 and of not in c2:
+                # renamed on one side, deleted on the other side, but filter
+                # out files that have been renamed and then deleted
+                renamedelete[of] = [f for f in fl if f in c1 or f in c2]
+                renamedeleteset.update(fl) # reverse map for below
+        else:
+            divergeset.update(fl) # reverse map for below
+
+    if bothnew:
+        repo.ui.debug("  unmatched files new in both:\n   %s\n"
+                      % "\n   ".join(bothnew))
+    bothdiverge = {}
+    bothincompletediverge = {}
+    remainder = {}
+    both1 = {'copy': {},
+             'fullcopy': {},
+             'incomplete': {},
+             'diverge': bothdiverge,
+             'incompletediverge': bothincompletediverge
+            }
+    both2 = {'copy': {},
+             'fullcopy': {},
+             'incomplete': {},
+             'diverge': bothdiverge,
+             'incompletediverge': bothincompletediverge
+            }
+    for f in bothnew:
+        copies._checkcopies(c1, c2, f, base, tca, dirtyc1, limit, both1)
+        copies._checkcopies(c2, c1, f, base, tca, dirtyc2, limit, both2)
+
+    if dirtyc1 and dirtyc2:
+        pass
+    elif dirtyc1:
+        # incomplete copies may only be found on the "dirty" side for bothnew
+        assert not both2['incomplete']
+        remainder = copies._combinecopies({}, both1['incomplete'], copy, bothdiverge,
+                                          bothincompletediverge)
+    elif dirtyc2:
+        assert not both1['incomplete']
+        remainder = copies._combinecopies({}, both2['incomplete'], copy, bothdiverge,
+                                          bothincompletediverge)
+    else:
+        # incomplete copies and divergences can't happen outside grafts
+        assert not both1['incomplete']
+        assert not both2['incomplete']
+        assert not bothincompletediverge
+    for f in remainder:
+        assert f not in bothdiverge
+        ic = remainder[f]
+        if ic[0] in (m1 if dirtyc1 else m2):
+            # backed-out rename on one side, but watch out for deleted files
+            bothdiverge[f] = ic
+    for of, fl in bothdiverge.items():
+        if len(fl) == 2 and fl[0] == fl[1]:
+            copy[fl[0]] = of # not actually divergent, just matching renames
+
+    if fullcopy and repo.ui.debugflag:
+        repo.ui.debug("  all copies found (* = to merge, ! = divergent, "
+                      "% = renamed and deleted):\n")
+        for f in sorted(fullcopy):
+            note = ""
+            if f in copy:
+                note += "*"
+            if f in divergeset:
+                note += "!"
+            if f in renamedeleteset:
+                note += "%"
+            repo.ui.debug("   src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f,
+                                                              note))
+    del divergeset
+
+    if not fullcopy:
+        return copy, {}, diverge, renamedelete, {}
+
+    repo.ui.debug("  checking for directory renames\n")
+
+    # generate a directory move map
+    d1, d2 = c1.dirs(), c2.dirs()
+    # Hack for adding '', which is not otherwise added, to d1 and d2
+    d1.addpath('/')
+    d2.addpath('/')
+    invalid = set()
+    dirmove = {}
+
+    # examine each file copy for a potential directory move, which is
+    # when all the files in a directory are moved to a new directory
+    for dst, src in fullcopy.iteritems():
+        dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
+        if dsrc in invalid:
+            # already seen to be uninteresting
+            continue
+        elif dsrc in d1 and ddst in d1:
+            # directory wasn't entirely moved locally
+            invalid.add(dsrc + "/")
+        elif dsrc in d2 and ddst in d2:
+            # directory wasn't entirely moved remotely
+            invalid.add(dsrc + "/")
+        elif dsrc + "/" in dirmove and dirmove[dsrc + "/"] != ddst + "/":
+            # files from the same directory moved to two different places
+            invalid.add(dsrc + "/")
+        else:
+            # looks good so far
+            dirmove[dsrc + "/"] = ddst + "/"
+
+    for i in invalid:
+        if i in dirmove:
+            del dirmove[i]
+    del d1, d2, invalid
+
+    if not dirmove:
+        return copy, {}, diverge, renamedelete, {}
+
+    for d in dirmove:
+        repo.ui.debug("   discovered dir src: '%s' -> dst: '%s'\n" %
+                      (d, dirmove[d]))
+
+    movewithdir = {}
+    # check unaccounted nonoverlapping files against directory moves
+    for f in u1r + u2r:
+        if f not in fullcopy:
+            for d in dirmove:
+                if f.startswith(d):
+                    # new file added in a directory that was moved, move it
+                    df = dirmove[d] + f[len(d):]
+                    if df not in copy:
+                        movewithdir[f] = df
+                        repo.ui.debug(("   pending file src: '%s' -> "
+                                       "dst: '%s'\n") % (f, df))
+                    break
+
+    return copy, movewithdir, diverge, renamedelete, dirmove
+
+if util.safehasattr(copies, '_fullcopytracing'):
+    copies._fullcopytracing = fixedcopytracing
--- a/tests/test-evolve-content-divergence.t	Thu Jun 07 18:18:30 2018 +0530
+++ b/tests/test-evolve-content-divergence.t	Sat Jul 21 02:42:29 2018 +0530
@@ -696,144 +696,41 @@
   evolving 23:3f7a1f693080 "added z"
   updating to "local" side of the conflict: 53242575ffa9
   merging "other" content-divergent changeset 'cdb0643c69fc'
-  transaction abort!
-  rollback completed
-  ** Unknown exception encountered with possibly-broken third-party extension evolve
-  ** which supports versions 4.6 of Mercurial.
-  ** Please disable evolve and try your action again.
-  ** If that fixes the bug please report it to https://bz.mercurial-scm.org/
-  ** Python 2.7.12 (default, Dec  4 2017, 14:50:18) [GCC 5.4.0 20160609]
-  ** Mercurial Distributed SCM (version 4.7rc0+2-10d40c83205b)
-  ** Extensions loaded: rebase, evolve
-  Traceback (most recent call last):
-    File "/tmp/hgtests.rpfl6k/install/bin/hg", line 41, in <module>
-      dispatch.run()
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 90, in run
-      status = dispatch(req)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 213, in dispatch
-      ret = _runcatch(req) or 0
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 354, in _runcatch
-      return _callcatch(ui, _runcatchfunc)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 362, in _callcatch
-      return scmutil.callcatch(ui, func)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/scmutil.py", line 161, in callcatch
-      return func()
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 344, in _runcatchfunc
-      return _dispatch(req)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 984, in _dispatch
-      cmdpats, cmdoptions)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 730, in runcommand
-      ret = _runcommand(ui, options, cmd, d)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 992, in _runcommand
-      return cmdfunc()
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 981, in <lambda>
-      d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/util.py", line 1528, in check
-      return func(*args, **kwargs)
-    File "/home/foobar/repo/mutable-history/hgext3rd/evolve/evolvecmd.py", line 1519, in evolve
-      continueevolve(ui, repo, evolvestate, progresscb)
-    File "/home/foobar/repo/mutable-history/hgext3rd/evolve/evolvecmd.py", line 1706, in continueevolve
-      _continuecontentdivergent(ui, repo, evolvestate, progresscb)
-    File "/home/foobar/repo/mutable-history/hgext3rd/evolve/evolvecmd.py", line 1752, in _continuecontentdivergent
-      evolvestate)
-    File "/home/foobar/repo/mutable-history/hgext3rd/evolve/evolvecmd.py", line 512, in _mergecontentdivergents
-      mergeancestor=True)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/merge.py", line 2052, in update
-      followcopies, matcher=matcher, mergeforce=mergeforce)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/merge.py", line 1337, in calculateupdates
-      acceptremote, followcopies)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/merge.py", line 1138, in manifestmerge
-      ret = copies.mergecopies(repo, wctx, p2, pa)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/copies.py", line 383, in mergecopies
-      return _fullcopytracing(repo, c1, c2, base)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/copies.py", line 537, in _fullcopytracing
-      assert not both2['incomplete']
-  AssertionError
+  merging y
+  warning: conflicts while merging y! (edit, then use 'hg resolve --mark')
+  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  fix conflicts and see `hg help evolve.interrupted`
   [1]
 
   $ hg diff
-  diff -r 7bbcf24ddecf z
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/z	Thu Jan 01 00:00:00 1970 +0000
-  @@ -0,0 +1,1 @@
-  +bar
+  diff -r 53242575ffa9 y
+  --- a/y	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/y	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,5 @@
+  +<<<<<<< local: 53242575ffa9 bar - test: added z
+   watbar
+  +=======
+  +foo
+  +>>>>>>> other: cdb0643c69fc - test: added z
 
   $ echo foo > y
   $ hg resolve -m
-  abort: resolve command not applicable when not merging
-  [255]
+  (no more unresolved files)
+  continue: hg evolve --continue
   $ hg evolve --continue
-  evolving 23:3f7a1f693080 "added z"
-  updating to "local" side of the conflict: 53242575ffa9
-  merging "other" content-divergent changeset '4954f28e111b'
-  transaction abort!
-  rollback completed
-  ** Unknown exception encountered with possibly-broken third-party extension evolve
-  ** which supports versions 4.6 of Mercurial.
-  ** Please disable evolve and try your action again.
-  ** If that fixes the bug please report it to https://bz.mercurial-scm.org/
-  ** Python 2.7.12 (default, Dec  4 2017, 14:50:18) [GCC 5.4.0 20160609]
-  ** Mercurial Distributed SCM (version 4.7rc0+2-10d40c83205b)
-  ** Extensions loaded: rebase, evolve
-  Traceback (most recent call last):
-    File "/tmp/hgtests.rpfl6k/install/bin/hg", line 41, in <module>
-      dispatch.run()
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 90, in run
-      status = dispatch(req)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 213, in dispatch
-      ret = _runcatch(req) or 0
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 354, in _runcatch
-      return _callcatch(ui, _runcatchfunc)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 362, in _callcatch
-      return scmutil.callcatch(ui, func)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/scmutil.py", line 161, in callcatch
-      return func()
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 344, in _runcatchfunc
-      return _dispatch(req)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 984, in _dispatch
-      cmdpats, cmdoptions)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 730, in runcommand
-      ret = _runcommand(ui, options, cmd, d)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 992, in _runcommand
-      return cmdfunc()
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/dispatch.py", line 981, in <lambda>
-      d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/util.py", line 1528, in check
-      return func(*args, **kwargs)
-    File "/home/foobar/repo/mutable-history/hgext3rd/evolve/evolvecmd.py", line 1519, in evolve
-      continueevolve(ui, repo, evolvestate, progresscb)
-    File "/home/foobar/repo/mutable-history/hgext3rd/evolve/evolvecmd.py", line 1706, in continueevolve
-      _continuecontentdivergent(ui, repo, evolvestate, progresscb)
-    File "/home/foobar/repo/mutable-history/hgext3rd/evolve/evolvecmd.py", line 1752, in _continuecontentdivergent
-      evolvestate)
-    File "/home/foobar/repo/mutable-history/hgext3rd/evolve/evolvecmd.py", line 512, in _mergecontentdivergents
-      mergeancestor=True)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/merge.py", line 2052, in update
-      followcopies, matcher=matcher, mergeforce=mergeforce)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/merge.py", line 1337, in calculateupdates
-      acceptremote, followcopies)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/merge.py", line 1138, in manifestmerge
-      ret = copies.mergecopies(repo, wctx, p2, pa)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/copies.py", line 383, in mergecopies
-      return _fullcopytracing(repo, c1, c2, base)
-    File "/tmp/hgtests.rpfl6k/install/lib/python/mercurial/copies.py", line 537, in _fullcopytracing
-      assert not both2['incomplete']
-  AssertionError
-  [1]
+  working directory is now at 6fc7d9682de6
 
   $ hg glog
-  *  25:53242575ffa9 added z
+  @  27:6fc7d9682de6 added z
+  |   () [bar] draft
+  o  21:7bbcf24ddecf added y
   |   () [bar] draft
-  | *  23:3f7a1f693080 added z
-  | |   () [default] draft
-  @ |  21:7bbcf24ddecf added y
-  | |   () [bar] draft
-  o |  15:b006cf317e0e added foo to x
-  | |   () [bar] draft
-  o |  10:4ae4427ee9f8 added d
-  | |   () [default] draft
-  o |  9:917281f93fcb added c
-  |/    () [default] draft
+  o  15:b006cf317e0e added foo to x
+  |   () [bar] draft
+  o  10:4ae4427ee9f8 added d
+  |   () [default] draft
+  o  9:917281f93fcb added c
+  |   () [default] draft
   o  8:171614c9a791 added b
   |   () [default] draft
   o  1:c7586e2a9264 added a
@@ -847,15 +744,21 @@
   # Date 0 0
   #      Thu Jan 01 00:00:00 1970 +0000
   # Branch bar
-  # Node ID 7bbcf24ddecfe97d7c2ac6fa8c07c155c8fda47b
-  # Parent  b006cf317e0ed16dbe786c439577475580f645f1
-  added y
+  # Node ID 6fc7d9682de6e3bee6c8b1266b756ed7d522b7e4
+  # Parent  7bbcf24ddecfe97d7c2ac6fa8c07c155c8fda47b
+  added z
   
-  diff -r b006cf317e0e -r 7bbcf24ddecf y
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  diff -r 7bbcf24ddecf -r 6fc7d9682de6 y
+  --- a/y	Thu Jan 01 00:00:00 1970 +0000
   +++ b/y	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,1 @@
+  -watbar
+  +foo
+  diff -r 7bbcf24ddecf -r 6fc7d9682de6 z
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/z	Thu Jan 01 00:00:00 1970 +0000
   @@ -0,0 +1,1 @@
-  +watbar
+  +bar
 
   $ cd ..