[PATCH V3] revset: add "matching" keyword

Angel Ezquerra angel.ezquerra at gmail.com
Mon Apr 9 17:58:36 CDT 2012


# HG changeset patch
# User Angel Ezquerra <angel.ezquerra at gmail.com>
# Date 1333282334 -7200
# Node ID 6df9af23933fd08edfe2e1842a0005794df5434e
# Parent  3e673d11cfe0d21368bc76bea4a2e23674f49e30
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.
+    You don't need to type the whole field name. You can use the
+    beginning of the field name, as long as there is no ambiguity.
+    metatadata is the default field which is used if no fields are specified.
+    metatadata is an special field that instructs the function to look
+    for revisions matching the description, the author and the date.
+    You can match more than one field at a time. To do so pass the list of
+    fields separated by spaces (e.g. 'author desc')
+    """
+    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:
+            fieldlist = getstring(l[1],
+                _("matching requires a string "
+                "as its second argument")).split()
+
+    # 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: %s") % 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