diff mercurial/merge.py @ 18338:384df4db6520

merge: merge file flags together with file content The 'x' flag and the 'l' flag are very different. It is usually not a problem to change the 'x' flag of a normal file independent of the content, but one does not simply change the type of a file to 'l' independent of the content. This removes the fmerge function that merged both 'x' and 'l' independent of content early in the merge process. This correctly introduces some conflicts instead of silent incorrect merges. 3-way flag merge will now be done in the resolve process, right next to file content merge. Conflicts can thus be resolved with (slightly inconvenient) resolve commands like 'resolve f --tool internal:other'. It thus brings us closer to be able to re-solve manifest merge after the merge and avoid prompts during merge. This also removes the "conflicting flags for a - (n)one, e(x)ec or sym(l)ink?" prompt that nobody could answer and that made it easy to mix symlink targets and file contents up. Instead it will give a file merge where a sufficiently clever merge tool can help resolving the issue.
author Mads Kiilerich <mads@kiilerich.com>
date Wed, 09 Jan 2013 02:02:45 +0100
parents 77973b6a7b0b
children aadefcee1f5e
line wrap: on
line diff
--- a/mercurial/merge.py	Tue Jan 15 01:05:12 2013 +0100
+++ b/mercurial/merge.py	Wed Jan 09 02:02:45 2013 +0100
@@ -45,11 +45,11 @@
                 f.write("\0".join([d] + v) + "\n")
             f.close()
             self._dirty = False
-    def add(self, fcl, fco, fca, fd, flags):
+    def add(self, fcl, fco, fca, fd):
         hash = util.sha1(fcl.path()).hexdigest()
         self._repo.opener.write("merge/" + hash, fcl.data())
         self._state[fd] = ['u', hash, fcl.path(), fca.path(),
-                           hex(fca.filenode()), fco.path(), flags]
+                           hex(fca.filenode()), fco.path(), fcl.flags()]
         self._dirty = True
     def __contains__(self, dfile):
         return dfile in self._state
@@ -67,12 +67,22 @@
         if self[dfile] == 'r':
             return 0
         state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
+        fcd = wctx[dfile]
+        fco = octx[ofile]
+        fca = self._repo.filectx(afile, fileid=anode)
+        # "premerge" x flags
+        flo = fco.flags()
+        fla = fca.flags()
+        if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
+            if fca.node() == nullid:
+                self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
+                                   afile)
+            elif flags == fla:
+                flags = flo
+        # restore local
         f = self._repo.opener("merge/" + hash)
         self._repo.wwrite(dfile, f.read(), flags)
         f.close()
-        fcd = wctx[dfile]
-        fco = octx[ofile]
-        fca = self._repo.filectx(afile, fileid=anode)
         r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
         if r is None:
             # no real conflict
