[PATCH 2 of 4 NEW-CONCEPT] track-tags: introduce first bits of tags tracking during transaction

Pierre-Yves David pierre-yves.david at ens-lyon.org
Thu Mar 30 11:03:40 EDT 2017


# HG changeset patch
# User Pierre-Yves David <pierre-yves.david at ens-lyon.org>
# Date 1490675889 -7200
#      Tue Mar 28 06:38:09 2017 +0200
# Node ID 25952f350daaae03506991d566c8de170b5db094
# Parent  f6e7b8a14f4a94db6742410331db752112c76b82
# EXP-Topic tags
# Available At https://www.mercurial-scm.org/repo/users/marmoute/mercurial/
#              hg pull https://www.mercurial-scm.org/repo/users/marmoute/mercurial/ -r 25952f350daa
track-tags: introduce first bits of tags tracking during transaction

This changeset introduces detection of tags changes during transaction. When
this happens a 'tag_moved=1' argument is set for hooks, similar to what we do
for bookmarks and phases.

This code is disabled by default as there is still various performance
concerns.  Some require a smarter use of our existing tag caches and some other
require rework around the transaction logic to skip execution when unneeded.
These performance improvement have been delayed, I would like to be able to
experiment and stabilize the feature behavior first.

Later changesets will push the concept further and provide a way for hooks to
knows what are the actual changes introduced by the transaction. Similar work
is needed for the other families of changes (bookmark, phase, obsolescence,
etc). Upgrade of the transaction logic will likely be performed at the same
time.

The current code can report some false positive when .hgtags file changes but
resulting tags are unchanged. This will be fixed in the next changeset.

For testing, we simply globally enable a hook in the tag test as all the
possible tag update cases should exist there. A couple of them show the false
positive mentionned above.

See in code documentation for more details.

diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -1001,8 +1001,54 @@ class localrepository(object):
         vfsmap = {'plain': self.vfs} # root of .hg/
         # we must avoid cyclic reference between repo and transaction.
         reporef = weakref.ref(self)
-        def validate(tr):
+        # Code to track tag movement
+        #
+        # Since tags are all handled as file content, it is actually quite hard
+        # to track these movement from a code perspective. So we fallback to a
+        # tracking at the repository level. One could envision to track changes
+        # to the '.hgtags' file through changegroup apply but that fails to
+        # cope with case where transaction expose new heads without changegroup
+        # being involved (eg: phase movement).
+        #
+        # For now, We gate the feature behind a flag since this likely comes
+        # with performance impacts. The current code run more often than needed
+        # and do not use caches as much as it could.  The current focus is on
+        # the behavior of the feature so we disable it by default. The flag
+        # will be removed when we are happy with the performance impact.
+        tracktags = lambda x: None
+        # experimental config: experimental.hook-track-tags
+        shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags',
+                                             False)
+        if desc != 'strip' and shouldtracktags:
+            oldheads = self.changelog.headrevs()
+            def tracktags(tr2):
+                repo = reporef()
+                oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
+                newheads = repo.changelog.headrevs()
+                newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
+                # notes: we compare lists here.
+                # As we do it only once buiding set would not be cheaper
+                if oldfnodes != newfnodes:
+                    tr2.hookargs['tag_moved'] = '1'
+        def validate(tr2):
             """will run pre-closing hooks"""
+            # XXX the transaction API is a bit lacking here so we take a hacky
+            # path for now
+            #
+            # We cannot add this as a "pending" hooks since the 'tr.hookargs'
+            # dict is copied before these run. In addition we needs the data
+            # available to in memory hooks too.
+            #
+            # Moreover, we also needs to make sure this runs before txnclose
+            # hooks and there is no "pending" mechanism that would execute
+            # logic only if hooks are about to run.
+            #
+            # Fixing this limitation of the transaction is also needed to track
+            # other families of changes (bookmarks, phases, obsolescence).
+            #
+            # This will have to be fixed before we remove the experimental
+            # gating.
+            tracktags(tr2)
             reporef().hook('pretxnclose', throw=True,
                            txnname=desc, **pycompat.strkwargs(tr.hookargs))
         def releasefn(tr, success):
