changeset 9536:f04d17912441

cmdutil: templating keywords latesttag and latesttagdistance This can be used for referring to revisions in a reasonable meaningful, stable and monotonically increasing way, suitable for releases or builds directly from a repository. The latest tag is found by searching through untagged ancestors and finding the latest tagged ancestor based on tag date. The distance is found from the length of the longest path to the tagged revision. For example: hg log -l1 --template '{latesttag}+{latesttagdistance}\n' can return 1.3.1+197 This is mostly work by Gilles Moris <gilles.moris@free.fr>
author Mads Kiilerich <mads@kiilerich.com>
date Sat, 03 Oct 2009 18:31:20 +0200
parents 75d290db2df6
children 460e410c39be
files mercurial/cmdutil.py mercurial/help.py tests/test-command-template tests/test-command-template.out
diffstat 4 files changed, 133 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/cmdutil.py	Sat Oct 03 23:38:10 2009 +0200
+++ b/mercurial/cmdutil.py	Sat Oct 03 18:31:20 2009 +0200
@@ -745,6 +745,9 @@
                                          'parent': '{rev}:{node|formatnode} ',
                                          'manifest': '{rev}:{node|formatnode}',
                                          'filecopy': '{name} ({source})'})
+        # Cache mapping from rev to a tuple with tag date, tag
+        # distance and tag name
+        self._latesttagcache = {-1: (0, 0, 'null')}
 
     def use_template(self, t):
         '''set template string to use'''
@@ -762,6 +765,30 @@
             return []
         return parents
 
+    def _latesttaginfo(self, rev):
+        '''return date, distance and name for the latest tag of rev'''
+        todo = [rev]
+        while todo:
+            rev = todo.pop()
+            if rev in self._latesttagcache:
+                continue
+            ctx = self.repo[rev]
+            tags = [t for t in ctx.tags() if self.repo.tagtype(t) == 'global']
+            if tags:
+                self._latesttagcache[rev] = ctx.date()[0], 0, ':'.join(sorted(tags))
+                continue
+            try:
+                # The tuples are laid out so the right one can be found by comparison.
+                pdate, pdist, ptag = max(
+                    self._latesttagcache[p.rev()] for p in ctx.parents())
+            except KeyError:
+                # Cache miss - recurse
+                todo.append(rev)
+                todo.extend(p.rev() for p in ctx.parents())
+                continue
+            self._latesttagcache[rev] = pdate, pdist + 1, ptag
+        return self._latesttagcache[rev]
+
     def _show(self, ctx, copies, props):
         '''show a single changeset or file revision'''
 
@@ -879,6 +906,11 @@
                 removes += i[2]
             return '%s: +%s/-%s' % (files, adds, removes)
 
+        def showlatesttag(**args):
+            return self._latesttaginfo(ctx.rev())[2]
+        def showlatesttagdistance(**args):
+            return self._latesttaginfo(ctx.rev())[1]
+
         defprops = {
             'author': ctx.user(),
             'branches': showbranches,
@@ -896,6 +928,8 @@
             'tags': showtags,
             'extras': showextras,
             'diffstat': showdiffstat,
+            'latesttag': showlatesttag,
+            'latesttagdistance': showlatesttagdistance,
             }
         props = props.copy()
         props.update(defprops)
--- a/mercurial/help.py	Sat Oct 03 23:38:10 2009 +0200
+++ b/mercurial/help.py	Sat Oct 03 18:31:20 2009 +0200
@@ -393,6 +393,9 @@
                 number.
     :tags:      List of strings. Any tags associated with the
                 changeset.
+    :latesttag: String. Most recent global tag in the ancestors of this
+                changeset.
+    :latesttagdistance: Integer. Longest path to the latest tag.
 
     The "date" keyword does not produce human-readable output. If you
     want to use a date in your output, you can use a filter to process
--- a/tests/test-command-template	Sat Oct 03 23:38:10 2009 +0200
+++ b/tests/test-command-template	Sat Oct 03 18:31:20 2009 +0200
@@ -127,4 +127,49 @@
 echo 'x = "f' >> t
 hg log
 
+cd ..
+
+echo '# latesttag'
+hg init latesttag
+cd latesttag
+
+echo a > file
+hg ci -Am a -d '0 0'
+
+echo b >> file
+hg ci -m b -d '1 0'
+
+echo c >> head1
+hg ci -Am h1c -d '2 0'
+
+hg update -q 1
+echo d >> head2
+hg ci -Am h2d -d '3 0'
+
+echo e >> head2
+hg ci -m h2e -d '4 0'
+
+hg merge -q
+hg ci -m merge -d '5 0'
+
+echo '# No tag set'
+hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
+
+echo '# one common tag: longuest path wins'
+hg tag -r 1 -m t1 -d '6 0' t1
+hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
+
+echo '# one ancestor tag: more recent wins'
+hg tag -r 2 -m t2 -d '7 0' t2
+hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
+
+echo '# two branch tags: more recent wins'
+hg tag -r 3 -m t3 -d '8 0' t3
+hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
+
+echo '# merged tag overrides'
+hg tag -r 5 -m t5 -d '9 0' t5
+hg tag -r 3 -m at3 -d '10 0' at3
+hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
+
 echo '# done'
--- a/tests/test-command-template.out	Sat Oct 03 23:38:10 2009 +0200
+++ b/tests/test-command-template.out	Sat Oct 03 18:31:20 2009 +0200
@@ -672,4 +672,55 @@
 1e4e1b8f71e05681d422154f5421e385fec3454f
 # error on syntax
 abort: t:3: unmatched quotes
+# latesttag
+adding file
+adding head1
+adding head2
+created new head
+# No tag set
+5: null+5
+4: null+4
+3: null+3
+2: null+3
+1: null+2
+0: null+1
+# one common tag: longuest path wins
+6: t1+4
+5: t1+3
+4: t1+2
+3: t1+1
+2: t1+1
+1: t1+0
+0: null+1
+# one ancestor tag: more recent wins
+7: t2+3
+6: t2+2
+5: t2+1
+4: t1+2
+3: t1+1
+2: t2+0
+1: t1+0
+0: null+1
+# two branch tags: more recent wins
+8: t3+5
+7: t3+4
+6: t3+3
+5: t3+2
+4: t3+1
+3: t3+0
+2: t2+0
+1: t1+0
+0: null+1
+# merged tag overrides
+10: t5+5
+9: t5+4
+8: t5+3
+7: t5+2
+6: t5+1
+5: t5+0
+4: at3:t3+1
+3: at3:t3+0
+2: t2+0
+1: t1+0
+0: null+1
 # done