@@ -2840,6 +2840,10 @@
newtree = revset.findaliases(ui, tree)
if newtree != tree:
ui.note(revset.prettyformat(newtree), "\n")
+ tree = newtree
+ newtree = revset.foldconcat(tree)
+ if newtree != tree:
+ ui.note(revset.prettyformat(newtree), "\n")
if opts["optimize"]:
weight, optimizedtree = revset.optimize(newtree, True)
ui.note("* optimized:\n", revset.prettyformat(optimizedtree), "\n")
@@ -81,6 +81,19 @@
defines three aliases, ``h``, ``d``, and ``rs``. ``rs(0:tip, author)`` is
exactly equivalent to ``reverse(sort(0:tip, author))``.
+An infix operator ``##`` can concatenate strings and identifiers into
+one string. For example::
+
+ [revsetalias]
+ issue($1) = grep(r'\bissue[ :]?' ## $1 ## r'\b|\bbug\(' ## $1 ## r'\)')
+
+``issue(1234)`` is equivalent to ``grep(r'\bissue[ :]?1234\b|\bbug\(1234\)')``
+in this case. This matches against all of "issue 1234", "issue:1234",
+"issue1234" and "bug(1234)".
+
+All other prefix, infix and postfix operators have lower priority than
+``##``. For example, ``$1 ## $2~2`` is equivalent to ``($1 ## $2)~2``.
+
Command line equivalents for :hg:`log`::
-f -> ::.
@@ -102,7 +102,8 @@
return baseset(sorted(reachable))
elements = {
- "(": (20, ("group", 1, ")"), ("func", 1, ")")),
+ "(": (21, ("group", 1, ")"), ("func", 1, ")")),
+ "##": (20, None, ("_concat", 20)),
"~": (18, None, ("ancestor", 18)),
"^": (18, None, ("parent", 18), ("parentpost", 18)),
"-": (5, ("negate", 19), ("minus", 5)),
@@ -148,6 +149,9 @@
elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
yield ('..', None, pos)
pos += 1 # skip ahead
+ elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
+ yield ('##', None, pos)
+ pos += 1 # skip ahead
elif c in "():,-|&+!~^": # handle simple operators
yield (c, None, pos)
elif (c in '"\'' or c == 'r' and
@@ -2155,6 +2159,27 @@
alias.warned = True
return tree
+def foldconcat(tree):
+ """Fold elements to be concatenated by `##`
+ """
+ if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
+ return tree
+ if tree[0] == '_concat':
+ pending = [tree]
+ l = []
+ while pending:
+ e = pending.pop()
+ if e[0] == '_concat':
+ pending.extend(reversed(e[1:]))
+ elif e[0] in ('string', 'symbol'):
+ l.append(e[1])
+ else:
+ msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
+ raise error.ParseError(msg)
+ return ('string', ''.join(l))
+ else:
+ return tuple(foldconcat(t) for t in tree)
+
def parse(spec, lookup=None):
p = parser.parser(tokenize, elements)
return p.parse(spec, lookup=lookup)
@@ -2170,6 +2195,7 @@
raise error.ParseError(_("invalid token"), pos)
if ui:
tree = findaliases(ui, tree, showwarning=ui.warn)
+ tree = foldconcat(tree)
weight, tree = optimize(tree, True)
def mfunc(repo, subset):
if util.safehasattr(subset, 'isascending'):
@@ -1123,6 +1123,54 @@
$ cd ../repo
$ log 'remote(".a.b.c.", "../remote3")'
+tests for concatenation of strings/symbols by "##"
+
+ $ try "278 ## '5f5' ## 1ee ## 'ce5'"
+ (_concat
+ (_concat
+ (_concat
+ ('symbol', '278')
+ ('string', '5f5'))
+ ('symbol', '1ee'))
+ ('string', 'ce5'))
+ ('string', '2785f51eece5')
+ 0
+
+ $ echo 'cat4($1, $2, $3, $4) = $1 ## $2 ## $3 ## $4' >> .hg/hgrc
+ $ try "cat4(278, '5f5', 1ee, 'ce5')"
+ (func
+ ('symbol', 'cat4')
+ (list
+ (list
+ (list
+ ('symbol', '278')
+ ('string', '5f5'))
+ ('symbol', '1ee'))
+ ('string', 'ce5')))
+ (_concat
+ (_concat
+ (_concat
+ ('symbol', '278')
+ ('string', '5f5'))
+ ('symbol', '1ee'))
+ ('string', 'ce5'))
+ ('string', '2785f51eece5')
+ 0
+
+(check concatenation in alias nesting)
+
+ $ echo 'cat2($1, $2) = $1 ## $2' >> .hg/hgrc
+ $ echo 'cat2x2($1, $2, $3, $4) = cat2($1 ## $2, $3 ## $4)' >> .hg/hgrc
+ $ log "cat2x2(278, '5f5', 1ee, 'ce5')"
+ 0
+
+(check operator priority)
+
+ $ echo 'cat2n2($1, $2, $3, $4) = $1 ## $2 or $3 ## $4~2' >> .hg/hgrc
+ $ log "cat2n2(2785f5, 1eece5, 24286f, 4ae135)"
+ 0
+ 4
+
$ cd ..
test author/desc/keyword in problematic encoding