diff --git a/tests/test-tag.t b/tests/test-tag.t
--- a/tests/test-tag.t
+++ b/tests/test-tag.t
@@ -1,3 +1,19 @@
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > hook-track-tags=1
+  > [hooks]
+  > txnclose.track-tag=${TESTTMP}/taghook.sh
+  > EOF
+
+  $ cat << EOF > taghook.sh
+  > #!/bin/sh
+  > # escape the "$" otherwise the test runner interpret it when writting the
+  > # file...
+  > if [ -n "\$HG_TAG_MOVED" ]; then
+  >     echo 'hook: tag changes detected'
+  > fi
+  > EOF
+  $ chmod +x taghook.sh
   $ hg init test
   $ cd test
 
@@ -20,6 +36,7 @@
 specified)
 
   $ HGEDITOR=cat hg tag "bleah"
+  hook: tag changes detected
   $ hg history
   changeset:   1:d4f0d2909abc
   tag:         tip
@@ -68,10 +85,14 @@ specified)
   [255]
 
   $ hg tag -r 0 "bleah0"
+  hook: tag changes detected
   $ hg tag -l -r 1 "bleah1"
   $ hg tag gack gawk gorp
+  hook: tag changes detected
   $ hg tag -f gack
+  hook: tag changes detected
   $ hg tag --remove gack gorp
+  hook: tag changes detected
 
   $ hg tag "bleah "
   abort: tag 'bleah' already exists (use -f to force)
@@ -83,7 +104,9 @@ specified)
   abort: tag 'bleah' already exists (use -f to force)
   [255]
   $ hg tag -r 0 "  bleahbleah  "
+  hook: tag changes detected
   $ hg tag -r 0 " bleah bleah "
+  hook: tag changes detected
 
   $ cat .hgtags
   acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
@@ -112,6 +135,7 @@ tagging on a non-head revision
   abort: working directory is not at a branch head (use -f to force)
   [255]
   $ hg tag -f "foobar"
+  hook: tag changes detected
   $ cat .hgtags
   acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
   $ cat .hg/localtags
@@ -169,16 +193,19 @@ cloning local tags
   summary:     Removed tag gack, gorp
   
   $ hg clone -q -rbleah1 test test1
+  hook: tag changes detected
   $ hg -R test1 parents --style=compact
   1[tip]   d4f0d2909abc   1970-01-01 00:00 +0000   test
     Added tag bleah for changeset acb14030fe0a
   
   $ hg clone -q -r5 test#bleah1 test2
+  hook: tag changes detected
   $ hg -R test2 parents --style=compact
   5[tip]   b4bb47aaff09   1970-01-01 00:00 +0000   test
     Removed tag gack, gorp
   
   $ hg clone -q -U test#bleah1 test3
+  hook: tag changes detected
   $ hg -R test3 parents --style=compact
 
   $ cd test
@@ -203,9 +230,11 @@ doesn't end with EOL
   > f = file('.hgtags', 'w'); f.write(last); f.close()
   > EOF
   $ hg ci -m'broken manual edit of .hgtags'
+  hook: tag changes detected
   $ cat .hgtags; echo
   acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
   $ hg tag newline
+  hook: tag changes detected
   $ cat .hgtags; echo
   acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
   a0eea09de1eeec777b46f2085260a373b2fbc293 newline
@@ -219,6 +248,7 @@ tag and branch using same name
   $ hg ci -m"discouraged"
   $ hg tag tag-and-branch-same-name
   warning: tag tag-and-branch-same-name conflicts with existing branch name
+  hook: tag changes detected
 
 test custom commit messages
 
@@ -303,6 +333,7 @@ then, test custom commit message itself
   HG: branch 'tag-and-branch-same-name'
   HG: changed .hgtags
   ====
+  hook: tag changes detected
   $ hg log -l1 --template "{desc}\n"
   custom tag message
   second line
