diff mercurial/merge.py @ 3733:9e67fecbfd16

merge: handle directory renames commit: handle new copy dirstate case correctly findcopies: keep a map of all copies found for directory logic add dirs filter check for merge:followdirs config option generate a directory move map find files that match directory move map manifestmerge: add directory rename cases applyupdates: skip actions with None file add "d" action recordupdates: add "d" action add simple directory rename test
author Matt Mackall <mpm@selenic.com>
date Thu, 30 Nov 2006 17:36:33 -0600
parents ffe9fef84801
children b1eeaeb936ae
line wrap: on
line diff
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -122,11 +122,20 @@ def findcopies(repo, m1, m2, ma, limit):
                 return
             c2 = ctx(of, man[of])
             ca = c.ancestor(c2)
-            if not ca or c == ca or c2 == ca:
+            if not ca: # unrelated
                 return
             if ca.path() == c.path() or ca.path() == c2.path():
+                fullcopy[c.path()] = of
+                if c == ca or c2 == ca: # no merge needed, ignore copy
+                    return
                 copy[c.path()] = of
 
+    def dirs(files):
+        d = {}
+        for f in files:
+            d[os.path.dirname(f)] = True
+        return d
+
     if not repo.ui.configbool("merge", "followcopies", True):
         return {}
 
@@ -136,6 +145,7 @@ def findcopies(repo, m1, m2, ma, limit):
 
     dcopies = repo.dirstate.copies()
     copy = {}
+    fullcopy = {}
     u1 = nonoverlap(m1, m2, ma)
     u2 = nonoverlap(m2, m1, ma)
     ctx = util.cachefunc(lambda f, n: repo.filectx(f, fileid=n[:20]))
@@ -146,6 +156,38 @@ def findcopies(repo, m1, m2, ma, limit):
     for f in u2:
         checkcopies(ctx(f, m2[f]), m1)
 
+    if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
+        return copy
+
+    # generate a directory move map
+    d1, d2 = dirs(m1), dirs(m2)
+    invalid = {}
+    dirmove = {}
+
+    for dst, src in fullcopy.items():
+        dsrc, ddst = os.path.dirname(src), os.path.dirname(dst)
+        if dsrc in invalid:
+            continue
+        elif (dsrc in d1 and ddst in d1) or (dsrc in d2 and ddst in d2):
+            invalid[dsrc] = True
+        elif dsrc in dirmove and dirmove[dsrc] != ddst:
+            invalid[dsrc] = True
+            del dirmove[dsrc]
+        else:
+            dirmove[dsrc] = ddst
+
+    del d1, d2, invalid
+
+    if not dirmove:
+        return copy
+
+    # check unaccounted nonoverlapping files
+    for f in u1 + u2:
+        if f not in fullcopy:
+            d = os.path.dirname(f)
+            if d in dirmove:
+                copy[f] = dirmove[d] + "/" + os.path.basename(f)
+
     return copy
 
 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
@@ -210,7 +252,10 @@ def manifestmerge(repo, p1, p2, pa, over
             continue
         elif f in copy:
             f2 = copy[f]
-            if f2 in m1: # case 2 A,B/B/B
+            if f2 not in m2: # directory rename
+                act("remote renamed directory to " + f2, "d",
+                    f, None, f2, m1.execf(f))
+            elif f2 in m1: # case 2 A,B/B/B
                 act("local copied to " + f2, "m",
                     f, f2, f, fmerge(f, f2, f2), False)
             else: # case 4,21 A/B/B
@@ -238,7 +283,10 @@ def manifestmerge(repo, p1, p2, pa, over
             continue
         if f in copy:
             f2 = copy[f]
-            if f2 in m2: # rename case 1, A/A,B/A
+            if f2 not in m1: # directory rename
+                act("local renamed directory to " + f2, "d",
+                    None, f, f2, m2.execf(f))
+            elif f2 in m2: # rename case 1, A/A,B/A
                 act("remote copied to " + f, "m",
                     f2, f, f, fmerge(f2, f, f2), False)
             else: # case 3,20 A/B/A
@@ -264,7 +312,7 @@ def applyupdates(repo, action, wctx, mct
     action.sort()
     for a in action:
         f, m = a[:2]
-        if f[0] == "/":
+        if f and f[0] == "/":
             continue
         if m == "r": # remove
             repo.ui.note(_("removing %s\n") % f)
@@ -300,6 +348,20 @@ def applyupdates(repo, action, wctx, mct
             repo.wwrite(f, t)
             util.set_exec(repo.wjoin(f), flag)
             updated += 1
+        elif m == "d": # directory rename
+            f2, fd, flag = a[2:]
+            if f:
+                repo.ui.note(_("moving %s to %s\n") % (f, fd))
+                t = wctx.filectx(f).data()
+                repo.wwrite(fd, t)
+                util.set_exec(repo.wjoin(fd), flag)
+                util.unlink(repo.wjoin(f))
+            if f2:
+                repo.ui.note(_("getting %s to %s\n") % (f2, fd))
+                t = mctx.filectx(f2).data()
+                repo.wwrite(fd, t)
+                util.set_exec(repo.wjoin(fd), flag)
+            updated += 1
         elif m == "e": # exec
             flag = a[2]
             util.set_exec(repo.wjoin(f), flag)
@@ -345,6 +407,19 @@ def recordupdates(repo, action, branchme
                 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
                 if move:
                     repo.dirstate.forget([f])
+        elif m == "d": # directory rename
+            f2, fd, flag = a[2:]
+            if branchmerge:
+                repo.dirstate.update([fd], 'a')
+                if f:
+                    repo.dirstate.update([f], 'r')
+                    repo.dirstate.copy(f, fd)
+                if f2:
+                    repo.dirstate.copy(f2, fd)
+            else:
+                repo.dirstate.update([fd], 'n')
+                if f:
+                    repo.dirstate.forget([f])
 
 def update(repo, node, branchmerge, force, partial, wlock):
     """