Enhanced hg identify command

Gilles Moris gilles.moris at free.fr
Sat Jan 31 12:40:23 CST 2009


Hello,

Here is a follow up of the discussion about including pieces of the nearest
extension into the core. Here is a prototype before I go further.
I have started with hg id. I will see the templater later.

My goal is to extend the --tags option in a "git describe" way, to have a
seamless integration.
If tags are found on the given revision, then nothing changes. Otherwise,
several additional options will allow to search down or up in history for
the nearest revisions with tags:
 -d --describe      show nearest tags back in history
 -D --describe-all  show all nearest tags back in history
 -c --contains      show nearest tags forward in history
 -C --contains-all  show all nearest tags forward in history

The reason for having 4 options instead of 2 is for performance: I did not want
to penalize people wanting to stop on the first revision with tags. But all of
them will imply --tags.
Note that --describe or --contains doesn't mean only one tag will be displayed:
like hg id -t, the found revision can have several tags.
In a similar way, the "all" options will not display all the tags, as I stop
the search in a branch once a tag is found.

Before I go further, I would like to have a first review to see if I am going
in the right direction.

examples:
% hg id -r 7720 -D
1.1.2+33 1.1.1+141 1.1+176 1.0.2+231 1.0.1+642 1.0+791 0.9.5+1090 0.9.4+1087
% hg id -c -r 6000
1.0-342
% hg id -cd -r 6000
0.9.5+79 1.0-342

Regards.
Gilles.


diff -r a1138f437640 mercurial/commands.py
--- a/mercurial/commands.py     Thu Jan 29 19:25:25 2009 +0100
+++ b/mercurial/commands.py     Sat Jan 31 19:33:46 2009 +0100
@@ -1514,7 +1514,9 @@
                 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,
+             describe=None, describe_all=None,
+             contains=None, contains_all=None):
     """identify the working copy or specified revision

     With no revision, print a summary of the current state of the repo.
@@ -1532,6 +1534,8 @@
                            "(.hg not found)"))

     hexfunc = ui.debugflag and hex or short
+    if not tags:
+        tags = describe or describe_all or contains or contains_all
     default = not (num or id or branch or tags)
     output = []

@@ -1579,7 +1583,19 @@
         output.append(util.tolocal(ctx.branch()))

     if tags:
-        output.extend(ctx.tags())
+        tags = ctx.tags()
+        output.extend(tags)
+        if not tags:
+            if describe or describe_all:
+                for c, ts, d in ctx.nearesttagsdateh(fwd=False):
+                    output.extend([ '%s+%d' % (t, d) for t in ts ])
+                    if not describe_all:
+                        break
+            if contains or contains_all:
+                for c, ts, d in ctx.nearesttagsdateh(fwd=True):
+                    output.extend([ '%s-%d' % (t, d)  for t in ts ])
+                    if not contains_all:
+                        break

     ui.write("%s\n" % ' '.join(output))

@@ -3196,7 +3212,11 @@
           ('n', 'num', None, _('show local revision number')),
           ('i', 'id', None, _('show global revision id')),
           ('b', 'branch', None, _('show branch')),
-          ('t', 'tags', None, _('show tags'))],
+          ('t', 'tags', None, _('show tags')),
+          ('d', 'describe', None, _('show nearest tags back in history')),
+          ('D', 'describe-all', None, _('show all nearest tags back in history')),
+          ('c', 'contains', None, _('show nearest tags forward in history')),
+          ('C', 'contains-all', None, _('show all nearest tags forward in history'))],
          _('[-nibt] [-r REV] [SOURCE]')),
     "import|patch":
         (import_,
diff -r a1138f437640 mercurial/context.py
--- a/mercurial/context.py      Thu Jan 29 19:25:25 2009 +0100
+++ b/mercurial/context.py      Sat Jan 31 19:33:46 2009 +0100
@@ -7,7 +7,7 @@

 from node import nullid, nullrev, short, hex
 from i18n import _
-import ancestor, bdiff, error, util, os, errno
+import ancestor, bdiff, error, util, os, errno, heapq

 class propertycache(object):
     def __init__(self, func):
@@ -177,6 +177,62 @@
             if match.bad(fn, 'No such file in rev ' + str(self)) and match(fn):
                 yield fn

+    def nearesttagsdateh(self, fwd, cut=True):
+        """search for cset with tags trying to return csets ordered by date
+        this is a generator function that returns a tuple
+        (changectx, csettags, distancefromnode)
+        it searches like a breadth first search algorithm except that an
+        heuristic is set on date to give them priority over number of hops
+        arguments:
+        fwd(bool): True if need to search forward in history
+        cut(bool): True if the search is interrupted after a tag has been found
+                   on a branch
+        """
+        # initialize the search algorithm depending on the search direction
+        if fwd:
+            heuristic = lambda c: c.date()[0]
+            children = [[]] * (len(self._repo.changelog) + 1)
+            for r in xrange(self.rev() + 1, len(self._repo.changelog)):
+                for pr in self._repo.changelog.parentrevs(r):
+                    if not children[pr]:
+                        children[pr] = [r]
+                    else:
+                        children[pr].append(r)
+            infants = lambda r: children[r]
+        else:
+            heuristic = lambda c: - c.date()[0]
+            infants = lambda r: [ p for p in self._repo.changelog.parentrevs(r)
+                                  if p != nullrev ]
+
+        # use a distance map and a pseudo sorted list by date
+        # this is a generalised breadth first search with the date as heuristic
+        dmap = [0] * (len(self._repo.changelog) + 1)
+        heap = []
+        heapq.heappush(heap, (heuristic(self), self))
+
+        while heap:
+            # get the highest (resp lowest) date with heappop
+            date, ctx = heapq.heappop(heap)
+            rev = ctx.rev()
+            d = dmap[rev]
+            tags = ctx.tags()
+            if tags:
+                yield ctx, tags, d
+                # cut branch once a tag is found
+                if cut:
+                    continue
+            # queue neighbours to propagate search
+            for nr in infants(rev):
+                if dmap[nr]:
+                    # if a node was already queued, retains the longest path
+                    # this also avoid revisiting an already visited node
+                    dmap[nr] = max(dmap[nr], d+1)
+                else:
+                    n = self._repo[nr]
+                    # queue another node keeping the pseudo sorted assertion
+                    heapq.heappush(heap, (heuristic(n), n))
+                    dmap[nr] = d + 1
+
 class filectx(object):
     """A filecontext object makes access to data related to a particular
        filerevision convenient."""


More information about the Mercurial-devel mailing list