[PATCH 2 of 2] identify: add the --locate flag to display nearest surrounding tags

Gilles Moris gilles.moris at free.fr
Fri Aug 28 02:38:28 CDT 2009


 mercurial/cmdutil.py         |  69 ++++++++++++++++++++++++++++++++++
 mercurial/commands.py        |  28 ++++++++++++--
 tests/test-debugcomplete.out |   2 +-
 tests/test-identify          |  63 +++++++++++++++++++++++++++++++
 tests/test-identify.out      |  23 +++++++++++
 5 files changed, 180 insertions(+), 5 deletions(-)


# HG changeset patch
# User Gilles Moris <gilles.moris at free.fr>
# Date 1234603377 -3600
# Node ID 1583cb23bd884b0bcdc473054b1076f7a20f0a43
# Parent  6fed19d04b0d859931837172ad974edb1e7b6b80
identify: add the --locate flag to display nearest surrounding tags

The goal is to give an idea of the position of the revision in the history
topology (where are the nearest tags? on which named branch?).

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1287,3 +1287,72 @@
             ltmap[r] = ldate, ldist + 1, ltag
 
     return ltmap
+
+def nearesttags(repo, rev):
+    # ctx.children() is way too slow compared to ctx.parents(), upto
+    # two orders of magnitude. So we build our own cache to replace it.
+    chldcache = [[]] * (len(repo))
+    for r in range(len(repo)):
+        for pr in [p.rev() for p in repo[r].parents() if p.rev() != nullrev]:
+            if not chldcache[pr]:
+                chldcache[pr] = [r]
+            else:
+                chldcache[pr].append(r)
+
+    # compute the DAG distance map from the seeded rev
+    # initialize with -1 meaning unreachable rev
+    dmap = [-1] * len(repo)
+    dmap[rev] = 0
+    # distance map for descendants
+    for r in range(rev+1, len(repo)):
+        dl = [dmap[p.rev()] for p in repo[r].parents()
+              if p.rev() != nullrev and dmap[p.rev()] >= 0]
+        if dl:
+            dmap[r] = max(dl) + 1
+
+    # distance map for ancestors
+    for r in range(rev-1, -1, -1):
+        # we know that ancestors cannot be > rev (DAG partial ordering)
+        dl = [dmap[p] for p in chldcache[r] if dmap[p] >= 0 and p <= rev]
+        if dl:
+            dmap[r] = max(dl) + 1
+
+    # get revisions of relevant tags in the repo
+    revtags = set([repo.changelog.rev(n) for t, n in repo.tags().items()
+                   if repo.tagtype(t) and dmap[repo.changelog.rev(n)] >= 0])
+
+    def obliteratedeps(infants):
+        rl = set(infants(r))
+        while rl:
+            rr = rl.pop()
+            dmap[rr] = -1
+            for pr in infants(rr):
+                if dmap[pr] >= 0:
+                    rl.add(pr)
+    # we just want the first tag on each branch to be reported so ...
+    for r in revtags:
+        # ... set ancestors (resp descendants) as unreachable
+        if r < rev:
+            obliteratedeps(lambda rr: [p.rev() for p in repo[rr].parents()
+                                       if p.rev() != nullrev])
+        elif r > rev:
+            obliteratedeps(lambda rr: chldcache[rr])
+
+    # dispatch remaining tags in last or next tags lists
+    lasttags = []
+    nexttags = []
+    for r in [rr for rr in revtags if dmap[rr] >= 0]:
+        c = repo[r]
+        tags = sorted(t for t in c.tags() if repo.tagtype(t))
+        b = c.branch()
+        if r <= rev:
+            lasttags.append((-c.date()[0], dmap[r], tags, r, b))
+        elif r >= rev:
+            nexttags.append((c.date()[0], dmap[r], tags, r, b))
+
+    # sort by nearest (i.e. highest, resp. lowest) date
+    # for comparison of tuples, see python tutorial 5.8
+    lasttags = [(r, d, t, b) for dt, d, t, r, b in sorted(lasttags)]
+    nexttags = [(r, d, t, b) for dt, d, t, r, b in sorted(nexttags)]
+
+    return lasttags, nexttags
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1634,7 +1634,7 @@
                 ui.write("%s\n" % first)
 
 def identify(ui, repo, source=None,
-             rev=None, num=None, id=None, branch=None, tags=None):
+             rev=None, num=None, id=None, branch=None, tags=None, locate=None):
     """identify the working copy or specified revision
 
     With no revision, print a summary of the current state of the
@@ -1667,7 +1667,7 @@
             rev = revs[0]
         if not rev:
             rev = "tip"
-        if num or branch or tags:
+        if num or branch or tags or locate:
             raise util.Abort(
                 "can't query remote revision number, branch, or tags")
         output = [hexfunc(repo.lookup(rev))]
@@ -1708,6 +1708,25 @@
 
     ui.write("%s\n" % ' '.join(output))
 
+    if locate:
+        if isinstance(ctx, context.workingctx):
+            ctx = ctx.parents()[0]
+
+        def dpynsttags(fwd, nl, output):
+            for rev, dist, taglist, branch in nl:
+                if branch != 'default':
+                    brname = ' ' + branch
+                else:
+                    brname = ''
+                output.append('/'.join(taglist) +
+                              '(%s%d%s)' % (fwd and '+' or '-', dist, brname))
+            if len(output) > 1:
+                ui.write("%s\n" % ' '.join(output))
+
+        lt, nt = cmdutil.nearesttags(repo, ctx.rev())
+        dpynsttags(False, lt, ['based on'])
+        dpynsttags(True, nt, ['went in'])
+
 def import_(ui, repo, patch1, *patches, **opts):
     """import an ordered set of patches
 