@@ -311,6 +342,7 @@ then, test custom commit message itself
 local tag with .hgtags modified
 
   $ hg tag hgtags-modified
+  hook: tag changes detected
   $ hg rollback
   repository tip rolled back to revision 13 (undo commit)
   working directory now based on revision 13
@@ -330,9 +362,11 @@ tagging when at named-branch-head that's
   0 files updated, 1 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
   $ hg ci -m 'merge named branch'
+  hook: tag changes detected
   $ hg up 13
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg tag new-topo-head
+  hook: tag changes detected
 
 tagging on null rev
 
@@ -399,12 +433,14 @@ commit hook on tag used to be run withou
   > hg push "$TESTTMP/repo-tag-target"
   > EOF
   $ hg -R repo-tag --config hooks.commit="sh ../issue3344.sh" tag tag
+  hook: tag changes detected
   pushing to $TESTTMP/repo-tag-target (glob)
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  hook: tag changes detected
 
 automatically merge resolvable tag conflicts (i.e. tags that differ in rank)
 create two clones with some different tags as well as some common tags
@@ -416,6 +452,7 @@ check that we can merge tags that differ
   $ hg ci -A -m0
   adding f0
   $ hg tag tbase
+  hook: tag changes detected
   $ hg up -qr '.^'
   $ hg log -r 'wdir()' -T "{latesttagdistance}\n"
   1
@@ -431,18 +468,23 @@ check that we can merge tags that differ
   $ hg ci -A -m1
   adding f1
   $ hg tag t1 t2 t3
+  hook: tag changes detected
   $ hg tag --remove t2
+  hook: tag changes detected
   $ hg tag t5
+  hook: tag changes detected
   $ echo c2 > f2
   $ hg ci -A -m2
   adding f2
   $ hg tag -f t3
+  hook: tag changes detected
 
   $ cd ../repo-automatic-tag-merge
   $ echo c3 > f3
   $ hg ci -A -m3
   adding f3
   $ hg tag -f t4 t5 t6
+  hook: tag changes detected
 
   $ hg up -q '.^'
   $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
@@ -455,6 +497,7 @@ check that we can merge tags that differ
   $ hg up -qC
 
   $ hg tag --remove t5
+  hook: tag changes detected
   $ echo c4 > f4
   $ hg log -r '.' -T "{changessincelatesttag} changes since {latesttag}\n"
   2 changes since t4:t6
@@ -473,7 +516,9 @@ check that we can merge tags that differ
   $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
   4 changes since t4:t6
   $ hg tag t2
+  hook: tag changes detected
   $ hg tag -f t6
+  hook: tag changes detected
 
   $ cd ../repo-automatic-tag-merge-clone
   $ hg pull
@@ -483,6 +528,7 @@ check that we can merge tags that differ
   adding manifests
   adding file changes
   added 6 changesets with 6 changes to 3 files (+1 heads)
+  hook: tag changes detected
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge --tool internal:tagmerge
   merging .hgtags
@@ -543,10 +589,12 @@ detect merge tag conflicts
   $ hg update -C -r tip
   3 files updated, 0 files merged, 2 files removed, 0 files unresolved
   $ hg tag t7
+  hook: tag changes detected
   $ hg update -C -r 'first(sort(head()))'
   3 files updated, 0 files merged, 2 files removed, 0 files unresolved
   $ printf "%s %s\n" `hg log -r . --template "{node} t7"` >> .hgtags
   $ hg commit -m "manually add conflicting t7 tag"
+  hook: tag changes detected
   $ hg merge --tool internal:tagmerge
   merging .hgtags
   automatic .hgtags merge failed
@@ -581,11 +629,13 @@ handle the loss of tags
   $ hg ci -A -m5
   adding f5
   $ hg tag -f t7
+  hook: tag changes detected
   $ hg update -r 'p1(t7)'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ printf '' > .hgtags
   $ hg commit -m 'delete all tags'
   created new head
+  hook: tag changes detected
   $ hg log -r 'max(t7::)'
   changeset:   17:ffe462b50880
   user:        test


More information about the Mercurial-devel mailing list