[PATCH 3 of 3 V2] merge: add automatic tag merge algorithm

Angel Ezquerra angel.ezquerra at gmail.com
Mon Mar 17 01:36:53 CDT 2014


# HG changeset patch
# User Angel Ezquerra <angel.ezquerra at gmail.com>
# Date 1395006485 -3600
#      Sun Mar 16 22:48:05 2014 +0100
# Node ID ee742da53c1e43b33d84555662da9ed3ba938f6f
# Parent  d2b518fe1a6c7d113cd0d8df5ce1f687e6ec0219
merge: add automatic tag merge algorithm

Try to automatically merge conflicting .hgtags files using the following
algorithm:

- keep all tags that are identical on both parents (including their history)
- keep all the tags that are on only one of the merge parents

If there are any tags that are different in any way on both parents, the
automatic merge fails and we revert to the original merge behavior (which is to
do a regular .hgtags text merge).

This is a very simple algorithm, but it solves what is possibly the most common
tag merge conflict: the one that occurs when new, different tags are added on
each merge parent. Without this algorithm that scenario always results on an
.hgtags merge conflict which requires user intervention even though there is a
simple recipe to fix it (the one that this revision implements).

We could probably come up with ways to solve other less common merge conflicts
but those would get us a much smaller gain with an increased risk. We can
explore those in the future.

# Notes:
- The algorithm assumes that tags are never removed from the .hgtags file. This
should be true in most cases (particularly if the user does not normally modify
the .hgtags file on its own). We could improve this merge algorithm to handle
that case as well.
- All existing and new tests pass without modification.

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -12,6 +12,7 @@
 from mercurial import obsolete
 import error, util, filemerge, copies, subrepo, worker, dicthelpers
 import errno, os, shutil
+import tags
 
 _pack = struct.pack
 _unpack = struct.unpack
@@ -686,7 +687,13 @@
                 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
                                  overwrite)
                 continue
-            audit(fd)
+            elif fd == '.hgtags':  # tags need merging
+                if not tags.tagmerge(repo, wctx, mctx):
+                    merged += 1
+                    ms.mark(fd, 'r')
+                    continue
+            else:
+                audit(fd)
             r = ms.resolve(fd, wctx)
             if r is not None and r > 0:
                 unresolved += 1
diff --git a/mercurial/tags.py b/mercurial/tags.py
--- a/mercurial/tags.py
+++ b/mercurial/tags.py
@@ -299,3 +299,47 @@
         cachefile.close()
     except (OSError, IOError):
         pass
+
+def writetags(repo, tags, munge=None):
+    fp = repo.wfile('.hgtags', 'wb')
+    for name in tags:
+        m = munge and munge(name) or name
+        node, hist = tags[name]
+        hist.append(node)
+        for nd in hist:
+            fp.write('%s %s\n' % (hex(nd), m))
+    fp.close()
+
+def tagmerge(repo, p1ctx, p2ctx):
+    '''Merge the tags of two revisions'''
+    ui = repo.ui
+    ui.note(_('merging .hgtags\n'))
+    p1tags = _readtags(
+        ui, repo, p1ctx['.hgtags'].data().splitlines(), "p1 tags")
+    p2tags = _readtags(
+        ui, repo, p2ctx['.hgtags'].data().splitlines(), "p2 tags")
+
+    conflictedtags = []
+    mergedtags = p1tags.copy()
+    # sortdict does not implement iteritems()
+    for name, nodehist in p2tags.items():
+        if name not in mergedtags:
+            mergedtags[name] = nodehist
+            continue
+        # there is no conflict unless both tags point to different revisions
+        # and have a non identical tag history
+        p1node, p1hist = mergedtags[name]
+        p2node, p2hist = nodehist
+        if p1node != p2node or p1hist != p2hist:
+            conflictedtags.append(name)
+            continue
+    if not conflictedtags:
+        # Write the merged .hgtags file
+        writetags(repo, mergedtags)
+        ui.note(_('.hgtags merged successfully\n'))
+        return None
+    numconflicts = len(conflictedtags)
+    ui.warn(_('automatic .hgtags merge failed\n'
+            'the following %d tags are in conflict: %s\n')
+            % (numconflicts, ', '.join(sorted(conflictedtags))))
+    return numconflicts
diff --git a/tests/test-tag.t b/tests/test-tag.t
--- a/tests/test-tag.t
+++ b/tests/test-tag.t
@@ -323,3 +323,123 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
 
