[PATCH 2 of 7] copies: update _checkcopies to work in a rotated DAG

Gábor Stefanik gabor.stefanik at nng.com
Fri Oct 7 08:31:35 EDT 2016


# HG changeset patch
# User Gábor Stefanik <gabor.stefanik at nng.com>
# Date 1475588213 -7200
#      Tue Oct 04 15:36:53 2016 +0200
# Node ID 08f3260e3764bcfbe13945aa62bf079807df02fc
# Parent  5b7cd65a9be6c82b3ad14096b41db44c198a6769
copies: update _checkcopies to work in a rotated DAG

This introduces a distinction between "merge common ancestor" and
"topological common ancestor". During a regular merge, these two are
identical. Graft, however, performs a merge in a rotated DAG, where the
merge common ancestor will not be a common ancestor at all in the
original DAG.

To correctly find copies in case of a graft, we need to take both the
merge CA and the topological CA into account, and track any renames
between them in reverse.

Special care must be taken about the case where the merge CA lies not
between the topological CA and the source, but outside it (typically
between the topological CA and the destination).
This is handled using the remoteca parameter; it would be possible to
have _checkcopies itself detect this, but it's quite expensive and also
guaranteed not to change between multiple _checkcopies invocations in the
same mergecopies call, so it's better to check it there, and pass to
_checkcopies as a parameter.

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -345,10 +345,12 @@
     bothnew = sorted(addedinm1 & addedinm2)
 
     for f in u1u:
-        _checkcopies(c1, f, m1, m2, ca, limit, diverge, copy1, fullcopy1)
+        _checkcopies(c1, f, m1, m2, ca, ca, False, limit, diverge, copy1,
+                     fullcopy1)
 
     for f in u2u:
-        _checkcopies(c2, f, m2, m1, ca, limit, diverge, copy2, fullcopy2)
+        _checkcopies(c2, f, m2, m1, ca, ca, False, limit, diverge, copy2,
+                     fullcopy2)
 
     copy = dict(copy1.items() + copy2.items())
     movewithdir = dict(movewithdir1.items() + movewithdir2.items())
@@ -373,8 +375,10 @@
                       % "\n   ".join(bothnew))
     bothdiverge, _copy, _fullcopy = {}, {}, {}
     for f in bothnew:
-        _checkcopies(c1, f, m1, m2, ca, limit, bothdiverge, _copy, _fullcopy)
-        _checkcopies(c2, f, m2, m1, ca, limit, bothdiverge, _copy, _fullcopy)
+        _checkcopies(c1, f, m1, m2, ca, ca, False, limit, bothdiverge, _copy,
+                     _fullcopy)
+        _checkcopies(c2, f, m2, m1, ca, ca, False, limit, bothdiverge, _copy,
+                     _fullcopy)
     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
@@ -454,7 +458,8 @@
 
     return copy, movewithdir, diverge, renamedelete
 
-def _checkcopies(ctx, f, m1, m2, ca, limit, diverge, copy, fullcopy):
+def _checkcopies(ctx, f, m1, m2, ca, tca, remoteca, limit, diverge, copy,
+                 fullcopy):
     """
     check possible copies of f from m1 to m2
 
@@ -462,7 +467,9 @@
     f = the filename to check
     m1 = the source manifest
     m2 = the destination manifest
-    ca = the changectx of the common ancestor
+    ca = the changectx of the common ancestor, overridden on graft
+    tca = topological common ancestor for graft-like scenarios
+    remoteca = True if ca is outside tca::ctx, False otherwise
     limit = the rev number to not search beyond
     diverge = record all diverges in this dict
     copy = record all non-divergent copies in this dict
@@ -475,6 +482,8 @@
     """
 
     ma = ca.manifest()
+    mta = tca.manifest()
+    backwards = ca != tca and not remoteca and f in ma
     getfctx = _makegetfctx(ctx)
 
     def _related(f1, f2, limit):
@@ -520,15 +529,26 @@
             continue
         seen.add(of)
 
-        fullcopy[f] = of # remember for dir rename detection
+        # remember for dir rename detection
+        if backwards:
+            fullcopy[of] = f # grafting backwards through renames
+        else:
+            fullcopy[f] = of
         if of not in m2:
             continue # no match, keep looking
         if m2[of] == ma.get(of):
             return # no merge needed, quit early
         c2 = getfctx(of, m2[of])
-        cr = _related(oc, c2, ca.rev())
+        cr = _related(oc, c2, tca.rev())
         if cr and (of == f or of == c2.path()): # non-divergent
-            copy[f] = of
+            if backwards:
+                copy[of] = f
+            elif of in ma:
+                copy[f] = of
+            elif remoteca: # special case: a <- b <- a -> b "ping-pong" rename
+                copy[of] = f
+                del fullcopy[f]
+                fullcopy[of] = f
             return
 
     if of in ma:
diff --git a/tests/test-graft.t b/tests/test-graft.t
--- a/tests/test-graft.t
+++ b/tests/test-graft.t
@@ -427,8 +427,8 @@
   $ hg graft 3 --log -u foo
   grafting 3:4c60f11aa304 "3"
   warning: can't find ancestor for 'c' copied from 'b'!
-  $ hg log --template '{rev} {parents} {desc}\n' -r tip
-  14 1:5d205f8b35b6  3
+  $ hg log --template '{rev}:{node|short} {parents} {desc}\n' -r tip
+  14:0c921c65ef1e 1:5d205f8b35b6  3
   (grafted from 4c60f11aa304a54ae1c199feb94e7fc771e51ed8)
 
 Resolve conflicted graft
@@ -620,7 +620,7 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     2
   
-  changeset:   14:f64defefacee
+  changeset:   14:0c921c65ef1e
   parent:      1:5d205f8b35b6
   user:        foo
   date:        Thu Jan 01 00:00:00 1970 +0000


More information about the Mercurial-devel mailing list