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

Mads Kiilerich mads at kiilerich.com
Sun Mar 23 19:28:31 CDT 2014


On 03/17/2014 07:36 AM, Angel Ezquerra wrote:
> # 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.

This is a custom merge tool for a specific file type. Shouldn't it be in 
filemerge like other merge patterns?

It hardcodes an algorithm for merging .hgtags. It might be good enough 
to use by default, but I think we should avoid making it mandatory. 
There might be cases where it not is what the user want (for instance if 
they have their own .hgtags merge tool). It should be possible to 
disable / overrule the use of this tool - perhaps by configuring a merge 
pattern for .hgtags.

The merge algorithm do not use the ancestor - and can thus not see if 
anything has been removed. It do not even detect when it is 
reintroducing removed tags - it will just silently merge it incorrectly. 
I think it should handle that correctly before it is enabled by default.

The merge process will sort the .hgtags entries by tag names when 
writing back. If the .hgtags not already thad the entries grouped by tag 
name (because the tag had been reused), it will rewrite the tag file 
completely. That will make other merges that not are using this merge 
tool much harder. I don't think it is ok it regroups the entries - 
especially not when it not is necessary. It would be significantly less 
intrusive if it started out by writing out all the (undeleted) lines 
from one of the ancestor and then appended the missing entries from the 
other parent.

/Mads



> 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 ..
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel



More information about the Mercurial-devel mailing list