+  $ cd ..
+
+automatically merge simple tag conflicts
+
+  $ hg init repo-automatic-tag-merge
+  $ cd repo-automatic-tag-merge
+  $ echo c0 > f0
+  $ hg ci -A -m0
+  adding f0
+  $ hg tag tbase
+  $ echo c1 > f1
+  $ hg ci -A -m1
+  adding f1
+  $ hg tag t1 t2 t3
+  $ hg tag --remove t2
+  $ echo c2 > f2
+  $ hg ci -A -m2
+  adding f2
+  $ hg tag -f t3
+  $ hg update -C -r 'children(tbase)'
+  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ echo c3 > f3
+  $ hg ci -A -m3
+  adding f3
+  created new head
+  $ hg tag -f t4 t5 t6
+  $ hg tag --remove t5
+  $ echo c4 > f4
+  $ hg ci -A -m4
+  adding f4
+  $ hg tag -f t6
+  $ hg merge
+  2 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg status
+  M .hgtags
+  M f1
+  M f2
+  $ hg resolve -l
+  R .hgtags
+  $ cat .hgtags
+  6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
+  9aa4e1292a27a248f8d07339bed9931d54907be7 t4
+  9aa4e1292a27a248f8d07339bed9931d54907be7 t5
+  9aa4e1292a27a248f8d07339bed9931d54907be7 t5
+  0000000000000000000000000000000000000000 t5
+  9aa4e1292a27a248f8d07339bed9931d54907be7 t6
+  9aa4e1292a27a248f8d07339bed9931d54907be7 t6
+  929bca7b18d067cbf3844c3896319a940059d748 t6
+  4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
+  4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
+  4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
+  0000000000000000000000000000000000000000 t2
+  4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
+  4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
+  edfec0022752ab491fc116d0e1f6e96c86f63041 t3
+  $ hg diff --git -r 'p1()' .hgtags
+  diff --git a/.hgtags b/.hgtags
+  --- a/.hgtags
+  +++ b/.hgtags
+  @@ -1,8 +1,15 @@
+   6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
+   9aa4e1292a27a248f8d07339bed9931d54907be7 t4
+   9aa4e1292a27a248f8d07339bed9931d54907be7 t5
+  -9aa4e1292a27a248f8d07339bed9931d54907be7 t6
+   9aa4e1292a27a248f8d07339bed9931d54907be7 t5
+   0000000000000000000000000000000000000000 t5
+   9aa4e1292a27a248f8d07339bed9931d54907be7 t6
+  +9aa4e1292a27a248f8d07339bed9931d54907be7 t6
+   929bca7b18d067cbf3844c3896319a940059d748 t6
+  +4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
+  +4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
+  +4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
+  +0000000000000000000000000000000000000000 t2
+  +4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
+  +4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
+  +edfec0022752ab491fc116d0e1f6e96c86f63041 t3
+  $ hg diff --git -r 'p2()' .hgtags
+  diff --git a/.hgtags b/.hgtags
+  --- a/.hgtags
+  +++ b/.hgtags
+  @@ -1,8 +1,15 @@
+   6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
+  +9aa4e1292a27a248f8d07339bed9931d54907be7 t4
+  +9aa4e1292a27a248f8d07339bed9931d54907be7 t5
+  +9aa4e1292a27a248f8d07339bed9931d54907be7 t5
+  +0000000000000000000000000000000000000000 t5
+  +9aa4e1292a27a248f8d07339bed9931d54907be7 t6
+  +9aa4e1292a27a248f8d07339bed9931d54907be7 t6
+  +929bca7b18d067cbf3844c3896319a940059d748 t6
+   4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
+   4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
+  -4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
+   4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
+   0000000000000000000000000000000000000000 t2
+   4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
+  +4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
+   edfec0022752ab491fc116d0e1f6e96c86f63041 t3
+
+detect merge tag conflicts
+
+  $ hg update -C -r tip
+  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg tag t7
+  $ hg update -C 6
+  3 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg tag -f t7
+  $ hg merge
+  automatic .hgtags merge failed
+  the following 1 tags are in conflict: t7
+  merging .hgtags
+  warning: conflicts during merge.
+  merging .hgtags incomplete! (edit conflicts, then use 'hg resolve --mark')
+  2 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ hg resolve -l
+  U .hgtags
+
+  $ cd ..


More information about the Mercurial-devel mailing list