[PATCH 1 of 5 v3] revset: add pattern matching to 'tag' revset expression

Simon King simon at simonking.org.uk
Mon May 28 16:43:49 CDT 2012


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

If the string provided to the 'tag' predicate starts with 're:', the rest
of the string will be treated as a regular expression and matched against
all tags in the repository.

There is a slight backwards-compatibility problem for people who actually
have tags that start with 're:'. As a workaround, these tags can be matched
using a 'literal:' prefix.

If no tags match the pattern, an error is raised. This matches the behaviour
of the previous exact-match code.

diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -1119,20 +1119,77 @@
     l.sort()
     return [e[-1] for e in l]
 
+def _stringmatcher(pattern):
+    """
+    accepts a string, possibly starting with 're:' or 'literal:' prefix.
+    returns the matcher name, pattern, and matcher function.
+    missing or unknown prefixes are treated as literal matches.
+
+    helper for tests:
+    >>> def test(pattern, *tests):
+    ...     kind, pattern, matcher = _stringmatcher(pattern)
+    ...     print (kind, pattern, map(matcher, tests))
+
+    exact matching (no prefix):
+    >>> test('abcdefg', 'abc', 'def', 'abcdefg')
+    ('literal', 'abcdefg', [False, False, True])
+
+    regex matching ('re:' prefix)
+    >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
+    ('re', 'a.+b', [False, False, True])
+
+    force exact matches ('literal:' prefix)
+    >>> test('literal:re:foobar', 'foobar', 're:foobar')
+    ('literal', 're:foobar', [False, True])
+
+    unknown prefixes are ignored and treated as literals
+    >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
+    ('literal', 'foo:bar', [False, False, True])
+    """
+    if ':' in pattern:
+        prefix, pattern = pattern.split(':', 1)
+    else:
+        prefix = 'literal'
+
+    if prefix == 're':
+        try:
+            regex = re.compile(pattern)
+        except re.error, e:
+            raise error.ParseError(_('invalid regular expression: %s')
+                                   % e)
+        return 're', pattern, lambda s: bool(regex.search(s))
+    elif prefix == 'literal':
+        return prefix, pattern, lambda s: s == pattern
+    else:
+        # unknown prefix, treat as literal
+        pattern = '%s:%s' % (prefix, pattern)
+        return 'literal', pattern, lambda s: s == pattern
+
+
 def tag(repo, subset, x):
     """``tag([name])``
     The specified tag by name, or all tagged revisions if no name is given.
+
+    If `name` starts with `re:`, the remainder of the name is treated as
+    a regular expression. To match a tag that actually starts with `re:`,
+    use the prefix `literal:`.
     """
     # i18n: "tag" is a keyword
     args = getargs(x, 0, 1, _("tag takes one or no 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])
+        pattern = getstring(args[0],
+                            # i18n: "tag" is a keyword
+                            _('the argument to tag must be a string'))
+        kind, pattern, matcher = _stringmatcher(pattern)
+        s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
+        if not s:
+            if kind == 'literal':
+                # i18n: "tag" is a keyword
+                errmsg = _("tag '%s' does not exist") % pattern
+            else:
+                errmsg = _("no tags exist that match '%s'") % pattern
+            raise util.Abort(errmsg)
     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,22 @@
   6
   $ log 'tag(tip)'
   9
+
+we can use patterns when searching for tags
+
+  $ log 'tag("1..*")'
+  abort: tag '1..*' does not exist
+  [255]
+  $ log 'tag("re:1..*")'
+  6
+  $ log 'tag("re:[0-9].[0-9]")'
+  6
+  $ log 'tag("literal:1.0")'
+  6
+  $ log 'tag("re:0..*")'
+  abort: no tags exist that match '0..*'
+  [255]
+
   $ log 'tag(unknown)'
   abort: tag 'unknown' does not exist
   [255]


More information about the Mercurial-devel mailing list