changeset 3159:c82ea81d6850

Add core copy detection algorithm This adds findcopies, which detects merge-relevant copies between files in a pair of manifests back to the merge ancestor. While the merge code invokes the copy detection routine, it does not yet use the result.
author Matt Mackall <mpm@selenic.com>
date Mon, 25 Sep 2006 16:45:31 -0500
parents 15d585dcdd1c
children b1f10d3223c1
files mercurial/merge.py tests/test-rename-merge1 tests/test-rename-merge1.out
diffstat 3 files changed, 113 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/merge.py	Mon Sep 25 16:43:21 2006 -0500
+++ b/mercurial/merge.py	Mon Sep 25 16:45:31 2006 -0500
@@ -94,6 +94,77 @@
 
     return action
 
+def nonoverlap(d1, d2):
+    """
+    Return list of elements in d1 not in d2
+    """
+
+    l = []
+    for d in d1:
+        if d not in d2:
+            l.append(d)
+
+    l.sort()
+    return l
+
+def findold(fctx, limit):
+    """
+    find files that path was copied from, back to linkrev limit
+    """
+
+    old = {}
+    orig = fctx.path()
+    visit = [fctx]
+    while visit:
+        fc = visit.pop()
+        if fc.rev() < limit:
+            continue
+        if fc.path() != orig and fc.path() not in old:
+            old[fc.path()] = 1
+        visit += fc.parents()
+
+    old = old.keys()
+    old.sort()
+    return old
+
+def findcopies(repo, m1, m2, limit):
+    """
+    Find moves and copies between m1 and m2 back to limit linkrev
+    """
+
+    copy = {}
+    match = {}
+    u1 = nonoverlap(m1, m2)
+    u2 = nonoverlap(m2, m1)
+    ctx = util.cachefunc(lambda f,n: repo.filectx(f, fileid=n[:20]))
+
+    def checkpair(c, f2, man):
+        ''' check if an apparent pair actually matches '''
+        c2 = ctx(f2, man[f2])
+        ca = c.ancestor(c2)
+        if ca:
+            copy[c.path()] = f2
+            copy[f2] = c.path()
+
+    for f in u1:
+        c = ctx(f, m1[f])
+        for of in findold(c, limit):
+            if of in m2:
+                checkpair(c, of, m2)
+            else:
+                match.setdefault(of, []).append(f)
+
+    for f in u2:
+        c = ctx(f, m2[f])
+        for of in findold(c, limit):
+            if of in m1:
+                checkpair(c, of, m1)
+            elif of in match:
+                for mf in match[of]:
+                    checkpair(c, mf, m1)
+
+    return copy
+
 def manifestmerge(ui, m1, m2, ma, overwrite, backwards, partial):
     """
     Merge manifest m1 with m2 using ancestor ma and generate merge action list
@@ -289,6 +360,11 @@
         checkunknown(repo, m2, status)
     if not branchmerge:
         action += forgetremoved(m2, status)
+
+    copy = {}
+    if not (backwards or overwrite):
+        copy = findcopies(repo, m1, m2, repo.changelog.rev(pa))
+
     action += manifestmerge(repo.ui, m1, m2, ma, overwrite, backwards, partial)
     del m1, m2, ma
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rename-merge1	Mon Sep 25 16:45:31 2006 -0500
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+mkdir t
+cd t
+hg init
+echo foo > a
+echo foo > a2
+hg add a a2
+hg ci -m "start" -d "0 0"
+hg mv a b
+hg mv a2 b2
+hg ci -m "rename" -d "0 0"
+echo "checkout"
+hg co 0
+echo blahblah > a
+echo blahblah > a2
+hg mv a2 c2
+hg ci -m "modify" -d "0 0"
+echo "merge"
+hg merge -y --debug
+cat a
+cat b
+hg ci -m "merge" -d "0 0"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rename-merge1.out	Mon Sep 25 16:45:31 2006 -0500
@@ -0,0 +1,14 @@
+checkout
+2 files updated, 0 files merged, 2 files removed, 0 files unresolved
+merge
+resolving manifests
+ overwrite None branchmerge True partial False
+ ancestor f26ec4fc3fa3 local 8e765a822af2 remote af1939970a1c
+ b: remote created -> g
+ b2: remote created -> g
+getting b
+getting b2
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+(branch merge, don't forget to commit)
+blahblah
+foo