templater: store revisions as ints so min/max won't compare them as strings
Because a template value has no explicit type (like ancient PHP), ifcontains()
has to coerce the type of the needle. Before, it was always converted to a
string, which meant any container type should be a list/dict of strings.
This no longer works since we've introduced min/max functions.
In order to work around the untyped nature of templater, this patch adds
a type specifier to hybrid dict/list. It isn't named as "valuetype" since
the _hybrid class can also wrap a dict.
--- a/mercurial/templatekw.py Mon Oct 09 12:47:22 2017 -0700
+++ b/mercurial/templatekw.py Tue Sep 19 23:13:46 2017 +0900
@@ -37,12 +37,13 @@
- "{files|json}"
"""
- def __init__(self, gen, values, makemap, joinfmt):
+ def __init__(self, gen, values, makemap, joinfmt, keytype=None):
if gen is not None:
self.gen = gen # generator or function returning generator
self._values = values
self._makemap = makemap
self.joinfmt = joinfmt
+ self.keytype = keytype # hint for 'x in y' where type(x) is unresolved
def gen(self):
"""Default generator to stringify this as {join(self, ' ')}"""
for i, x in enumerate(self._values):
@@ -788,15 +789,14 @@
repo = args['repo']
ctx = args['ctx']
pctxs = scmutil.meaningfulparents(repo, ctx)
- # ifcontains() needs a list of str
- prevs = ["%d" % p.rev() for p in pctxs]
+ prevs = [p.rev() for p in pctxs]
parents = [[('rev', p.rev()),
('node', p.hex()),
('phase', p.phasestr())]
for p in pctxs]
f = _showlist('parent', parents, args)
- return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
- lambda x: scmutil.formatchangeid(repo[int(x)]))
+ return _hybrid(f, prevs, lambda x: {'ctx': repo[x], 'revcache': {}},
+ lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
@templatekeyword('phase')
def showphase(repo, ctx, templ, **args):
@@ -818,12 +818,10 @@
be evaluated"""
args = pycompat.byteskwargs(args)
repo = args['ctx'].repo()
- # ifcontains() needs a list of str
- revs = ["%d" % r for r in revs]
- f = _showlist(name, revs, args)
+ f = _showlist(name, ['%d' % r for r in revs], args)
return _hybrid(f, revs,
- lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}},
- pycompat.identity)
+ lambda x: {name: x, 'ctx': repo[x], 'revcache': {}},
+ pycompat.identity, keytype=int)
@templatekeyword('subrepos')
def showsubrepos(**args):
--- a/mercurial/templater.py Mon Oct 09 12:47:22 2017 -0700
+++ b/mercurial/templater.py Tue Sep 19 23:13:46 2017 +0900
@@ -333,12 +333,12 @@
# empty dict/list should be False as they are expected to be ''
return bool(stringify(thing))
-def evalinteger(context, mapping, arg, err):
+def evalinteger(context, mapping, arg, err=None):
v = evalfuncarg(context, mapping, arg)
try:
return int(v)
except (TypeError, ValueError):
- raise error.ParseError(err)
+ raise error.ParseError(err or _('not an integer'))
def evalstring(context, mapping, arg):
return stringify(evalrawexp(context, mapping, arg))
@@ -353,6 +353,20 @@
thing = func(context, mapping, data)
return stringify(thing)
+_evalfuncbytype = {
+ bool: evalboolean,
+ bytes: evalstring,
+ int: evalinteger,
+}
+
+def evalastype(context, mapping, arg, typ):
+ """Evaluate given argument and coerce its type"""
+ try:
+ f = _evalfuncbytype[typ]
+ except KeyError:
+ raise error.ProgrammingError('invalid type specified: %r' % typ)
+ return f(context, mapping, arg)
+
def runinteger(context, mapping, data):
return int(data)
@@ -782,8 +796,9 @@
# i18n: "ifcontains" is a keyword
raise error.ParseError(_("ifcontains expects three or four arguments"))
- needle = evalstring(context, mapping, args[0])
haystack = evalfuncarg(context, mapping, args[1])
+ needle = evalastype(context, mapping, args[0],
+ getattr(haystack, 'keytype', None) or bytes)
if needle in haystack:
yield evalrawexp(context, mapping, args[2])
--- a/tests/test-command-template.t Mon Oct 09 12:47:22 2017 -0700
+++ b/tests/test-command-template.t Tue Sep 19 23:13:46 2017 +0900
@@ -3147,6 +3147,13 @@
$ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), "")}\n'
default
+Test min/max of integers
+
+ $ hg log -R latesttag -l1 -T '{min(revset("9:10"))}\n'
+ 9
+ $ hg log -R latesttag -l1 -T '{max(revset("9:10"))}\n'
+ 10
+
Test dot operator precedence:
$ hg debugtemplate -R latesttag -r0 -v '{manifest.node|short}\n'