[PATCH RFC] revset: add pattern matching to 'tag' revset expression

Simon King simon at simonking.org.uk
Fri May 25 16:23:02 CDT 2012


# HG changeset patch
# User Simon King <simon at simonking.org.uk>
# Date 1337980885 -3600
# Node ID acd9d3574a98a80e9db12b89a6f338d1541f3a1d
# Parent  2ac08d8b21aa7b6e0a062afed5a3f357ccef67f9
revset: add pattern matching to 'tag' revset expression

The 'tag' revset expression now takes an optional second parameter, which
is either 're' or 'glob'. When one of these is supplied, the tag name is
treated as a pattern, and any tags which match the pattern will be returned
from the expression.

Outstanding questions:

1. Is an optional parameter the right way to support this? Would it be better
   to have a separate expression, or use 're:' and 'glob:' prefixes on the
   tag name parameter instead?

2. Is it OK to use the python fnmatch module for glob patterns? Mercurial's
   match module seems much more geared towards filename matching; even the
   _globre function treats path separators specially.

3. Patterns must match from the start of the tag name. I think this would be
   the least surprising behaviour, but perhaps others may disagree.

diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -6,6 +6,7 @@
 # GNU General Public License version 2 or any later version.
 
 import re
+import fnmatch
 import parser, util, error, discovery, hbisect, phases
 import node
 import bookmarks as bookmarksmod
@@ -1120,19 +1121,42 @@
     return [e[-1] for e in l]
 
 def tag(repo, subset, x):
-    """``tag([name])``
+    """``tag([name[, patterntype]])``
     The specified tag by name, or all tagged revisions if no name is given.
+
+    If patterntype is passed, it may be one of 're' or 'glob'. Revisions
+    with tags matching the pattern will be returned.
     """
     # i18n: "tag" is a keyword
-    args = getargs(x, 0, 1, _("tag takes one or no arguments"))
+    args = getargs(x, 0, 2, _("tag takes at most 2 arguments"))
     cl = repo.changelog
     if args:
         tn = getstring(args[0],
                        # i18n: "tag" is a keyword
                        _('the argument to tag must be a string'))
-        if not repo.tags().get(tn, None):
-            raise util.Abort(_("tag '%s' does not exist") % tn)
-        s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
+        if len(args) == 1:
+            if not repo.tags().get(tn, None):
+                raise util.Abort(_("tag '%s' does not exist") % tn)
+            s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
+        else:
+            # i18n: "tag" is a keyword
+            typemsg = _("the second argument to tag must be 're' or 'glob'")
+            patterntype = getstring(args[1], typemsg)
+            if patterntype == 're':
+                try:
+                    regexp = re.compile(tn)
+                except re.error, e:
+                    raise error.ParseError(_("invalid match pattern: %s") % e)
+            elif patterntype == 'glob':
+                regexp = re.compile(fnmatch.translate(tn))
+            else:
+                raise util.Abort(typemsg)
+            s = set()
+            for t, n in repo.tagslist():
+                if regexp.match(t):
+                    s.add(cl.rev(n))
+            if not s:
+                raise util.Abort(_('no tags match pattern "%s"') % tn)
     else:
         s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
     return [r for r in subset if r in s]
diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -369,6 +369,16 @@
   6
   $ log 'tag(tip)'
   9
+  $ log 'tag("1.*", "glob")'
+  6
+  $ log 'tag("0.*", "glob")'
+  abort: no tags match pattern "0.*"
+  [255]
+  $ log 'tag("foo", "re")'
+  abort: no tags match pattern "foo"
+  [255]
+  $ log 'tag("[0-9].[0-9]", "re")'
+  6
   $ log 'tag(unknown)'
   abort: tag 'unknown' does not exist
   [255]


More information about the Mercurial-devel mailing list