@@ -3348,8 +3367,9 @@
           ('n', 'num', None, _('show local revision number')),
           ('i', 'id', None, _('show global revision id')),
           ('b', 'branch', None, _('show branch')),
-          ('t', 'tags', None, _('show tags'))],
-         _('[-nibt] [-r REV] [SOURCE]')),
+          ('t', 'tags', None, _('show tags')),
+          ('L', 'locate', None, _('show how far are the nearest tags'))],
+         _('[-nibtL] [-r REV] [SOURCE]')),
     "import|patch":
         (import_,
          [('p', 'strip', 1,
diff --git a/tests/test-debugcomplete.out b/tests/test-debugcomplete.out
--- a/tests/test-debugcomplete.out
+++ b/tests/test-debugcomplete.out
@@ -207,7 +207,7 @@
 grep: print0, all, follow, ignore-case, files-with-matches, line-number, rev, user, date, include, exclude
 heads: rev, active, closed, style, template
 help: 
-identify: rev, num, id, branch, tags
+identify: rev, num, id, branch, tags, locate
 import: strip, base, force, no-commit, exact, import-branch, message, logfile, date, user, similarity
 incoming: force, newest-first, bundle, rev, patch, git, limit, no-merges, style, template, ssh, remotecmd
 locate: rev, print0, fullpath, include, exclude
diff --git a/tests/test-identify b/tests/test-identify
--- a/tests/test-identify
+++ b/tests/test-identify
@@ -43,4 +43,67 @@
 echo % remote with tags?
 hg id -t http://localhost:$HGPORT1/
 
+hg revert a
+hg branch -q stable
+echo fix1 > b
+hg add b
+hg ci -d '1 0' -mfix1
+hg up -q default
+echo feat1 >> a
+hg ci -d '2 0' -mfeat1
+echo feat2 >> a
+hg ci -d '3 0' -mfeat2
+hg merge -q stable
+hg ci -d '4 0' -mmerge1
+
+hg up -q stable
+hg merge -q default
+hg ci -d '5 0' -mmergeback
+
+echo fix2 >> b
+hg ci -d '6 0' -m fix2
+echo fix3 >> b
+hg ci -d '7 0' -m fix3
+hg up -q default
+echo feat3 >> a
+hg ci -d '8 0' -mfeat3
+hg merge -q stable
+hg ci -d '9 0' -mmerge2
+
+echo % test --locate
+echo "[extensions]" >> $HGRCPATH
+echo "mq =" >> $HGRCPATH
+hg qnew -d '10 0' patch1
+echo c > c
+hg add c
+hg qrefresh -d '10 0'
+hg qnew -d '11 0' patch2
+echo c >> c
+hg qrefresh -d '11 0'
+
+echo % at qtip
+hg id -L
+hg id -L -r 4
+
+echo % tags top and bottom
+hg tag -l -r 9 top
+hg tag -l -r 9 alttop
+hg tag -l -r 0 bottom
+hg id -L
+hg id -L -r 4
+
+echo % nearest tags by date
+hg up 4
+hg tag -l -r 2 exp1
+hg tag -l -r 7 st2
+hg id -L
+
+echo % closer tags by hops after
+hg tag -l -r 1 st1
+hg tag -l -r 8 exp2
+hg id -L
+
+echo % remote verbose
+hg id -L http://localhost:$HGPORT1/
+
 true # ends with util.Abort -> returns 255
diff --git a/tests/test-identify.out b/tests/test-identify.out
--- a/tests/test-identify.out
+++ b/tests/test-identify.out
@@ -23,3 +23,26 @@
 cb9a9f314b8b
 % remote with tags?
 abort: can't query remote revision number, branch, or tags
+created new head
+created new head
+% test --locate
+% at qtip
+04b2329333f9 qtip/patch2/tip
+7fe7ca622a6b
+% tags top and bottom
+04b2329333f9 qtip/tip/patch2
+based on alttop/top(-2)
+7fe7ca622a6b
+based on bottom(-3)
+went in alttop/top(+4)
+% nearest tags by date
+2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+7fe7ca622a6b
+based on exp1(-2)
+went in st2(+3 stable)
+% closer tags by hops after
+7fe7ca622a6b
+based on exp1(-2) st1(-1 stable)
+went in st2(+3 stable) exp2(+1)
+% remote verbose
+abort: can't query remote revision number, branch, or tags


More information about the Mercurial-devel mailing list