diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -174,7 +174,7 @@ def revrange(repo, revs): 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) diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -1296,7 +1296,10 @@ def debugrevspec(ui, repo, expr): 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) diff --git a/mercurial/help/revsets.txt b/mercurial/help/revsets.txt --- a/mercurial/help/revsets.txt +++ b/mercurial/help/revsets.txt @@ -61,6 +61,26 @@ The following predicates are supported: .. predicatesmarker +New predicates (known as "aliases") can be defined, using any combination of +existing predicates or other aliases. An alias definition looks like:: + + = + +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 -> ::. diff --git a/mercurial/revset.py b/mercurial/revset.py --- a/mercurial/revset.py +++ b/mercurial/revset.py @@ -889,14 +889,89 @@ def optimize(x, small): 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) diff --git a/tests/test-revset.t b/tests/test-revset.t --- a/tests/test-revset.t +++ b/tests/test-revset.t @@ -2,7 +2,7 @@ $ export HGENCODING $ try() { - > hg debugrevspec --debug $@ + > hg debugrevspec --debug "$@" > } $ log() { @@ -411,3 +411,27 @@ parentrevspec $ 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