[PATCH] backout: handle file moves correctly (issue1932)

Mateusz Kwapich mitrandir at fb.com
Thu Jan 8 21:39:00 UTC 2015


# HG changeset patch
# User Mateusz Kwapich <mitrandir at fb.com>
# Date 1420752870 28800
#      Thu Jan 08 13:34:30 2015 -0800
# Node ID b140af105987b9db7a296d24584a557bdc67fc6e
# Parent  7ad155e13f0f51df8e986a0ec4e58ac9a0ccedbb
backout: handle file moves correctly (issue1932)

The merge logic (ab)used by backout wasn't detecting file moves when the
parameter which should be common ancestor of merge heads was in fact child of
one of them.

This commit is fixing that by if-ing that exact case. Only backout is affected
by this change.

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -271,8 +271,23 @@
     for f in u1:
         checkcopies(ctx, f, m1, m2, ca, limit, diverge, copy, fullcopy)
 
-    for f in u2:
-        checkcopies(ctx, f, m2, m1, ca, limit, diverge, copy, fullcopy)
+    if c2 not in ca.parents():
+        for f in u2:
+            checkcopies(ctx, f, m2, m1, ca, limit, diverge, copy, fullcopy)
+    else:
+        # we are abusing merge logic during backout and so need to
+        # do different and hacky copy detection in that case
+        st = c2.status(ca)
+        for f in st.added:
+            parents = ca[f].parents()
+            if len(parents) == 1:
+                # yay, it's copy
+                path = parents[0].path()
+                fullcopy[path] = f
+                if f in m1:
+                    if _related(c1[f], c2[path], c2.rev()):
+                        copy[path] = f
+                diverge.setdefault(f, []).append(path)
 
     renamedelete = {}
     renamedelete2 = set()
@@ -374,6 +389,39 @@
 
     return copy, movewithdir, diverge, renamedelete
 
+
+def _related(f1, f2, limit):
+    # Walk back to common ancestor to see if the two files originate
+    # from the same file. Since workingfilectx's rev() is None it messes
+    # up the integer comparison logic, hence the pre-step check for
+    # None (f1 and f2 can only be workingfilectx's initially).
+
+    if f1 == f2:
+        return f1 # a match
+
+    g1, g2 = f1.ancestors(), f2.ancestors()
+    try:
+        f1r, f2r = f1.rev(), f2.rev()
+
+        if f1r is None:
+            f1 = g1.next()
+        if f2r is None:
+            f2 = g2.next()
+
+        while True:
+            f1r, f2r = f1.rev(), f2.rev()
+            if f1r > f2r:
+                f1 = g1.next()
+            elif f2r > f1r:
+                f2 = g2.next()
+            elif f1 == f2:
+                return f1 # a match
+            elif f1r == f2r or f1r < limit or f2r < limit:
+                return False # copy no longer relevant
+    except StopIteration:
+        return False
+
+
 def checkcopies(ctx, f, m1, m2, ca, limit, diverge, copy, fullcopy):
     """
     check possible copies of f from m1 to m2
@@ -391,37 +439,6 @@
 
     ma = ca.manifest()
 
-    def _related(f1, f2, limit):
-        # Walk back to common ancestor to see if the two files originate
-        # from the same file. Since workingfilectx's rev() is None it messes
-        # up the integer comparison logic, hence the pre-step check for
-        # None (f1 and f2 can only be workingfilectx's initially).
-
-        if f1 == f2:
-            return f1 # a match
-
-        g1, g2 = f1.ancestors(), f2.ancestors()
-        try:
-            f1r, f2r = f1.rev(), f2.rev()
-
-            if f1r is None:
-                f1 = g1.next()
-            if f2r is None:
-                f2 = g2.next()
-
-            while True:
-                f1r, f2r = f1.rev(), f2.rev()
-                if f1r > f2r:
-                    f1 = g1.next()
-                elif f2r > f1r:
-                    f2 = g2.next()
-                elif f1 == f2:
-                    return f1 # a match
-                elif f1r == f2r or f1r < limit or f2r < limit:
-                    return False # copy no longer relevant
-        except StopIteration:
-            return False
-
     of = None
     seen = set([f])
     for oc in ctx(f, m1[f]).ancestors():
diff --git a/tests/test-backout.t b/tests/test-backout.t
--- a/tests/test-backout.t
+++ b/tests/test-backout.t
@@ -125,6 +125,25 @@
   commit: (clean)
   update: (current)
 
+backout should handle moves correctly
+  $ echo one > a
+  $ hg commit -A -d '4 0' -m "recreate a"
+  adding a
+  $ hg mv a b
+  $ hg commit -d '5 0' -m "move"
+  $ echo two >> b
+  $ hg commit -d '6 0' -m "append b"
+  $ hg backout -d '7 0' -r 'desc("append b")'
+  reverting b
+  changeset 7:08b301958a07 backs out changeset 6:106def198235
+  $ hg backout -r 'desc("move")'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  changeset ceebf4199a3f backed out, don't forget to commit.
+  $ hg status -C
+  A a
+    b
+  R b
+
 across branch
 
   $ cd ..


More information about the Mercurial-devel mailing list