[PATCH] merge: add automatic tag merge algorithm

Angel Ezquerra angel.ezquerra at gmail.com
Tue Feb 18 17:28:37 CST 2014


# HG changeset patch
# User Angel Ezquerra <angel.ezquerra at gmail.com>
# Date 1392597815 -3600
#      Mon Feb 17 01:43:35 2014 +0100
# Node ID f3eb8304d9bb59e78b50b42a9341a2063e1cb451
# Parent  7648e9aef6eeab00a0946e877690e94fb12d389b
merge: add automatic tag merge algorithm

Try to automatically merge conflicting .hgtags files using the following
algorithm:
- keep all the tags that are on only one of the merge parents
- out of the common tags:
    - keep those that are exactly the same (including history) in both parents
    - keep those whose "full tag history" (i.e. current node plus tag history)
    is a super-set of the other merge parent "full tag history"
- If there are no tags left the merge was successful.
- If there are any tags left there was a conflict and the automated merge failed
    - When this happens, fall back to a regular file merge, which in most cases
    should open a merge tool (at which point the user can manually resolve the
    conflicts).

# 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.
- When the automated merge algorithm is successful, the merged tags are written
in alphabetical order. There main reason is that tags._readtags returns a
regular unordered python dict. We could change it to a sorteddict instead.
If we keep it this way we should probably change the hg tag command so that it
sorts tags when it modifies the .hgtags file as well.
- All tests pass without modification. The added test passes as well (while it
would fail without this change)

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -10,6 +10,7 @@
 from mercurial import obsolete
 import error, util, filemerge, copies, subrepo, worker, dicthelpers
 import errno, os, shutil
+import tags
 
 class mergestate(object):
     '''track 3-way merge state of individual files'''
@@ -519,7 +520,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, mctx)
             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
@@ -298,3 +298,53 @@
         cachefile.close()
     except (OSError, IOError):
         pass
+
+def tagmerge(repo, p1ctx, p2ctx):
+    '''Merge the tags of two revisions'''
+    ui = repo.ui
+    ui.note(_('merging .hgtags'))
+    p1tags = _readtags(
+        ui, repo, p1ctx['.hgtags'].data().splitlines(), "p1 tags")
+    p2tags = _readtags(
+        ui, repo, p2ctx['.hgtags'].data().splitlines(), "p2 tags")
+
+    numconflicts = 0
+    mergedtags = p1tags.copy()
+    for name, nodehist in p2tags.iteritems():
+        if name not in mergedtags:
+            mergedtags[name] = nodehist
+            continue
+        # there is no conflict unless both tags point to different revisions
+        # and have a non overlapping tag history
+        p1node, p1hist = mergedtags[name]
+        p2node, p2hist = nodehist
+        p1fullhist = [p1node] + p1hist
+        p2fullhist = [p2node] + p2hist
+        if p1fullhist == p2fullhist:
+            # mergetags already contains this tag data which it got from p1
+            continue
+        if p1fullhist == p2fullhist[:len(p1fullhist)]:
+            # p1 tag history is a subset of p2 tag history
+            mergednode, mergedhist = p2node, p2hist
+        elif p2fullhist == p1fullhist[:len(p2fullhist)]:
+            # p2 tag history is a subset of p1 tag history
+            mergednode, mergedhist = p1node, p1hist
+        else:
+            numconflicts += 1
+            continue
+        mergedtags[name] = mergednode, mergedhist
+
+    if not numconflicts:
+        # Write the merged .hgtags file
+        fp = repo.wfile('.hgtags', 'wb')
+        for name in sorted(mergedtags):
+            node, hist = mergedtags[name]
+            hist.append(node)
+            for nd in hist:
+                fp.write('%s %s\n' % (hex(nd), name))
+        fp.close()
+        ui.note(_('.hgtags merged successfully'))
+        return None
+
+    ui.warn(_('.hgtags merge failed (%d tag conflicts)') % numconflicts)
+    return numconflicts
diff --git a/tests/test-tag.t b/tests/test-tag.t
--- a/tests/test-tag.t
+++ b/tests/test-tag.t
@@ -271,6 +271,53 @@
   abort: cannot tag null revision
   [255]
 
+merging tags automatically
+
+  $ hg up -C new-topo-head
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg tag -f new-topo-head-2
+  $ hg status -mard
+  $ hg merge -r 15
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg status -mard
+  M .hgtags
+  $ hg resolve -l
+  R .hgtags
+  $ cat .hgtags
+  75a534207be6b03576e0c7a4fa5708d045f1c876 custom-tag
+  acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
+  0f26aaea6f74c3ed6c4aad8844403c9ba128d23a new-topo-head
+  0f26aaea6f74c3ed6c4aad8844403c9ba128d23a new-topo-head-2
+  a0eea09de1eeec777b46f2085260a373b2fbc293 newline
+  fc93d2ea1cd78e91216c6cfbbf26747c10ce11ae tag-and-branch-same-name
+  $ hg diff -r 'p1()' .hgtags
+  diff -r 0b98c6042b51 .hgtags
+  --- a/.hgtags	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hgtags	Tue Feb 18 23:21:31 2014 +0000
+  @@ -1,5 +1,6 @@
+  +75a534207be6b03576e0c7a4fa5708d045f1c876 custom-tag
+   acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
+  +0f26aaea6f74c3ed6c4aad8844403c9ba128d23a new-topo-head
+  +0f26aaea6f74c3ed6c4aad8844403c9ba128d23a new-topo-head-2
+   a0eea09de1eeec777b46f2085260a373b2fbc293 newline
+   fc93d2ea1cd78e91216c6cfbbf26747c10ce11ae tag-and-branch-same-name
+  -75a534207be6b03576e0c7a4fa5708d045f1c876 custom-tag
+  -0f26aaea6f74c3ed6c4aad8844403c9ba128d23a new-topo-head-2
+  $ hg diff -r 'p2()' .hgtags
+  diff -r ae5915ac112b .hgtags
+  --- a/.hgtags	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hgtags	Tue Feb 18 23:21:31 2014 +0000
+  @@ -1,5 +1,6 @@
+  +75a534207be6b03576e0c7a4fa5708d045f1c876 custom-tag
+   acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
+  +0f26aaea6f74c3ed6c4aad8844403c9ba128d23a new-topo-head
+  +0f26aaea6f74c3ed6c4aad8844403c9ba128d23a new-topo-head-2
+   a0eea09de1eeec777b46f2085260a373b2fbc293 newline
+   fc93d2ea1cd78e91216c6cfbbf26747c10ce11ae tag-and-branch-same-name
+  -75a534207be6b03576e0c7a4fa5708d045f1c876 custom-tag
+  -0f26aaea6f74c3ed6c4aad8844403c9ba128d23a new-topo-head
+
   $ cd ..
 
 tagging on an uncommitted merge (issue2542)


More information about the Mercurial-devel mailing list