changeset 14098:9f5a0acb0056

revset aliases
author Alexander Solovyov <alexander@solovyov.net>
date Sat, 30 Apr 2011 18:30:14 +0200
parents ca3376f044f8
children 0824a0a3cefc
files mercurial/cmdutil.py mercurial/commands.py mercurial/help/revsets.txt mercurial/revset.py tests/test-revset.t
diffstat 5 files changed, 126 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/cmdutil.py	Sat Apr 30 19:41:53 2011 +0200
+++ b/mercurial/cmdutil.py	Sat Apr 30 18:30:14 2011 +0200
@@ -174,7 +174,7 @@
             pass
 
         # fall through to new-style queries if old-style fails
-        m = revset.match(spec)
+        m = revset.match(repo.ui, spec)
         for r in m(repo, range(len(repo))):
             if r not in seen:
                 l.append(r)
--- a/mercurial/commands.py	Sat Apr 30 19:41:53 2011 +0200
+++ b/mercurial/commands.py	Sat Apr 30 18:30:14 2011 +0200
@@ -1296,7 +1296,10 @@
     if ui.verbose:
         tree = revset.parse(expr)[0]
         ui.note(tree, "\n")
-    func = revset.match(expr)
+        newtree = revset.findaliases(ui, tree)
+        if newtree != tree:
+            ui.note(newtree, "\n")
+    func = revset.match(ui, expr)
     for c in func(repo, range(len(repo))):
         ui.write("%s\n" % c)
 
--- a/mercurial/help/revsets.txt	Sat Apr 30 19:41:53 2011 +0200
+++ b/mercurial/help/revsets.txt	Sat Apr 30 18:30:14 2011 +0200
@@ -61,6 +61,26 @@
 
 .. predicatesmarker
 
+New predicates (known as "aliases") can be defined, using any combination of
+existing predicates or other aliases. An alias definition looks like::
+
+  <alias> = <definition>
+
+in the ``revsetalias`` section of ``.hgrc``. Arguments of the form `$1`, `$2`,
+etc. are substituted from the alias into the definition.
+
+For example,
+
+::
+
+  [revsetalias]
+  h = heads()
+  d($1) = sort($1, date)
+  rs($1, $2) = reverse(sort($1, $2))
+
+defines three aliases, ``h``, ``d``, and ``rs``. ``rs(0:tip, author)`` is
+exactly equivalent to ``reverse(sort(0:tip, author))``.
+
 Command line equivalents for :hg:`log`::
 
   -f    ->  ::.
--- a/mercurial/revset.py	Sat Apr 30 19:41:53 2011 +0200
+++ b/mercurial/revset.py	Sat Apr 30 18:30:14 2011 +0200
@@ -889,14 +889,89 @@
         return w + wa, (op, x[1], ta)
     return 1, x
 
+class revsetalias(object):
+    funcre = re.compile('^([^(]+)\(([^)]+)\)$')
+    args = ()
+
+    def __init__(self, token, value):
+        '''Aliases like:
+
+        h = heads(default)
+        b($1) = ancestors($1) - ancestors(default)
+        '''
+        if isinstance(token, tuple):
+            self.type, self.name = token
+        else:
+            m = self.funcre.search(token)
+            if m:
+                self.type = 'func'
+                self.name = m.group(1)
+                self.args = [x.strip() for x in m.group(2).split(',')]
+            else:
+                self.type = 'symbol'
+                self.name = token
+
+        if isinstance(value, str):
+            for arg in self.args:
+                value = value.replace(arg, repr(arg))
+            self.replacement, pos = parse(value)
+            if pos != len(value):
+                raise error.ParseError('invalid token', pos)
+        else:
+            self.replacement = value
+
+    def match(self, tree):
+        if not tree:
+            return False
+        if tree == (self.type, self.name):
+            return True
+        if tree[0] != self.type:
+            return False
+        if len(tree) > 1 and tree[1] != ('symbol', self.name):
+            return False
+        # 'func' + funcname + args
+        if ((self.args and len(tree) != 3) or
+            (len(self.args) == 1 and tree[2][0] == 'list') or
+            (len(self.args) > 1 and (tree[2][0] != 'list' or
+                                     len(tree[2]) - 1 != len(self.args)))):
+            raise error.ParseError('invalid amount of arguments', len(tree) - 2)
+        return True
+
+    def replace(self, tree):
+        if tree == (self.type, self.name):
+            return self.replacement
+        result = self.replacement
+        def getsubtree(i):
+            if tree[2][0] == 'list':
+                return tree[2][i + 1]
+            return tree[i + 2]
+        for i, v in enumerate(self.args):
+            valalias = revsetalias(('string', v), getsubtree(i))
+            result = valalias.process(result)
+        return result
+
+    def process(self, tree):
+        if self.match(tree):
+            return self.replace(tree)
+        if isinstance(tree, tuple):
+            return tuple(map(self.process, tree))
+        return tree
+
+def findaliases(ui, tree):
+    for k, v in ui.configitems('revsetalias'):
+        alias = revsetalias(k, v)
+        tree = alias.process(tree)
+    return tree
+
 parse = parser.parser(tokenize, elements).parse
 
-def match(spec):
+def match(ui, spec):
     if not spec:
         raise error.ParseError(_("empty query"))
     tree, pos = parse(spec)
     if (pos != len(spec)):
         raise error.ParseError("invalid token", pos)
+    tree = findaliases(ui, tree)
     weight, tree = optimize(tree, True)
     def mfunc(repo, subset):
         return getset(repo, subset, tree)
--- a/tests/test-revset.t	Sat Apr 30 19:41:53 2011 +0200
+++ b/tests/test-revset.t	Sat Apr 30 18:30:14 2011 +0200
@@ -2,7 +2,7 @@
   $ export HGENCODING
 
   $ try() {
-  >   hg debugrevspec --debug $@
+  >   hg debugrevspec --debug "$@"
   > }
 
   $ log() {
@@ -411,3 +411,27 @@
   $ log 'tip^foo'
   hg: parse error: ^ expects a number 0, 1, or 2
   [255]
+
+aliases:
+
+  $ echo '[revsetalias]' >> .hg/hgrc
+  $ echo 'm = merge()' >> .hg/hgrc
+  $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
+  $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
+
+  $ try m
+  ('symbol', 'm')
+  ('func', ('symbol', 'merge'), None)
+  6
+  $ try 'd(2:5)'
+  ('func', ('symbol', 'd'), ('range', ('symbol', '2'), ('symbol', '5')))
+  ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('range', ('symbol', '2'), ('symbol', '5')), ('symbol', 'date'))))
+  4
+  5
+  3
+  2
+  $ try 'rs(2 or 3, date)'
+  ('func', ('symbol', 'rs'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date')))
+  ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date'))))
+  3
+  2