[PATCH V2] revset: add "matching" keyword

Angel Ezquerra angel.ezquerra at gmail.com
Tue Apr 3 17:56:23 CDT 2012


# HG changeset patch
# User Angel Ezquerra <angel.ezquerra at gmail.com>
# Date 1333282334 -7200
# Node ID 3e65ae4b56dbc669c90613be40fb5b0f7cbe30e2
# Parent  f350021ee32ed22b8bd7d3211a8899df0e3d6552
revset: add "matching" keyword

This keyword can be used to find revisions that "match" one or more fields of a
given set of revisions.

A revision matches another if all the selected fields (description, author,
files, date, parents and/or substate) match the corresponding values of those
fields on the source revision.

By default this keyword looks for revisions that whose metadata match
(description, author and date) making it ideal to look for duplicate revisions.

matching takes 2 arguments (the second being optional):

1.- rev: a revset represeting a _single_ revision (e.g. tip, ., p1(.), etc)
2.- [field(s) to match]: an optional field or list of fields to match.
  By default matching will match the metadata fields (description, author and
  date). When matching more than one field, they must be input as a list. When
  matching a single field there is no need to surround it in () to make it a
  list.

Examples:

1.- Look for revisions with the same metadata (author, description and date)
as the 11th revision:

hg log -r "matching(11)"

2.- Look for revisions with the same description as the 11th revision:

hg log -r "matching(11, description)"

You do not need to type the whole 'description' word. You could also use
'descript' or 'desc' but not 'd' because 'd' also matches 'date' and 'date'
takes prefecedence because fields are matched in alphabetical order.

3.- Look for revisions with the same author as the current revision:

hg log -r "matching(., author)"

You could use 'user' rather than 'author' to get the same result.

4.- Look for revisions with the same description _AND_ author as the tip of the
repository:

hg log -r "matching(tip, (author, desc))"

5.- Look for revisions touching the same files as the the tip of the repository

hg log -r "matching(tip, files)"

6.- Look for revisions whose subrepos are on the same state as the parent of the
tip of the repository

hg log -r "matching(p1(tip), substate)"

7.- Look for revisions whose author and files both match the tip or the parent
of the tip of the repository:

hg log -r "matching(p1(tip):tip, (a, f))"

diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -858,6 +858,93 @@
         raise error.ParseError(_("rev expects a number"))
     return [r for r in subset if r == l]
 
+def matching(repo, subset, x):
+    """``matching(revision, field)``
+    Changesets in which a given set of fields match the set of fields in the
+    selected revision or set.
+    Valid fields are:
+    description, author, branch, date, files, phase,
+    parents, substate and metadata.
+    metatadata is the default. It is an special field
+    that instructs the function to look for revisions
+    matching the description, the author and the date.
+    """
+    l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
+
+    revs = getset(repo, xrange(len(repo)), l[0])
+
+    fieldlist = ['metadata']
+    if len(l) > 1:
+        try:
+            fieldlist = [getstring(item, '') for item in getlist(l[1])]
+        except error.ParseError:
+            # not a list, perhaps a string
+            fieldlist = [getstring(l[1],
+                _("matching requires a string or "
+                "a list as its second argument"))]
+
+    # the field name does not need to be complete
+    # (e.g. 'd', 'desc' and 'description' all match the decription field)
+    def matchopt(opt, value):
+        return opt.startswith(value)
+    def matchoptlist(optlist, value):
+        for opt in optlist:
+            if matchopt(opt, value):
+                return True
+        return False
+
+    # Make sure that there are no repeated fields, and expand the
+    # 'special' 'metadata' field type
+    fields = []
+    for field in fieldlist:
+        if matchopt('metadata', field):
+            fields += ['user', 'description', 'date']
+        else:
+            fields.append(field)
+    fields = set(fields)
+
+    # We may want to match more than one field
+    # Each field will be matched with its own "getfield" function
+    # which will be added to the getfieldfuncs array of functions
+    getfieldfuncs = []
+    for info in fields:
+        if matchoptlist(('author', 'user'), info):
+            getfield = lambda r: repo[r].user()
+        elif matchopt('branch', info):
+            getfield = lambda r: repo[r].branch()
+        elif matchopt('date', info):
+            getfield = lambda r: repo[r].date()
+        elif matchopt('description', info):
+            getfield = lambda r: repo[r].description()
+        elif matchopt('files', info):
+            getfield = lambda r: repo[r].files()
+        elif matchopt('parents', info):
+            getfield = lambda r: repo[r].parents()
+        elif matchopt('phase', info):
+            getfield = lambda r: repo[r].phase()
+        elif matchopt('substate', info):
+            getfield = lambda r: repo[r].substate
+        else:
+            raise error.ParseError(
+                _("unexpected field name passed to matching: %d") % info)
+        getfieldfuncs.append(getfield)
+
+    # convert the getfield array of functions into a "getinfo" function
+    # which retunrs an array of field values (or a single value if there is
+    # only one field to match)
+    if len(getfieldfuncs) == 1:
+        getinfo = getfieldfuncs[0]
+    else:
+        getinfo = lambda r: [f(r) for f in getfieldfuncs]
+
+    matches = []
+    for rev in revs:
+        target = getinfo(rev)
+        matches += [r for r in subset if getinfo(r) == target]
+    if len(revs) > 1:
+        matches = sorted(set(matches))
+    return matches
+
 def reverse(repo, subset, x):
     """``reverse(set)``
     Reverse order of set.
@@ -1019,6 +1106,7 @@
     "roots": roots,
     "sort": sort,
     "secret": secret,
+    "matching": matching,
     "tag": tag,
     "tagged": tagged,
     "user": user,


More information about the Mercurial-devel mailing list