@@ -183,32 +193,6 @@
     partial = function to filter file lists
     """
 
-    def fmerge(f, f2, fa):
-        """merge flags"""
-        a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
-        if m == n: # flags agree
-            return m # unchanged
-        if m and n and not a: # flags set, don't agree, differ from parent
-            r = repo.ui.promptchoice(
-                _(" conflicting flags for %s\n"
-                  "(n)one, e(x)ec or sym(l)ink?") % f,
-                (_("&None"), _("E&xec"), _("Sym&link")), 0)
-            if r == 1:
-                return "x" # Exec
-            if r == 2:
-                return "l" # Symlink
-            return ""
-        if m and m != a: # changed from a to m
-            return m
-        if n and n != a: # changed from a to n
-            if (n == 'l' or a == 'l') and m1.get(f) != ma.get(f):
-                # can't automatically merge symlink flag when there
-                # are file-level conflicts here, let filemerge take
-                # care of it
-                return m
-            return n
-        return '' # flag was cleared
-
     def act(msg, m, f, *args):
         repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
         actions.append((f, m) + args)
@@ -248,17 +232,25 @@
         if partial and not partial(f):
             continue
         if f in m2:
-            rflags = fmerge(f, f, f)
+            n2 = m2[f]
+            fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
+            nol = 'l' not in fl1 + fl2 + fla
             a = ma.get(f, nullid)
-            if n == m2[f] or m2[f] == a: # same or local newer
-                # is file locally modified or flags need changing?
-                # dirstate flags may need to be made current
-                if m1.flags(f) != rflags or n[20:]:
-                    act("update permissions", "e", f, rflags)
-            elif n == a: # remote newer
-                act("remote is newer", "g", f, rflags)
-            else: # both changed
-                act("versions differ", "m", f, f, f, rflags, False)
+            if n == n2 and fl1 == fl2:
+                pass # same - keep local
+            elif n2 == a and fl2 == fla:
+                pass # remote unchanged - keep local
+            elif n == a and fl1 == fla: # local unchanged - use remote
+                if n == n2: # optimization: keep local content
+                    act("update permissions", "e", f, fl2)
+                else:
+                    act("remote is newer", "g", f, fl2)
+            elif nol and n2 == a: # remote only changed 'x'
+                act("update permissions", "e", f, fl2)
+            elif nol and n == a: # local only changed 'x'
+                act("remote is newer", "g", f, fl)
+            else: # both changed something
+                act("versions differ", "m", f, f, f, False)
         elif f in copied: # files we'll deal with on m2 side
             pass
         elif f in movewithdir: # directory rename
@@ -267,8 +259,7 @@
                 m1.flags(f))
         elif f in copy: # case 2 A,B/B/B or case 4,21 A/B/B
             f2 = copy[f]
-            act("local copied/moved to " + f2, "m", f, f2, f,
-                fmerge(f, f2, f2), False)
+            act("local copied/moved to " + f2, "m", f, f2, f, False)
         elif f in ma: # clean, a different, no remote
             if n != ma[f]:
                 if repo.ui.promptchoice(
@@ -296,16 +287,15 @@
             f2 = copy[f]
             if f2 in m2: # rename case 1, A/A,B/A
                 act("remote copied to " + f, "m",
-                    f2, f, f, fmerge(f2, f, f2), False)
+                    f2, f, f, False)
             else: # case 3,20 A/B/A
                 act("remote moved to " + f, "m",
-                    f2, f, f, fmerge(f2, f, f2), True)
+                    f2, f, f, True)
         elif f not in ma:
             if (not overwrite
                 and _checkunknownfile(repo, p1, p2, f)):
-                rflags = fmerge(f, f, f)
                 act("remote differs from untracked local",
-                    "m", f, f, f, rflags, False)
+                    "m", f, f, f, False)
             else:
                 act("remote created", "g", f, m2.flags(f))
         elif n != ma[f]:
@@ -341,7 +331,7 @@
     for a in actions:
         f, m = a[:2]
         if m == "m": # merge
-            f2, fd, flags, move = a[2:]
+            f2, fd, move = a[2:]
             if fd == '.hgsubstate': # merged internally
                 continue
             repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
@@ -356,7 +346,7 @@
                 fca = fcl.ancestor(fco, actx)
             if not fca:
                 fca = repo.filectx(f, fileid=nullrev)
-            ms.add(fcl, fco, fca, fd, flags)
+            ms.add(fcl, fco, fca, fd)
             if f != fd and move:
                 moves.append(f)
 
@@ -390,7 +380,7 @@
                 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
                                  overwrite)
                 continue
-            f2, fd, flags, move = a[2:]
+            f2, fd, move = a[2:]
             audit(fd)
             r = ms.resolve(fd, wctx, mctx)
             if r is not None and r > 0:
@@ -484,7 +474,7 @@
             else:
                 repo.dirstate.normal(f)
         elif m == "m": # merge
-            f2, fd, flag, move = a[2:]
+            f2, fd, move = a[2:]
             if branchmerge:
                 # We've done a branch merge, mark this file as merged
                 # so that we properly record the merger later