##// END OF EJS Templates
templatefuncs: account for user's diffopts in diff() (BC)
Denis Laxalde -
r43321:da3329fe default
parent child Browse files
Show More
@@ -1,790 +1,795
1 # templatefuncs.py - common template functions
1 # templatefuncs.py - common template functions
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13 from .node import (
13 from .node import (
14 bin,
14 bin,
15 wdirid,
15 wdirid,
16 )
16 )
17 from . import (
17 from . import (
18 color,
18 color,
19 diffutil,
19 encoding,
20 encoding,
20 error,
21 error,
21 minirst,
22 minirst,
22 obsutil,
23 obsutil,
23 pycompat,
24 pycompat,
24 registrar,
25 registrar,
25 revset as revsetmod,
26 revset as revsetmod,
26 revsetlang,
27 revsetlang,
27 scmutil,
28 scmutil,
28 templatefilters,
29 templatefilters,
29 templatekw,
30 templatekw,
30 templateutil,
31 templateutil,
31 util,
32 util,
32 )
33 )
33 from .utils import (
34 from .utils import (
34 dateutil,
35 dateutil,
35 stringutil,
36 stringutil,
36 )
37 )
37
38
38 evalrawexp = templateutil.evalrawexp
39 evalrawexp = templateutil.evalrawexp
39 evalwrapped = templateutil.evalwrapped
40 evalwrapped = templateutil.evalwrapped
40 evalfuncarg = templateutil.evalfuncarg
41 evalfuncarg = templateutil.evalfuncarg
41 evalboolean = templateutil.evalboolean
42 evalboolean = templateutil.evalboolean
42 evaldate = templateutil.evaldate
43 evaldate = templateutil.evaldate
43 evalinteger = templateutil.evalinteger
44 evalinteger = templateutil.evalinteger
44 evalstring = templateutil.evalstring
45 evalstring = templateutil.evalstring
45 evalstringliteral = templateutil.evalstringliteral
46 evalstringliteral = templateutil.evalstringliteral
46
47
47 # dict of template built-in functions
48 # dict of template built-in functions
48 funcs = {}
49 funcs = {}
49 templatefunc = registrar.templatefunc(funcs)
50 templatefunc = registrar.templatefunc(funcs)
50
51
51 @templatefunc('date(date[, fmt])')
52 @templatefunc('date(date[, fmt])')
52 def date(context, mapping, args):
53 def date(context, mapping, args):
53 """Format a date. See :hg:`help dates` for formatting
54 """Format a date. See :hg:`help dates` for formatting
54 strings. The default is a Unix date format, including the timezone:
55 strings. The default is a Unix date format, including the timezone:
55 "Mon Sep 04 15:13:13 2006 0700"."""
56 "Mon Sep 04 15:13:13 2006 0700"."""
56 if not (1 <= len(args) <= 2):
57 if not (1 <= len(args) <= 2):
57 # i18n: "date" is a keyword
58 # i18n: "date" is a keyword
58 raise error.ParseError(_("date expects one or two arguments"))
59 raise error.ParseError(_("date expects one or two arguments"))
59
60
60 date = evaldate(context, mapping, args[0],
61 date = evaldate(context, mapping, args[0],
61 # i18n: "date" is a keyword
62 # i18n: "date" is a keyword
62 _("date expects a date information"))
63 _("date expects a date information"))
63 fmt = None
64 fmt = None
64 if len(args) == 2:
65 if len(args) == 2:
65 fmt = evalstring(context, mapping, args[1])
66 fmt = evalstring(context, mapping, args[1])
66 if fmt is None:
67 if fmt is None:
67 return dateutil.datestr(date)
68 return dateutil.datestr(date)
68 else:
69 else:
69 return dateutil.datestr(date, fmt)
70 return dateutil.datestr(date, fmt)
70
71
71 @templatefunc('dict([[key=]value...])', argspec='*args **kwargs')
72 @templatefunc('dict([[key=]value...])', argspec='*args **kwargs')
72 def dict_(context, mapping, args):
73 def dict_(context, mapping, args):
73 """Construct a dict from key-value pairs. A key may be omitted if
74 """Construct a dict from key-value pairs. A key may be omitted if
74 a value expression can provide an unambiguous name."""
75 a value expression can provide an unambiguous name."""
75 data = util.sortdict()
76 data = util.sortdict()
76
77
77 for v in args['args']:
78 for v in args['args']:
78 k = templateutil.findsymbolicname(v)
79 k = templateutil.findsymbolicname(v)
79 if not k:
80 if not k:
80 raise error.ParseError(_('dict key cannot be inferred'))
81 raise error.ParseError(_('dict key cannot be inferred'))
81 if k in data or k in args['kwargs']:
82 if k in data or k in args['kwargs']:
82 raise error.ParseError(_("duplicated dict key '%s' inferred") % k)
83 raise error.ParseError(_("duplicated dict key '%s' inferred") % k)
83 data[k] = evalfuncarg(context, mapping, v)
84 data[k] = evalfuncarg(context, mapping, v)
84
85
85 data.update((k, evalfuncarg(context, mapping, v))
86 data.update((k, evalfuncarg(context, mapping, v))
86 for k, v in args['kwargs'].iteritems())
87 for k, v in args['kwargs'].iteritems())
87 return templateutil.hybriddict(data)
88 return templateutil.hybriddict(data)
88
89
89 @templatefunc('diff([includepattern [, excludepattern]])', requires={'ctx'})
90 @templatefunc('diff([includepattern [, excludepattern]])',
91 requires={'ctx', 'ui'})
90 def diff(context, mapping, args):
92 def diff(context, mapping, args):
91 """Show a diff, optionally
93 """Show a diff, optionally
92 specifying files to include or exclude."""
94 specifying files to include or exclude."""
93 if len(args) > 2:
95 if len(args) > 2:
94 # i18n: "diff" is a keyword
96 # i18n: "diff" is a keyword
95 raise error.ParseError(_("diff expects zero, one, or two arguments"))
97 raise error.ParseError(_("diff expects zero, one, or two arguments"))
96
98
97 def getpatterns(i):
99 def getpatterns(i):
98 if i < len(args):
100 if i < len(args):
99 s = evalstring(context, mapping, args[i]).strip()
101 s = evalstring(context, mapping, args[i]).strip()
100 if s:
102 if s:
101 return [s]
103 return [s]
102 return []
104 return []
103
105
104 ctx = context.resource(mapping, 'ctx')
106 ctx = context.resource(mapping, 'ctx')
105 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
107 ui = context.resource(mapping, 'ui')
108 diffopts = diffutil.diffallopts(ui)
109 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)),
110 opts=diffopts)
106
111
107 return ''.join(chunks)
112 return ''.join(chunks)
108
113
109 @templatefunc('extdata(source)', argspec='source', requires={'ctx', 'cache'})
114 @templatefunc('extdata(source)', argspec='source', requires={'ctx', 'cache'})
110 def extdata(context, mapping, args):
115 def extdata(context, mapping, args):
111 """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
116 """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
112 if 'source' not in args:
117 if 'source' not in args:
113 # i18n: "extdata" is a keyword
118 # i18n: "extdata" is a keyword
114 raise error.ParseError(_('extdata expects one argument'))
119 raise error.ParseError(_('extdata expects one argument'))
115
120
116 source = evalstring(context, mapping, args['source'])
121 source = evalstring(context, mapping, args['source'])
117 if not source:
122 if not source:
118 sym = templateutil.findsymbolicname(args['source'])
123 sym = templateutil.findsymbolicname(args['source'])
119 if sym:
124 if sym:
120 raise error.ParseError(_('empty data source specified'),
125 raise error.ParseError(_('empty data source specified'),
121 hint=_("did you mean extdata('%s')?") % sym)
126 hint=_("did you mean extdata('%s')?") % sym)
122 else:
127 else:
123 raise error.ParseError(_('empty data source specified'))
128 raise error.ParseError(_('empty data source specified'))
124 cache = context.resource(mapping, 'cache').setdefault('extdata', {})
129 cache = context.resource(mapping, 'cache').setdefault('extdata', {})
125 ctx = context.resource(mapping, 'ctx')
130 ctx = context.resource(mapping, 'ctx')
126 if source in cache:
131 if source in cache:
127 data = cache[source]
132 data = cache[source]
128 else:
133 else:
129 data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
134 data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
130 return data.get(ctx.rev(), '')
135 return data.get(ctx.rev(), '')
131
136
132 @templatefunc('files(pattern)', requires={'ctx'})
137 @templatefunc('files(pattern)', requires={'ctx'})
133 def files(context, mapping, args):
138 def files(context, mapping, args):
134 """All files of the current changeset matching the pattern. See
139 """All files of the current changeset matching the pattern. See
135 :hg:`help patterns`."""
140 :hg:`help patterns`."""
136 if not len(args) == 1:
141 if not len(args) == 1:
137 # i18n: "files" is a keyword
142 # i18n: "files" is a keyword
138 raise error.ParseError(_("files expects one argument"))
143 raise error.ParseError(_("files expects one argument"))
139
144
140 raw = evalstring(context, mapping, args[0])
145 raw = evalstring(context, mapping, args[0])
141 ctx = context.resource(mapping, 'ctx')
146 ctx = context.resource(mapping, 'ctx')
142 m = ctx.match([raw])
147 m = ctx.match([raw])
143 files = list(ctx.matches(m))
148 files = list(ctx.matches(m))
144 return templateutil.compatfileslist(context, mapping, "file", files)
149 return templateutil.compatfileslist(context, mapping, "file", files)
145
150
146 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
151 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
147 def fill(context, mapping, args):
152 def fill(context, mapping, args):
148 """Fill many
153 """Fill many
149 paragraphs with optional indentation. See the "fill" filter."""
154 paragraphs with optional indentation. See the "fill" filter."""
150 if not (1 <= len(args) <= 4):
155 if not (1 <= len(args) <= 4):
151 # i18n: "fill" is a keyword
156 # i18n: "fill" is a keyword
152 raise error.ParseError(_("fill expects one to four arguments"))
157 raise error.ParseError(_("fill expects one to four arguments"))
153
158
154 text = evalstring(context, mapping, args[0])
159 text = evalstring(context, mapping, args[0])
155 width = 76
160 width = 76
156 initindent = ''
161 initindent = ''
157 hangindent = ''
162 hangindent = ''
158 if 2 <= len(args) <= 4:
163 if 2 <= len(args) <= 4:
159 width = evalinteger(context, mapping, args[1],
164 width = evalinteger(context, mapping, args[1],
160 # i18n: "fill" is a keyword
165 # i18n: "fill" is a keyword
161 _("fill expects an integer width"))
166 _("fill expects an integer width"))
162 try:
167 try:
163 initindent = evalstring(context, mapping, args[2])
168 initindent = evalstring(context, mapping, args[2])
164 hangindent = evalstring(context, mapping, args[3])
169 hangindent = evalstring(context, mapping, args[3])
165 except IndexError:
170 except IndexError:
166 pass
171 pass
167
172
168 return templatefilters.fill(text, width, initindent, hangindent)
173 return templatefilters.fill(text, width, initindent, hangindent)
169
174
170 @templatefunc('filter(iterable[, expr])')
175 @templatefunc('filter(iterable[, expr])')
171 def filter_(context, mapping, args):
176 def filter_(context, mapping, args):
172 """Remove empty elements from a list or a dict. If expr specified, it's
177 """Remove empty elements from a list or a dict. If expr specified, it's
173 applied to each element to test emptiness."""
178 applied to each element to test emptiness."""
174 if not (1 <= len(args) <= 2):
179 if not (1 <= len(args) <= 2):
175 # i18n: "filter" is a keyword
180 # i18n: "filter" is a keyword
176 raise error.ParseError(_("filter expects one or two arguments"))
181 raise error.ParseError(_("filter expects one or two arguments"))
177 iterable = evalwrapped(context, mapping, args[0])
182 iterable = evalwrapped(context, mapping, args[0])
178 if len(args) == 1:
183 if len(args) == 1:
179 def select(w):
184 def select(w):
180 return w.tobool(context, mapping)
185 return w.tobool(context, mapping)
181 else:
186 else:
182 def select(w):
187 def select(w):
183 if not isinstance(w, templateutil.mappable):
188 if not isinstance(w, templateutil.mappable):
184 raise error.ParseError(_("not filterable by expression"))
189 raise error.ParseError(_("not filterable by expression"))
185 lm = context.overlaymap(mapping, w.tomap(context))
190 lm = context.overlaymap(mapping, w.tomap(context))
186 return evalboolean(context, lm, args[1])
191 return evalboolean(context, lm, args[1])
187 return iterable.filter(context, mapping, select)
192 return iterable.filter(context, mapping, select)
188
193
189 @templatefunc('formatnode(node)', requires={'ui'})
194 @templatefunc('formatnode(node)', requires={'ui'})
190 def formatnode(context, mapping, args):
195 def formatnode(context, mapping, args):
191 """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
196 """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
192 if len(args) != 1:
197 if len(args) != 1:
193 # i18n: "formatnode" is a keyword
198 # i18n: "formatnode" is a keyword
194 raise error.ParseError(_("formatnode expects one argument"))
199 raise error.ParseError(_("formatnode expects one argument"))
195
200
196 ui = context.resource(mapping, 'ui')
201 ui = context.resource(mapping, 'ui')
197 node = evalstring(context, mapping, args[0])
202 node = evalstring(context, mapping, args[0])
198 if ui.debugflag:
203 if ui.debugflag:
199 return node
204 return node
200 return templatefilters.short(node)
205 return templatefilters.short(node)
201
206
202 @templatefunc('mailmap(author)', requires={'repo', 'cache'})
207 @templatefunc('mailmap(author)', requires={'repo', 'cache'})
203 def mailmap(context, mapping, args):
208 def mailmap(context, mapping, args):
204 """Return the author, updated according to the value
209 """Return the author, updated according to the value
205 set in the .mailmap file"""
210 set in the .mailmap file"""
206 if len(args) != 1:
211 if len(args) != 1:
207 raise error.ParseError(_("mailmap expects one argument"))
212 raise error.ParseError(_("mailmap expects one argument"))
208
213
209 author = evalstring(context, mapping, args[0])
214 author = evalstring(context, mapping, args[0])
210
215
211 cache = context.resource(mapping, 'cache')
216 cache = context.resource(mapping, 'cache')
212 repo = context.resource(mapping, 'repo')
217 repo = context.resource(mapping, 'repo')
213
218
214 if 'mailmap' not in cache:
219 if 'mailmap' not in cache:
215 data = repo.wvfs.tryread('.mailmap')
220 data = repo.wvfs.tryread('.mailmap')
216 cache['mailmap'] = stringutil.parsemailmap(data)
221 cache['mailmap'] = stringutil.parsemailmap(data)
217
222
218 return stringutil.mapname(cache['mailmap'], author)
223 return stringutil.mapname(cache['mailmap'], author)
219
224
220 @templatefunc(
225 @templatefunc(
221 'pad(text, width[, fillchar=\' \'[, left=False[, truncate=False]]])',
226 'pad(text, width[, fillchar=\' \'[, left=False[, truncate=False]]])',
222 argspec='text width fillchar left truncate')
227 argspec='text width fillchar left truncate')
223 def pad(context, mapping, args):
228 def pad(context, mapping, args):
224 """Pad text with a
229 """Pad text with a
225 fill character."""
230 fill character."""
226 if 'text' not in args or 'width' not in args:
231 if 'text' not in args or 'width' not in args:
227 # i18n: "pad" is a keyword
232 # i18n: "pad" is a keyword
228 raise error.ParseError(_("pad() expects two to four arguments"))
233 raise error.ParseError(_("pad() expects two to four arguments"))
229
234
230 width = evalinteger(context, mapping, args['width'],
235 width = evalinteger(context, mapping, args['width'],
231 # i18n: "pad" is a keyword
236 # i18n: "pad" is a keyword
232 _("pad() expects an integer width"))
237 _("pad() expects an integer width"))
233
238
234 text = evalstring(context, mapping, args['text'])
239 text = evalstring(context, mapping, args['text'])
235
240
236 truncate = False
241 truncate = False
237 left = False
242 left = False
238 fillchar = ' '
243 fillchar = ' '
239 if 'fillchar' in args:
244 if 'fillchar' in args:
240 fillchar = evalstring(context, mapping, args['fillchar'])
245 fillchar = evalstring(context, mapping, args['fillchar'])
241 if len(color.stripeffects(fillchar)) != 1:
246 if len(color.stripeffects(fillchar)) != 1:
242 # i18n: "pad" is a keyword
247 # i18n: "pad" is a keyword
243 raise error.ParseError(_("pad() expects a single fill character"))
248 raise error.ParseError(_("pad() expects a single fill character"))
244 if 'left' in args:
249 if 'left' in args:
245 left = evalboolean(context, mapping, args['left'])
250 left = evalboolean(context, mapping, args['left'])
246 if 'truncate' in args:
251 if 'truncate' in args:
247 truncate = evalboolean(context, mapping, args['truncate'])
252 truncate = evalboolean(context, mapping, args['truncate'])
248
253
249 fillwidth = width - encoding.colwidth(color.stripeffects(text))
254 fillwidth = width - encoding.colwidth(color.stripeffects(text))
250 if fillwidth < 0 and truncate:
255 if fillwidth < 0 and truncate:
251 return encoding.trim(color.stripeffects(text), width, leftside=left)
256 return encoding.trim(color.stripeffects(text), width, leftside=left)
252 if fillwidth <= 0:
257 if fillwidth <= 0:
253 return text
258 return text
254 if left:
259 if left:
255 return fillchar * fillwidth + text
260 return fillchar * fillwidth + text
256 else:
261 else:
257 return text + fillchar * fillwidth
262 return text + fillchar * fillwidth
258
263
259 @templatefunc('indent(text, indentchars[, firstline])')
264 @templatefunc('indent(text, indentchars[, firstline])')
260 def indent(context, mapping, args):
265 def indent(context, mapping, args):
261 """Indents all non-empty lines
266 """Indents all non-empty lines
262 with the characters given in the indentchars string. An optional
267 with the characters given in the indentchars string. An optional
263 third parameter will override the indent for the first line only
268 third parameter will override the indent for the first line only
264 if present."""
269 if present."""
265 if not (2 <= len(args) <= 3):
270 if not (2 <= len(args) <= 3):
266 # i18n: "indent" is a keyword
271 # i18n: "indent" is a keyword
267 raise error.ParseError(_("indent() expects two or three arguments"))
272 raise error.ParseError(_("indent() expects two or three arguments"))
268
273
269 text = evalstring(context, mapping, args[0])
274 text = evalstring(context, mapping, args[0])
270 indent = evalstring(context, mapping, args[1])
275 indent = evalstring(context, mapping, args[1])
271
276
272 if len(args) == 3:
277 if len(args) == 3:
273 firstline = evalstring(context, mapping, args[2])
278 firstline = evalstring(context, mapping, args[2])
274 else:
279 else:
275 firstline = indent
280 firstline = indent
276
281
277 # the indent function doesn't indent the first line, so we do it here
282 # the indent function doesn't indent the first line, so we do it here
278 return templatefilters.indent(firstline + text, indent)
283 return templatefilters.indent(firstline + text, indent)
279
284
280 @templatefunc('get(dict, key)')
285 @templatefunc('get(dict, key)')
281 def get(context, mapping, args):
286 def get(context, mapping, args):
282 """Get an attribute/key from an object. Some keywords
287 """Get an attribute/key from an object. Some keywords
283 are complex types. This function allows you to obtain the value of an
288 are complex types. This function allows you to obtain the value of an
284 attribute on these types."""
289 attribute on these types."""
285 if len(args) != 2:
290 if len(args) != 2:
286 # i18n: "get" is a keyword
291 # i18n: "get" is a keyword
287 raise error.ParseError(_("get() expects two arguments"))
292 raise error.ParseError(_("get() expects two arguments"))
288
293
289 dictarg = evalwrapped(context, mapping, args[0])
294 dictarg = evalwrapped(context, mapping, args[0])
290 key = evalrawexp(context, mapping, args[1])
295 key = evalrawexp(context, mapping, args[1])
291 try:
296 try:
292 return dictarg.getmember(context, mapping, key)
297 return dictarg.getmember(context, mapping, key)
293 except error.ParseError as err:
298 except error.ParseError as err:
294 # i18n: "get" is a keyword
299 # i18n: "get" is a keyword
295 hint = _("get() expects a dict as first argument")
300 hint = _("get() expects a dict as first argument")
296 raise error.ParseError(bytes(err), hint=hint)
301 raise error.ParseError(bytes(err), hint=hint)
297
302
298 @templatefunc('config(section, name[, default])', requires={'ui'})
303 @templatefunc('config(section, name[, default])', requires={'ui'})
299 def config(context, mapping, args):
304 def config(context, mapping, args):
300 """Returns the requested hgrc config option as a string."""
305 """Returns the requested hgrc config option as a string."""
301 fn = context.resource(mapping, 'ui').config
306 fn = context.resource(mapping, 'ui').config
302 return _config(context, mapping, args, fn, evalstring)
307 return _config(context, mapping, args, fn, evalstring)
303
308
304 @templatefunc('configbool(section, name[, default])', requires={'ui'})
309 @templatefunc('configbool(section, name[, default])', requires={'ui'})
305 def configbool(context, mapping, args):
310 def configbool(context, mapping, args):
306 """Returns the requested hgrc config option as a boolean."""
311 """Returns the requested hgrc config option as a boolean."""
307 fn = context.resource(mapping, 'ui').configbool
312 fn = context.resource(mapping, 'ui').configbool
308 return _config(context, mapping, args, fn, evalboolean)
313 return _config(context, mapping, args, fn, evalboolean)
309
314
310 @templatefunc('configint(section, name[, default])', requires={'ui'})
315 @templatefunc('configint(section, name[, default])', requires={'ui'})
311 def configint(context, mapping, args):
316 def configint(context, mapping, args):
312 """Returns the requested hgrc config option as an integer."""
317 """Returns the requested hgrc config option as an integer."""
313 fn = context.resource(mapping, 'ui').configint
318 fn = context.resource(mapping, 'ui').configint
314 return _config(context, mapping, args, fn, evalinteger)
319 return _config(context, mapping, args, fn, evalinteger)
315
320
316 def _config(context, mapping, args, configfn, defaultfn):
321 def _config(context, mapping, args, configfn, defaultfn):
317 if not (2 <= len(args) <= 3):
322 if not (2 <= len(args) <= 3):
318 raise error.ParseError(_("config expects two or three arguments"))
323 raise error.ParseError(_("config expects two or three arguments"))
319
324
320 # The config option can come from any section, though we specifically
325 # The config option can come from any section, though we specifically
321 # reserve the [templateconfig] section for dynamically defining options
326 # reserve the [templateconfig] section for dynamically defining options
322 # for this function without also requiring an extension.
327 # for this function without also requiring an extension.
323 section = evalstringliteral(context, mapping, args[0])
328 section = evalstringliteral(context, mapping, args[0])
324 name = evalstringliteral(context, mapping, args[1])
329 name = evalstringliteral(context, mapping, args[1])
325 if len(args) == 3:
330 if len(args) == 3:
326 default = defaultfn(context, mapping, args[2])
331 default = defaultfn(context, mapping, args[2])
327 return configfn(section, name, default)
332 return configfn(section, name, default)
328 else:
333 else:
329 return configfn(section, name)
334 return configfn(section, name)
330
335
331 @templatefunc('if(expr, then[, else])')
336 @templatefunc('if(expr, then[, else])')
332 def if_(context, mapping, args):
337 def if_(context, mapping, args):
333 """Conditionally execute based on the result of
338 """Conditionally execute based on the result of
334 an expression."""
339 an expression."""
335 if not (2 <= len(args) <= 3):
340 if not (2 <= len(args) <= 3):
336 # i18n: "if" is a keyword
341 # i18n: "if" is a keyword
337 raise error.ParseError(_("if expects two or three arguments"))
342 raise error.ParseError(_("if expects two or three arguments"))
338
343
339 test = evalboolean(context, mapping, args[0])
344 test = evalboolean(context, mapping, args[0])
340 if test:
345 if test:
341 return evalrawexp(context, mapping, args[1])
346 return evalrawexp(context, mapping, args[1])
342 elif len(args) == 3:
347 elif len(args) == 3:
343 return evalrawexp(context, mapping, args[2])
348 return evalrawexp(context, mapping, args[2])
344
349
345 @templatefunc('ifcontains(needle, haystack, then[, else])')
350 @templatefunc('ifcontains(needle, haystack, then[, else])')
346 def ifcontains(context, mapping, args):
351 def ifcontains(context, mapping, args):
347 """Conditionally execute based
352 """Conditionally execute based
348 on whether the item "needle" is in "haystack"."""
353 on whether the item "needle" is in "haystack"."""
349 if not (3 <= len(args) <= 4):
354 if not (3 <= len(args) <= 4):
350 # i18n: "ifcontains" is a keyword
355 # i18n: "ifcontains" is a keyword
351 raise error.ParseError(_("ifcontains expects three or four arguments"))
356 raise error.ParseError(_("ifcontains expects three or four arguments"))
352
357
353 haystack = evalwrapped(context, mapping, args[1])
358 haystack = evalwrapped(context, mapping, args[1])
354 try:
359 try:
355 needle = evalrawexp(context, mapping, args[0])
360 needle = evalrawexp(context, mapping, args[0])
356 found = haystack.contains(context, mapping, needle)
361 found = haystack.contains(context, mapping, needle)
357 except error.ParseError:
362 except error.ParseError:
358 found = False
363 found = False
359
364
360 if found:
365 if found:
361 return evalrawexp(context, mapping, args[2])
366 return evalrawexp(context, mapping, args[2])
362 elif len(args) == 4:
367 elif len(args) == 4:
363 return evalrawexp(context, mapping, args[3])
368 return evalrawexp(context, mapping, args[3])
364
369
365 @templatefunc('ifeq(expr1, expr2, then[, else])')
370 @templatefunc('ifeq(expr1, expr2, then[, else])')
366 def ifeq(context, mapping, args):
371 def ifeq(context, mapping, args):
367 """Conditionally execute based on
372 """Conditionally execute based on
368 whether 2 items are equivalent."""
373 whether 2 items are equivalent."""
369 if not (3 <= len(args) <= 4):
374 if not (3 <= len(args) <= 4):
370 # i18n: "ifeq" is a keyword
375 # i18n: "ifeq" is a keyword
371 raise error.ParseError(_("ifeq expects three or four arguments"))
376 raise error.ParseError(_("ifeq expects three or four arguments"))
372
377
373 test = evalstring(context, mapping, args[0])
378 test = evalstring(context, mapping, args[0])
374 match = evalstring(context, mapping, args[1])
379 match = evalstring(context, mapping, args[1])
375 if test == match:
380 if test == match:
376 return evalrawexp(context, mapping, args[2])
381 return evalrawexp(context, mapping, args[2])
377 elif len(args) == 4:
382 elif len(args) == 4:
378 return evalrawexp(context, mapping, args[3])
383 return evalrawexp(context, mapping, args[3])
379
384
380 @templatefunc('join(list, sep)')
385 @templatefunc('join(list, sep)')
381 def join(context, mapping, args):
386 def join(context, mapping, args):
382 """Join items in a list with a delimiter."""
387 """Join items in a list with a delimiter."""
383 if not (1 <= len(args) <= 2):
388 if not (1 <= len(args) <= 2):
384 # i18n: "join" is a keyword
389 # i18n: "join" is a keyword
385 raise error.ParseError(_("join expects one or two arguments"))
390 raise error.ParseError(_("join expects one or two arguments"))
386
391
387 joinset = evalwrapped(context, mapping, args[0])
392 joinset = evalwrapped(context, mapping, args[0])
388 joiner = " "
393 joiner = " "
389 if len(args) > 1:
394 if len(args) > 1:
390 joiner = evalstring(context, mapping, args[1])
395 joiner = evalstring(context, mapping, args[1])
391 return joinset.join(context, mapping, joiner)
396 return joinset.join(context, mapping, joiner)
392
397
393 @templatefunc('label(label, expr)', requires={'ui'})
398 @templatefunc('label(label, expr)', requires={'ui'})
394 def label(context, mapping, args):
399 def label(context, mapping, args):
395 """Apply a label to generated content. Content with
400 """Apply a label to generated content. Content with
396 a label applied can result in additional post-processing, such as
401 a label applied can result in additional post-processing, such as
397 automatic colorization."""
402 automatic colorization."""
398 if len(args) != 2:
403 if len(args) != 2:
399 # i18n: "label" is a keyword
404 # i18n: "label" is a keyword
400 raise error.ParseError(_("label expects two arguments"))
405 raise error.ParseError(_("label expects two arguments"))
401
406
402 ui = context.resource(mapping, 'ui')
407 ui = context.resource(mapping, 'ui')
403 thing = evalstring(context, mapping, args[1])
408 thing = evalstring(context, mapping, args[1])
404 # preserve unknown symbol as literal so effects like 'red', 'bold',
409 # preserve unknown symbol as literal so effects like 'red', 'bold',
405 # etc. don't need to be quoted
410 # etc. don't need to be quoted
406 label = evalstringliteral(context, mapping, args[0])
411 label = evalstringliteral(context, mapping, args[0])
407
412
408 return ui.label(thing, label)
413 return ui.label(thing, label)
409
414
410 @templatefunc('latesttag([pattern])')
415 @templatefunc('latesttag([pattern])')
411 def latesttag(context, mapping, args):
416 def latesttag(context, mapping, args):
412 """The global tags matching the given pattern on the
417 """The global tags matching the given pattern on the
413 most recent globally tagged ancestor of this changeset.
418 most recent globally tagged ancestor of this changeset.
414 If no such tags exist, the "{tag}" template resolves to
419 If no such tags exist, the "{tag}" template resolves to
415 the string "null". See :hg:`help revisions.patterns` for the pattern
420 the string "null". See :hg:`help revisions.patterns` for the pattern
416 syntax.
421 syntax.
417 """
422 """
418 if len(args) > 1:
423 if len(args) > 1:
419 # i18n: "latesttag" is a keyword
424 # i18n: "latesttag" is a keyword
420 raise error.ParseError(_("latesttag expects at most one argument"))
425 raise error.ParseError(_("latesttag expects at most one argument"))
421
426
422 pattern = None
427 pattern = None
423 if len(args) == 1:
428 if len(args) == 1:
424 pattern = evalstring(context, mapping, args[0])
429 pattern = evalstring(context, mapping, args[0])
425 return templatekw.showlatesttags(context, mapping, pattern)
430 return templatekw.showlatesttags(context, mapping, pattern)
426
431
427 @templatefunc('localdate(date[, tz])')
432 @templatefunc('localdate(date[, tz])')
428 def localdate(context, mapping, args):
433 def localdate(context, mapping, args):
429 """Converts a date to the specified timezone.
434 """Converts a date to the specified timezone.
430 The default is local date."""
435 The default is local date."""
431 if not (1 <= len(args) <= 2):
436 if not (1 <= len(args) <= 2):
432 # i18n: "localdate" is a keyword
437 # i18n: "localdate" is a keyword
433 raise error.ParseError(_("localdate expects one or two arguments"))
438 raise error.ParseError(_("localdate expects one or two arguments"))
434
439
435 date = evaldate(context, mapping, args[0],
440 date = evaldate(context, mapping, args[0],
436 # i18n: "localdate" is a keyword
441 # i18n: "localdate" is a keyword
437 _("localdate expects a date information"))
442 _("localdate expects a date information"))
438 if len(args) >= 2:
443 if len(args) >= 2:
439 tzoffset = None
444 tzoffset = None
440 tz = evalfuncarg(context, mapping, args[1])
445 tz = evalfuncarg(context, mapping, args[1])
441 if isinstance(tz, bytes):
446 if isinstance(tz, bytes):
442 tzoffset, remainder = dateutil.parsetimezone(tz)
447 tzoffset, remainder = dateutil.parsetimezone(tz)
443 if remainder:
448 if remainder:
444 tzoffset = None
449 tzoffset = None
445 if tzoffset is None:
450 if tzoffset is None:
446 try:
451 try:
447 tzoffset = int(tz)
452 tzoffset = int(tz)
448 except (TypeError, ValueError):
453 except (TypeError, ValueError):
449 # i18n: "localdate" is a keyword
454 # i18n: "localdate" is a keyword
450 raise error.ParseError(_("localdate expects a timezone"))
455 raise error.ParseError(_("localdate expects a timezone"))
451 else:
456 else:
452 tzoffset = dateutil.makedate()[1]
457 tzoffset = dateutil.makedate()[1]
453 return templateutil.date((date[0], tzoffset))
458 return templateutil.date((date[0], tzoffset))
454
459
455 @templatefunc('max(iterable)')
460 @templatefunc('max(iterable)')
456 def max_(context, mapping, args, **kwargs):
461 def max_(context, mapping, args, **kwargs):
457 """Return the max of an iterable"""
462 """Return the max of an iterable"""
458 if len(args) != 1:
463 if len(args) != 1:
459 # i18n: "max" is a keyword
464 # i18n: "max" is a keyword
460 raise error.ParseError(_("max expects one argument"))
465 raise error.ParseError(_("max expects one argument"))
461
466
462 iterable = evalwrapped(context, mapping, args[0])
467 iterable = evalwrapped(context, mapping, args[0])
463 try:
468 try:
464 return iterable.getmax(context, mapping)
469 return iterable.getmax(context, mapping)
465 except error.ParseError as err:
470 except error.ParseError as err:
466 # i18n: "max" is a keyword
471 # i18n: "max" is a keyword
467 hint = _("max first argument should be an iterable")
472 hint = _("max first argument should be an iterable")
468 raise error.ParseError(bytes(err), hint=hint)
473 raise error.ParseError(bytes(err), hint=hint)
469
474
470 @templatefunc('min(iterable)')
475 @templatefunc('min(iterable)')
471 def min_(context, mapping, args, **kwargs):
476 def min_(context, mapping, args, **kwargs):
472 """Return the min of an iterable"""
477 """Return the min of an iterable"""
473 if len(args) != 1:
478 if len(args) != 1:
474 # i18n: "min" is a keyword
479 # i18n: "min" is a keyword
475 raise error.ParseError(_("min expects one argument"))
480 raise error.ParseError(_("min expects one argument"))
476
481
477 iterable = evalwrapped(context, mapping, args[0])
482 iterable = evalwrapped(context, mapping, args[0])
478 try:
483 try:
479 return iterable.getmin(context, mapping)
484 return iterable.getmin(context, mapping)
480 except error.ParseError as err:
485 except error.ParseError as err:
481 # i18n: "min" is a keyword
486 # i18n: "min" is a keyword
482 hint = _("min first argument should be an iterable")
487 hint = _("min first argument should be an iterable")
483 raise error.ParseError(bytes(err), hint=hint)
488 raise error.ParseError(bytes(err), hint=hint)
484
489
485 @templatefunc('mod(a, b)')
490 @templatefunc('mod(a, b)')
486 def mod(context, mapping, args):
491 def mod(context, mapping, args):
487 """Calculate a mod b such that a / b + a mod b == a"""
492 """Calculate a mod b such that a / b + a mod b == a"""
488 if not len(args) == 2:
493 if not len(args) == 2:
489 # i18n: "mod" is a keyword
494 # i18n: "mod" is a keyword
490 raise error.ParseError(_("mod expects two arguments"))
495 raise error.ParseError(_("mod expects two arguments"))
491
496
492 func = lambda a, b: a % b
497 func = lambda a, b: a % b
493 return templateutil.runarithmetic(context, mapping,
498 return templateutil.runarithmetic(context, mapping,
494 (func, args[0], args[1]))
499 (func, args[0], args[1]))
495
500
496 @templatefunc('obsfateoperations(markers)')
501 @templatefunc('obsfateoperations(markers)')
497 def obsfateoperations(context, mapping, args):
502 def obsfateoperations(context, mapping, args):
498 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
503 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
499 if len(args) != 1:
504 if len(args) != 1:
500 # i18n: "obsfateoperations" is a keyword
505 # i18n: "obsfateoperations" is a keyword
501 raise error.ParseError(_("obsfateoperations expects one argument"))
506 raise error.ParseError(_("obsfateoperations expects one argument"))
502
507
503 markers = evalfuncarg(context, mapping, args[0])
508 markers = evalfuncarg(context, mapping, args[0])
504
509
505 try:
510 try:
506 data = obsutil.markersoperations(markers)
511 data = obsutil.markersoperations(markers)
507 return templateutil.hybridlist(data, name='operation')
512 return templateutil.hybridlist(data, name='operation')
508 except (TypeError, KeyError):
513 except (TypeError, KeyError):
509 # i18n: "obsfateoperations" is a keyword
514 # i18n: "obsfateoperations" is a keyword
510 errmsg = _("obsfateoperations first argument should be an iterable")
515 errmsg = _("obsfateoperations first argument should be an iterable")
511 raise error.ParseError(errmsg)
516 raise error.ParseError(errmsg)
512
517
513 @templatefunc('obsfatedate(markers)')
518 @templatefunc('obsfatedate(markers)')
514 def obsfatedate(context, mapping, args):
519 def obsfatedate(context, mapping, args):
515 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
520 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
516 if len(args) != 1:
521 if len(args) != 1:
517 # i18n: "obsfatedate" is a keyword
522 # i18n: "obsfatedate" is a keyword
518 raise error.ParseError(_("obsfatedate expects one argument"))
523 raise error.ParseError(_("obsfatedate expects one argument"))
519
524
520 markers = evalfuncarg(context, mapping, args[0])
525 markers = evalfuncarg(context, mapping, args[0])
521
526
522 try:
527 try:
523 # TODO: maybe this has to be a wrapped list of date wrappers?
528 # TODO: maybe this has to be a wrapped list of date wrappers?
524 data = obsutil.markersdates(markers)
529 data = obsutil.markersdates(markers)
525 return templateutil.hybridlist(data, name='date', fmt='%d %d')
530 return templateutil.hybridlist(data, name='date', fmt='%d %d')
526 except (TypeError, KeyError):
531 except (TypeError, KeyError):
527 # i18n: "obsfatedate" is a keyword
532 # i18n: "obsfatedate" is a keyword
528 errmsg = _("obsfatedate first argument should be an iterable")
533 errmsg = _("obsfatedate first argument should be an iterable")
529 raise error.ParseError(errmsg)
534 raise error.ParseError(errmsg)
530
535
531 @templatefunc('obsfateusers(markers)')
536 @templatefunc('obsfateusers(markers)')
532 def obsfateusers(context, mapping, args):
537 def obsfateusers(context, mapping, args):
533 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
538 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
534 if len(args) != 1:
539 if len(args) != 1:
535 # i18n: "obsfateusers" is a keyword
540 # i18n: "obsfateusers" is a keyword
536 raise error.ParseError(_("obsfateusers expects one argument"))
541 raise error.ParseError(_("obsfateusers expects one argument"))
537
542
538 markers = evalfuncarg(context, mapping, args[0])
543 markers = evalfuncarg(context, mapping, args[0])
539
544
540 try:
545 try:
541 data = obsutil.markersusers(markers)
546 data = obsutil.markersusers(markers)
542 return templateutil.hybridlist(data, name='user')
547 return templateutil.hybridlist(data, name='user')
543 except (TypeError, KeyError, ValueError):
548 except (TypeError, KeyError, ValueError):
544 # i18n: "obsfateusers" is a keyword
549 # i18n: "obsfateusers" is a keyword
545 msg = _("obsfateusers first argument should be an iterable of "
550 msg = _("obsfateusers first argument should be an iterable of "
546 "obsmakers")
551 "obsmakers")
547 raise error.ParseError(msg)
552 raise error.ParseError(msg)
548
553
549 @templatefunc('obsfateverb(successors, markers)')
554 @templatefunc('obsfateverb(successors, markers)')
550 def obsfateverb(context, mapping, args):
555 def obsfateverb(context, mapping, args):
551 """Compute obsfate related information based on successors (EXPERIMENTAL)"""
556 """Compute obsfate related information based on successors (EXPERIMENTAL)"""
552 if len(args) != 2:
557 if len(args) != 2:
553 # i18n: "obsfateverb" is a keyword
558 # i18n: "obsfateverb" is a keyword
554 raise error.ParseError(_("obsfateverb expects two arguments"))
559 raise error.ParseError(_("obsfateverb expects two arguments"))
555
560
556 successors = evalfuncarg(context, mapping, args[0])
561 successors = evalfuncarg(context, mapping, args[0])
557 markers = evalfuncarg(context, mapping, args[1])
562 markers = evalfuncarg(context, mapping, args[1])
558
563
559 try:
564 try:
560 return obsutil.obsfateverb(successors, markers)
565 return obsutil.obsfateverb(successors, markers)
561 except TypeError:
566 except TypeError:
562 # i18n: "obsfateverb" is a keyword
567 # i18n: "obsfateverb" is a keyword
563 errmsg = _("obsfateverb first argument should be countable")
568 errmsg = _("obsfateverb first argument should be countable")
564 raise error.ParseError(errmsg)
569 raise error.ParseError(errmsg)
565
570
566 @templatefunc('relpath(path)', requires={'repo'})
571 @templatefunc('relpath(path)', requires={'repo'})
567 def relpath(context, mapping, args):
572 def relpath(context, mapping, args):
568 """Convert a repository-absolute path into a filesystem path relative to
573 """Convert a repository-absolute path into a filesystem path relative to
569 the current working directory."""
574 the current working directory."""
570 if len(args) != 1:
575 if len(args) != 1:
571 # i18n: "relpath" is a keyword
576 # i18n: "relpath" is a keyword
572 raise error.ParseError(_("relpath expects one argument"))
577 raise error.ParseError(_("relpath expects one argument"))
573
578
574 repo = context.resource(mapping, 'repo')
579 repo = context.resource(mapping, 'repo')
575 path = evalstring(context, mapping, args[0])
580 path = evalstring(context, mapping, args[0])
576 return repo.pathto(path)
581 return repo.pathto(path)
577
582
578 @templatefunc('revset(query[, formatargs...])', requires={'repo', 'cache'})
583 @templatefunc('revset(query[, formatargs...])', requires={'repo', 'cache'})
579 def revset(context, mapping, args):
584 def revset(context, mapping, args):
580 """Execute a revision set query. See
585 """Execute a revision set query. See
581 :hg:`help revset`."""
586 :hg:`help revset`."""
582 if not len(args) > 0:
587 if not len(args) > 0:
583 # i18n: "revset" is a keyword
588 # i18n: "revset" is a keyword
584 raise error.ParseError(_("revset expects one or more arguments"))
589 raise error.ParseError(_("revset expects one or more arguments"))
585
590
586 raw = evalstring(context, mapping, args[0])
591 raw = evalstring(context, mapping, args[0])
587 repo = context.resource(mapping, 'repo')
592 repo = context.resource(mapping, 'repo')
588
593
589 def query(expr):
594 def query(expr):
590 m = revsetmod.match(repo.ui, expr, lookup=revsetmod.lookupfn(repo))
595 m = revsetmod.match(repo.ui, expr, lookup=revsetmod.lookupfn(repo))
591 return m(repo)
596 return m(repo)
592
597
593 if len(args) > 1:
598 if len(args) > 1:
594 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
599 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
595 revs = query(revsetlang.formatspec(raw, *formatargs))
600 revs = query(revsetlang.formatspec(raw, *formatargs))
596 else:
601 else:
597 cache = context.resource(mapping, 'cache')
602 cache = context.resource(mapping, 'cache')
598 revsetcache = cache.setdefault("revsetcache", {})
603 revsetcache = cache.setdefault("revsetcache", {})
599 if raw in revsetcache:
604 if raw in revsetcache:
600 revs = revsetcache[raw]
605 revs = revsetcache[raw]
601 else:
606 else:
602 revs = query(raw)
607 revs = query(raw)
603 revsetcache[raw] = revs
608 revsetcache[raw] = revs
604 return templatekw.showrevslist(context, mapping, "revision", revs)
609 return templatekw.showrevslist(context, mapping, "revision", revs)
605
610
606 @templatefunc('rstdoc(text, style)')
611 @templatefunc('rstdoc(text, style)')
607 def rstdoc(context, mapping, args):
612 def rstdoc(context, mapping, args):
608 """Format reStructuredText."""
613 """Format reStructuredText."""
609 if len(args) != 2:
614 if len(args) != 2:
610 # i18n: "rstdoc" is a keyword
615 # i18n: "rstdoc" is a keyword
611 raise error.ParseError(_("rstdoc expects two arguments"))
616 raise error.ParseError(_("rstdoc expects two arguments"))
612
617
613 text = evalstring(context, mapping, args[0])
618 text = evalstring(context, mapping, args[0])
614 style = evalstring(context, mapping, args[1])
619 style = evalstring(context, mapping, args[1])
615
620
616 return minirst.format(text, style=style, keep=['verbose'])
621 return minirst.format(text, style=style, keep=['verbose'])
617
622
618 @templatefunc('search(pattern, text)')
623 @templatefunc('search(pattern, text)')
619 def search(context, mapping, args):
624 def search(context, mapping, args):
620 """Look for the first text matching the regular expression pattern.
625 """Look for the first text matching the regular expression pattern.
621 Groups are accessible as ``{1}``, ``{2}``, ... in %-mapped template."""
626 Groups are accessible as ``{1}``, ``{2}``, ... in %-mapped template."""
622 if len(args) != 2:
627 if len(args) != 2:
623 # i18n: "search" is a keyword
628 # i18n: "search" is a keyword
624 raise error.ParseError(_(b'search expects two arguments'))
629 raise error.ParseError(_(b'search expects two arguments'))
625
630
626 pat = evalstring(context, mapping, args[0])
631 pat = evalstring(context, mapping, args[0])
627 src = evalstring(context, mapping, args[1])
632 src = evalstring(context, mapping, args[1])
628 try:
633 try:
629 patre = re.compile(pat)
634 patre = re.compile(pat)
630 except re.error:
635 except re.error:
631 # i18n: "search" is a keyword
636 # i18n: "search" is a keyword
632 raise error.ParseError(_(b'search got an invalid pattern: %s') % pat)
637 raise error.ParseError(_(b'search got an invalid pattern: %s') % pat)
633 # named groups shouldn't shadow *reserved* resource keywords
638 # named groups shouldn't shadow *reserved* resource keywords
634 badgroups = (context.knownresourcekeys()
639 badgroups = (context.knownresourcekeys()
635 & set(pycompat.byteskwargs(patre.groupindex)))
640 & set(pycompat.byteskwargs(patre.groupindex)))
636 if badgroups:
641 if badgroups:
637 raise error.ParseError(
642 raise error.ParseError(
638 # i18n: "search" is a keyword
643 # i18n: "search" is a keyword
639 _(b'invalid group %(group)s in search pattern: %(pat)s')
644 _(b'invalid group %(group)s in search pattern: %(pat)s')
640 % {b'group': b', '.join("'%s'" % g for g in sorted(badgroups)),
645 % {b'group': b', '.join("'%s'" % g for g in sorted(badgroups)),
641 b'pat': pat})
646 b'pat': pat})
642
647
643 match = patre.search(src)
648 match = patre.search(src)
644 if not match:
649 if not match:
645 return templateutil.mappingnone()
650 return templateutil.mappingnone()
646
651
647 lm = {b'0': match.group(0)}
652 lm = {b'0': match.group(0)}
648 lm.update((b'%d' % i, v) for i, v in enumerate(match.groups(), 1))
653 lm.update((b'%d' % i, v) for i, v in enumerate(match.groups(), 1))
649 lm.update(pycompat.byteskwargs(match.groupdict()))
654 lm.update(pycompat.byteskwargs(match.groupdict()))
650 return templateutil.mappingdict(lm, tmpl=b'{0}')
655 return templateutil.mappingdict(lm, tmpl=b'{0}')
651
656
652 @templatefunc('separate(sep, args...)', argspec='sep *args')
657 @templatefunc('separate(sep, args...)', argspec='sep *args')
653 def separate(context, mapping, args):
658 def separate(context, mapping, args):
654 """Add a separator between non-empty arguments."""
659 """Add a separator between non-empty arguments."""
655 if 'sep' not in args:
660 if 'sep' not in args:
656 # i18n: "separate" is a keyword
661 # i18n: "separate" is a keyword
657 raise error.ParseError(_("separate expects at least one argument"))
662 raise error.ParseError(_("separate expects at least one argument"))
658
663
659 sep = evalstring(context, mapping, args['sep'])
664 sep = evalstring(context, mapping, args['sep'])
660 first = True
665 first = True
661 for arg in args['args']:
666 for arg in args['args']:
662 argstr = evalstring(context, mapping, arg)
667 argstr = evalstring(context, mapping, arg)
663 if not argstr:
668 if not argstr:
664 continue
669 continue
665 if first:
670 if first:
666 first = False
671 first = False
667 else:
672 else:
668 yield sep
673 yield sep
669 yield argstr
674 yield argstr
670
675
671 @templatefunc('shortest(node, minlength=4)', requires={'repo', 'cache'})
676 @templatefunc('shortest(node, minlength=4)', requires={'repo', 'cache'})
672 def shortest(context, mapping, args):
677 def shortest(context, mapping, args):
673 """Obtain the shortest representation of
678 """Obtain the shortest representation of
674 a node."""
679 a node."""
675 if not (1 <= len(args) <= 2):
680 if not (1 <= len(args) <= 2):
676 # i18n: "shortest" is a keyword
681 # i18n: "shortest" is a keyword
677 raise error.ParseError(_("shortest() expects one or two arguments"))
682 raise error.ParseError(_("shortest() expects one or two arguments"))
678
683
679 hexnode = evalstring(context, mapping, args[0])
684 hexnode = evalstring(context, mapping, args[0])
680
685
681 minlength = 4
686 minlength = 4
682 if len(args) > 1:
687 if len(args) > 1:
683 minlength = evalinteger(context, mapping, args[1],
688 minlength = evalinteger(context, mapping, args[1],
684 # i18n: "shortest" is a keyword
689 # i18n: "shortest" is a keyword
685 _("shortest() expects an integer minlength"))
690 _("shortest() expects an integer minlength"))
686
691
687 repo = context.resource(mapping, 'repo')
692 repo = context.resource(mapping, 'repo')
688 if len(hexnode) > 40:
693 if len(hexnode) > 40:
689 return hexnode
694 return hexnode
690 elif len(hexnode) == 40:
695 elif len(hexnode) == 40:
691 try:
696 try:
692 node = bin(hexnode)
697 node = bin(hexnode)
693 except TypeError:
698 except TypeError:
694 return hexnode
699 return hexnode
695 else:
700 else:
696 try:
701 try:
697 node = scmutil.resolvehexnodeidprefix(repo, hexnode)
702 node = scmutil.resolvehexnodeidprefix(repo, hexnode)
698 except error.WdirUnsupported:
703 except error.WdirUnsupported:
699 node = wdirid
704 node = wdirid
700 except error.LookupError:
705 except error.LookupError:
701 return hexnode
706 return hexnode
702 if not node:
707 if not node:
703 return hexnode
708 return hexnode
704 cache = context.resource(mapping, 'cache')
709 cache = context.resource(mapping, 'cache')
705 try:
710 try:
706 return scmutil.shortesthexnodeidprefix(repo, node, minlength, cache)
711 return scmutil.shortesthexnodeidprefix(repo, node, minlength, cache)
707 except error.RepoLookupError:
712 except error.RepoLookupError:
708 return hexnode
713 return hexnode
709
714
710 @templatefunc('strip(text[, chars])')
715 @templatefunc('strip(text[, chars])')
711 def strip(context, mapping, args):
716 def strip(context, mapping, args):
712 """Strip characters from a string. By default,
717 """Strip characters from a string. By default,
713 strips all leading and trailing whitespace."""
718 strips all leading and trailing whitespace."""
714 if not (1 <= len(args) <= 2):
719 if not (1 <= len(args) <= 2):
715 # i18n: "strip" is a keyword
720 # i18n: "strip" is a keyword
716 raise error.ParseError(_("strip expects one or two arguments"))
721 raise error.ParseError(_("strip expects one or two arguments"))
717
722
718 text = evalstring(context, mapping, args[0])
723 text = evalstring(context, mapping, args[0])
719 if len(args) == 2:
724 if len(args) == 2:
720 chars = evalstring(context, mapping, args[1])
725 chars = evalstring(context, mapping, args[1])
721 return text.strip(chars)
726 return text.strip(chars)
722 return text.strip()
727 return text.strip()
723
728
724 @templatefunc('sub(pattern, replacement, expression)')
729 @templatefunc('sub(pattern, replacement, expression)')
725 def sub(context, mapping, args):
730 def sub(context, mapping, args):
726 """Perform text substitution
731 """Perform text substitution
727 using regular expressions."""
732 using regular expressions."""
728 if len(args) != 3:
733 if len(args) != 3:
729 # i18n: "sub" is a keyword
734 # i18n: "sub" is a keyword
730 raise error.ParseError(_("sub expects three arguments"))
735 raise error.ParseError(_("sub expects three arguments"))
731
736
732 pat = evalstring(context, mapping, args[0])
737 pat = evalstring(context, mapping, args[0])
733 rpl = evalstring(context, mapping, args[1])
738 rpl = evalstring(context, mapping, args[1])
734 src = evalstring(context, mapping, args[2])
739 src = evalstring(context, mapping, args[2])
735 try:
740 try:
736 patre = re.compile(pat)
741 patre = re.compile(pat)
737 except re.error:
742 except re.error:
738 # i18n: "sub" is a keyword
743 # i18n: "sub" is a keyword
739 raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
744 raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
740 try:
745 try:
741 yield patre.sub(rpl, src)
746 yield patre.sub(rpl, src)
742 except re.error:
747 except re.error:
743 # i18n: "sub" is a keyword
748 # i18n: "sub" is a keyword
744 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
749 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
745
750
746 @templatefunc('startswith(pattern, text)')
751 @templatefunc('startswith(pattern, text)')
747 def startswith(context, mapping, args):
752 def startswith(context, mapping, args):
748 """Returns the value from the "text" argument
753 """Returns the value from the "text" argument
749 if it begins with the content from the "pattern" argument."""
754 if it begins with the content from the "pattern" argument."""
750 if len(args) != 2:
755 if len(args) != 2:
751 # i18n: "startswith" is a keyword
756 # i18n: "startswith" is a keyword
752 raise error.ParseError(_("startswith expects two arguments"))
757 raise error.ParseError(_("startswith expects two arguments"))
753
758
754 patn = evalstring(context, mapping, args[0])
759 patn = evalstring(context, mapping, args[0])
755 text = evalstring(context, mapping, args[1])
760 text = evalstring(context, mapping, args[1])
756 if text.startswith(patn):
761 if text.startswith(patn):
757 return text
762 return text
758 return ''
763 return ''
759
764
760 @templatefunc('word(number, text[, separator])')
765 @templatefunc('word(number, text[, separator])')
761 def word(context, mapping, args):
766 def word(context, mapping, args):
762 """Return the nth word from a string."""
767 """Return the nth word from a string."""
763 if not (2 <= len(args) <= 3):
768 if not (2 <= len(args) <= 3):
764 # i18n: "word" is a keyword
769 # i18n: "word" is a keyword
765 raise error.ParseError(_("word expects two or three arguments, got %d")
770 raise error.ParseError(_("word expects two or three arguments, got %d")
766 % len(args))
771 % len(args))
767
772
768 num = evalinteger(context, mapping, args[0],
773 num = evalinteger(context, mapping, args[0],
769 # i18n: "word" is a keyword
774 # i18n: "word" is a keyword
770 _("word expects an integer index"))
775 _("word expects an integer index"))
771 text = evalstring(context, mapping, args[1])
776 text = evalstring(context, mapping, args[1])
772 if len(args) == 3:
777 if len(args) == 3:
773 splitter = evalstring(context, mapping, args[2])
778 splitter = evalstring(context, mapping, args[2])
774 else:
779 else:
775 splitter = None
780 splitter = None
776
781
777 tokens = text.split(splitter)
782 tokens = text.split(splitter)
778 if num >= len(tokens) or num < -len(tokens):
783 if num >= len(tokens) or num < -len(tokens):
779 return ''
784 return ''
780 else:
785 else:
781 return tokens[num]
786 return tokens[num]
782
787
783 def loadfunction(ui, extname, registrarobj):
788 def loadfunction(ui, extname, registrarobj):
784 """Load template function from specified registrarobj
789 """Load template function from specified registrarobj
785 """
790 """
786 for name, func in registrarobj._table.iteritems():
791 for name, func in registrarobj._table.iteritems():
787 funcs[name] = func
792 funcs[name] = func
788
793
789 # tell hggettext to extract docstrings from these functions:
794 # tell hggettext to extract docstrings from these functions:
790 i18nfunctions = funcs.values()
795 i18nfunctions = funcs.values()
@@ -1,1627 +1,1638
1 Test template filters and functions
1 Test template filters and functions
2 ===================================
2 ===================================
3
3
4 $ hg init a
4 $ hg init a
5 $ cd a
5 $ cd a
6 $ echo a > a
6 $ echo a > a
7 $ hg add a
7 $ hg add a
8 $ echo line 1 > b
8 $ echo line 1 > b
9 $ echo line 2 >> b
9 $ echo line 2 >> b
10 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
10 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
11
11
12 $ hg add b
12 $ hg add b
13 $ echo other 1 > c
13 $ echo other 1 > c
14 $ echo other 2 >> c
14 $ echo other 2 >> c
15 $ echo >> c
15 $ echo >> c
16 $ echo other 3 >> c
16 $ echo other 3 >> c
17 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
17 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
18
18
19 $ hg add c
19 $ hg add c
20 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
20 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
21 $ echo c >> c
21 $ echo c >> c
22 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
22 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
23
23
24 $ echo foo > .hg/branch
24 $ echo foo > .hg/branch
25 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
25 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
26
26
27 $ hg co -q 3
27 $ hg co -q 3
28 $ echo other 4 >> d
28 $ echo other 4 >> d
29 $ hg add d
29 $ hg add d
30 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
30 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
31
31
32 $ hg merge -q foo
32 $ hg merge -q foo
33 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
33 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
34
34
35 Second branch starting at nullrev:
35 Second branch starting at nullrev:
36
36
37 $ hg update null
37 $ hg update null
38 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
38 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
39 $ echo second > second
39 $ echo second > second
40 $ hg add second
40 $ hg add second
41 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
41 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
42 created new head
42 created new head
43
43
44 $ echo third > third
44 $ echo third > third
45 $ hg add third
45 $ hg add third
46 $ hg mv second fourth
46 $ hg mv second fourth
47 $ hg commit -m third -d "2020-01-01 10:01"
47 $ hg commit -m third -d "2020-01-01 10:01"
48
48
49 $ hg phase -r 5 --public
49 $ hg phase -r 5 --public
50 $ hg phase -r 7 --secret --force
50 $ hg phase -r 7 --secret --force
51
51
52 Filters work:
52 Filters work:
53
53
54 $ hg log --template '{author|domain}\n'
54 $ hg log --template '{author|domain}\n'
55
55
56 hostname
56 hostname
57
57
58
58
59
59
60
60
61 place
61 place
62 place
62 place
63 hostname
63 hostname
64
64
65 $ hg log --template '{author|person}\n'
65 $ hg log --template '{author|person}\n'
66 test
66 test
67 User Name
67 User Name
68 person
68 person
69 person
69 person
70 person
70 person
71 person
71 person
72 other
72 other
73 A. N. Other
73 A. N. Other
74 User Name
74 User Name
75
75
76 $ hg log --template '{author|user}\n'
76 $ hg log --template '{author|user}\n'
77 test
77 test
78 user
78 user
79 person
79 person
80 person
80 person
81 person
81 person
82 person
82 person
83 other
83 other
84 other
84 other
85 user
85 user
86
86
87 $ hg log --template '{date|date}\n'
87 $ hg log --template '{date|date}\n'
88 Wed Jan 01 10:01:00 2020 +0000
88 Wed Jan 01 10:01:00 2020 +0000
89 Mon Jan 12 13:46:40 1970 +0000
89 Mon Jan 12 13:46:40 1970 +0000
90 Sun Jan 18 08:40:01 1970 +0000
90 Sun Jan 18 08:40:01 1970 +0000
91 Sun Jan 18 08:40:00 1970 +0000
91 Sun Jan 18 08:40:00 1970 +0000
92 Sat Jan 17 04:53:20 1970 +0000
92 Sat Jan 17 04:53:20 1970 +0000
93 Fri Jan 16 01:06:40 1970 +0000
93 Fri Jan 16 01:06:40 1970 +0000
94 Wed Jan 14 21:20:00 1970 +0000
94 Wed Jan 14 21:20:00 1970 +0000
95 Tue Jan 13 17:33:20 1970 +0000
95 Tue Jan 13 17:33:20 1970 +0000
96 Mon Jan 12 13:46:40 1970 +0000
96 Mon Jan 12 13:46:40 1970 +0000
97
97
98 $ hg log --template '{date|isodate}\n'
98 $ hg log --template '{date|isodate}\n'
99 2020-01-01 10:01 +0000
99 2020-01-01 10:01 +0000
100 1970-01-12 13:46 +0000
100 1970-01-12 13:46 +0000
101 1970-01-18 08:40 +0000
101 1970-01-18 08:40 +0000
102 1970-01-18 08:40 +0000
102 1970-01-18 08:40 +0000
103 1970-01-17 04:53 +0000
103 1970-01-17 04:53 +0000
104 1970-01-16 01:06 +0000
104 1970-01-16 01:06 +0000
105 1970-01-14 21:20 +0000
105 1970-01-14 21:20 +0000
106 1970-01-13 17:33 +0000
106 1970-01-13 17:33 +0000
107 1970-01-12 13:46 +0000
107 1970-01-12 13:46 +0000
108
108
109 $ hg log --template '{date|isodatesec}\n'
109 $ hg log --template '{date|isodatesec}\n'
110 2020-01-01 10:01:00 +0000
110 2020-01-01 10:01:00 +0000
111 1970-01-12 13:46:40 +0000
111 1970-01-12 13:46:40 +0000
112 1970-01-18 08:40:01 +0000
112 1970-01-18 08:40:01 +0000
113 1970-01-18 08:40:00 +0000
113 1970-01-18 08:40:00 +0000
114 1970-01-17 04:53:20 +0000
114 1970-01-17 04:53:20 +0000
115 1970-01-16 01:06:40 +0000
115 1970-01-16 01:06:40 +0000
116 1970-01-14 21:20:00 +0000
116 1970-01-14 21:20:00 +0000
117 1970-01-13 17:33:20 +0000
117 1970-01-13 17:33:20 +0000
118 1970-01-12 13:46:40 +0000
118 1970-01-12 13:46:40 +0000
119
119
120 $ hg log --template '{date|rfc822date}\n'
120 $ hg log --template '{date|rfc822date}\n'
121 Wed, 01 Jan 2020 10:01:00 +0000
121 Wed, 01 Jan 2020 10:01:00 +0000
122 Mon, 12 Jan 1970 13:46:40 +0000
122 Mon, 12 Jan 1970 13:46:40 +0000
123 Sun, 18 Jan 1970 08:40:01 +0000
123 Sun, 18 Jan 1970 08:40:01 +0000
124 Sun, 18 Jan 1970 08:40:00 +0000
124 Sun, 18 Jan 1970 08:40:00 +0000
125 Sat, 17 Jan 1970 04:53:20 +0000
125 Sat, 17 Jan 1970 04:53:20 +0000
126 Fri, 16 Jan 1970 01:06:40 +0000
126 Fri, 16 Jan 1970 01:06:40 +0000
127 Wed, 14 Jan 1970 21:20:00 +0000
127 Wed, 14 Jan 1970 21:20:00 +0000
128 Tue, 13 Jan 1970 17:33:20 +0000
128 Tue, 13 Jan 1970 17:33:20 +0000
129 Mon, 12 Jan 1970 13:46:40 +0000
129 Mon, 12 Jan 1970 13:46:40 +0000
130
130
131 $ hg log --template '{desc|firstline}\n'
131 $ hg log --template '{desc|firstline}\n'
132 third
132 third
133 second
133 second
134 merge
134 merge
135 new head
135 new head
136 new branch
136 new branch
137 no user, no domain
137 no user, no domain
138 no person
138 no person
139 other 1
139 other 1
140 line 1
140 line 1
141
141
142 $ hg log --template '{node|short}\n'
142 $ hg log --template '{node|short}\n'
143 95c24699272e
143 95c24699272e
144 29114dbae42b
144 29114dbae42b
145 d41e714fe50d
145 d41e714fe50d
146 13207e5a10d9
146 13207e5a10d9
147 bbe44766e73d
147 bbe44766e73d
148 10e46f2dcbf4
148 10e46f2dcbf4
149 97054abb4ab8
149 97054abb4ab8
150 b608e9d1a3f0
150 b608e9d1a3f0
151 1e4e1b8f71e0
151 1e4e1b8f71e0
152
152
153 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
153 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
154 <changeset author="test"/>
154 <changeset author="test"/>
155 <changeset author="User Name &lt;user@hostname&gt;"/>
155 <changeset author="User Name &lt;user@hostname&gt;"/>
156 <changeset author="person"/>
156 <changeset author="person"/>
157 <changeset author="person"/>
157 <changeset author="person"/>
158 <changeset author="person"/>
158 <changeset author="person"/>
159 <changeset author="person"/>
159 <changeset author="person"/>
160 <changeset author="other@place"/>
160 <changeset author="other@place"/>
161 <changeset author="A. N. Other &lt;other@place&gt;"/>
161 <changeset author="A. N. Other &lt;other@place&gt;"/>
162 <changeset author="User Name &lt;user@hostname&gt;"/>
162 <changeset author="User Name &lt;user@hostname&gt;"/>
163
163
164 $ hg log --template '{rev}: {children}\n'
164 $ hg log --template '{rev}: {children}\n'
165 8:
165 8:
166 7: 8:95c24699272e
166 7: 8:95c24699272e
167 6:
167 6:
168 5: 6:d41e714fe50d
168 5: 6:d41e714fe50d
169 4: 6:d41e714fe50d
169 4: 6:d41e714fe50d
170 3: 4:bbe44766e73d 5:13207e5a10d9
170 3: 4:bbe44766e73d 5:13207e5a10d9
171 2: 3:10e46f2dcbf4
171 2: 3:10e46f2dcbf4
172 1: 2:97054abb4ab8
172 1: 2:97054abb4ab8
173 0: 1:b608e9d1a3f0
173 0: 1:b608e9d1a3f0
174
174
175 Formatnode filter works:
175 Formatnode filter works:
176
176
177 $ hg -q log -r 0 --template '{node|formatnode}\n'
177 $ hg -q log -r 0 --template '{node|formatnode}\n'
178 1e4e1b8f71e0
178 1e4e1b8f71e0
179
179
180 $ hg log -r 0 --template '{node|formatnode}\n'
180 $ hg log -r 0 --template '{node|formatnode}\n'
181 1e4e1b8f71e0
181 1e4e1b8f71e0
182
182
183 $ hg -v log -r 0 --template '{node|formatnode}\n'
183 $ hg -v log -r 0 --template '{node|formatnode}\n'
184 1e4e1b8f71e0
184 1e4e1b8f71e0
185
185
186 $ hg --debug log -r 0 --template '{node|formatnode}\n'
186 $ hg --debug log -r 0 --template '{node|formatnode}\n'
187 1e4e1b8f71e05681d422154f5421e385fec3454f
187 1e4e1b8f71e05681d422154f5421e385fec3454f
188
188
189 Age filter:
189 Age filter:
190
190
191 $ hg init unstable-hash
191 $ hg init unstable-hash
192 $ cd unstable-hash
192 $ cd unstable-hash
193 $ hg log --template '{date|age}\n' > /dev/null || exit 1
193 $ hg log --template '{date|age}\n' > /dev/null || exit 1
194
194
195 >>> from __future__ import absolute_import
195 >>> from __future__ import absolute_import
196 >>> import datetime
196 >>> import datetime
197 >>> fp = open('a', 'wb')
197 >>> fp = open('a', 'wb')
198 >>> n = datetime.datetime.now() + datetime.timedelta(366 * 7)
198 >>> n = datetime.datetime.now() + datetime.timedelta(366 * 7)
199 >>> fp.write(b'%d-%d-%d 00:00' % (n.year, n.month, n.day)) and None
199 >>> fp.write(b'%d-%d-%d 00:00' % (n.year, n.month, n.day)) and None
200 >>> fp.close()
200 >>> fp.close()
201 $ hg add a
201 $ hg add a
202 $ hg commit -m future -d "`cat a`"
202 $ hg commit -m future -d "`cat a`"
203
203
204 $ hg log -l1 --template '{date|age}\n'
204 $ hg log -l1 --template '{date|age}\n'
205 7 years from now
205 7 years from now
206
206
207 $ cd ..
207 $ cd ..
208 $ rm -rf unstable-hash
208 $ rm -rf unstable-hash
209
209
210 Filename filters:
210 Filename filters:
211
211
212 $ hg debugtemplate '{"foo/bar"|basename}|{"foo/"|basename}|{"foo"|basename}|\n'
212 $ hg debugtemplate '{"foo/bar"|basename}|{"foo/"|basename}|{"foo"|basename}|\n'
213 bar||foo|
213 bar||foo|
214 $ hg debugtemplate '{"foo/bar"|dirname}|{"foo/"|dirname}|{"foo"|dirname}|\n'
214 $ hg debugtemplate '{"foo/bar"|dirname}|{"foo/"|dirname}|{"foo"|dirname}|\n'
215 foo|foo||
215 foo|foo||
216 $ hg debugtemplate '{"foo/bar"|stripdir}|{"foo/"|stripdir}|{"foo"|stripdir}|\n'
216 $ hg debugtemplate '{"foo/bar"|stripdir}|{"foo/"|stripdir}|{"foo"|stripdir}|\n'
217 foo|foo|foo|
217 foo|foo|foo|
218
218
219 commondir() filter:
219 commondir() filter:
220
220
221 $ hg debugtemplate '{""|splitlines|commondir}\n'
221 $ hg debugtemplate '{""|splitlines|commondir}\n'
222
222
223 $ hg debugtemplate '{"foo/bar\nfoo/baz\nfoo/foobar\n"|splitlines|commondir}\n'
223 $ hg debugtemplate '{"foo/bar\nfoo/baz\nfoo/foobar\n"|splitlines|commondir}\n'
224 foo
224 foo
225 $ hg debugtemplate '{"foo/bar\nfoo/bar\n"|splitlines|commondir}\n'
225 $ hg debugtemplate '{"foo/bar\nfoo/bar\n"|splitlines|commondir}\n'
226 foo
226 foo
227 $ hg debugtemplate '{"/foo/bar\n/foo/bar\n"|splitlines|commondir}\n'
227 $ hg debugtemplate '{"/foo/bar\n/foo/bar\n"|splitlines|commondir}\n'
228 foo
228 foo
229 $ hg debugtemplate '{"/foo\n/foo\n"|splitlines|commondir}\n'
229 $ hg debugtemplate '{"/foo\n/foo\n"|splitlines|commondir}\n'
230
230
231 $ hg debugtemplate '{"foo/bar\nbar/baz"|splitlines|commondir}\n'
231 $ hg debugtemplate '{"foo/bar\nbar/baz"|splitlines|commondir}\n'
232
232
233 $ hg debugtemplate '{"foo/bar\nbar/baz\nbar/foo\n"|splitlines|commondir}\n'
233 $ hg debugtemplate '{"foo/bar\nbar/baz\nbar/foo\n"|splitlines|commondir}\n'
234
234
235 $ hg debugtemplate '{"foo/../bar\nfoo/bar"|splitlines|commondir}\n'
235 $ hg debugtemplate '{"foo/../bar\nfoo/bar"|splitlines|commondir}\n'
236 foo
236 foo
237 $ hg debugtemplate '{"foo\n/foo"|splitlines|commondir}\n'
237 $ hg debugtemplate '{"foo\n/foo"|splitlines|commondir}\n'
238
238
239
239
240 $ hg log -r null -T '{rev|commondir}'
240 $ hg log -r null -T '{rev|commondir}'
241 hg: parse error: argument is not a list of text
241 hg: parse error: argument is not a list of text
242 (template filter 'commondir' is not compatible with keyword 'rev')
242 (template filter 'commondir' is not compatible with keyword 'rev')
243 [255]
243 [255]
244
244
245 Add a dummy commit to make up for the instability of the above:
245 Add a dummy commit to make up for the instability of the above:
246
246
247 $ echo a > a
247 $ echo a > a
248 $ hg add a
248 $ hg add a
249 $ hg ci -m future
249 $ hg ci -m future
250
250
251 Count filter:
251 Count filter:
252
252
253 $ hg log -l1 --template '{node|count} {node|short|count}\n'
253 $ hg log -l1 --template '{node|count} {node|short|count}\n'
254 40 12
254 40 12
255
255
256 $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
256 $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
257 0 1 4
257 0 1 4
258
258
259 $ hg log -G --template '{rev}: children: {children|count}, \
259 $ hg log -G --template '{rev}: children: {children|count}, \
260 > tags: {tags|count}, file_adds: {file_adds|count}, \
260 > tags: {tags|count}, file_adds: {file_adds|count}, \
261 > ancestors: {revset("ancestors(%s)", rev)|count}'
261 > ancestors: {revset("ancestors(%s)", rev)|count}'
262 @ 9: children: 0, tags: 1, file_adds: 1, ancestors: 3
262 @ 9: children: 0, tags: 1, file_adds: 1, ancestors: 3
263 |
263 |
264 o 8: children: 1, tags: 0, file_adds: 2, ancestors: 2
264 o 8: children: 1, tags: 0, file_adds: 2, ancestors: 2
265 |
265 |
266 o 7: children: 1, tags: 0, file_adds: 1, ancestors: 1
266 o 7: children: 1, tags: 0, file_adds: 1, ancestors: 1
267
267
268 o 6: children: 0, tags: 0, file_adds: 0, ancestors: 7
268 o 6: children: 0, tags: 0, file_adds: 0, ancestors: 7
269 |\
269 |\
270 | o 5: children: 1, tags: 0, file_adds: 1, ancestors: 5
270 | o 5: children: 1, tags: 0, file_adds: 1, ancestors: 5
271 | |
271 | |
272 o | 4: children: 1, tags: 0, file_adds: 0, ancestors: 5
272 o | 4: children: 1, tags: 0, file_adds: 0, ancestors: 5
273 |/
273 |/
274 o 3: children: 2, tags: 0, file_adds: 0, ancestors: 4
274 o 3: children: 2, tags: 0, file_adds: 0, ancestors: 4
275 |
275 |
276 o 2: children: 1, tags: 0, file_adds: 1, ancestors: 3
276 o 2: children: 1, tags: 0, file_adds: 1, ancestors: 3
277 |
277 |
278 o 1: children: 1, tags: 0, file_adds: 1, ancestors: 2
278 o 1: children: 1, tags: 0, file_adds: 1, ancestors: 2
279 |
279 |
280 o 0: children: 1, tags: 0, file_adds: 1, ancestors: 1
280 o 0: children: 1, tags: 0, file_adds: 1, ancestors: 1
281
281
282
282
283 $ hg log -l1 -T '{termwidth|count}\n'
283 $ hg log -l1 -T '{termwidth|count}\n'
284 hg: parse error: not countable
284 hg: parse error: not countable
285 (template filter 'count' is not compatible with keyword 'termwidth')
285 (template filter 'count' is not compatible with keyword 'termwidth')
286 [255]
286 [255]
287
287
288 Upper/lower filters:
288 Upper/lower filters:
289
289
290 $ hg log -r0 --template '{branch|upper}\n'
290 $ hg log -r0 --template '{branch|upper}\n'
291 DEFAULT
291 DEFAULT
292 $ hg log -r0 --template '{author|lower}\n'
292 $ hg log -r0 --template '{author|lower}\n'
293 user name <user@hostname>
293 user name <user@hostname>
294 $ hg log -r0 --template '{date|upper}\n'
294 $ hg log -r0 --template '{date|upper}\n'
295 1000000.00
295 1000000.00
296
296
297 Add a commit that does all possible modifications at once
297 Add a commit that does all possible modifications at once
298
298
299 $ echo modify >> third
299 $ echo modify >> third
300 $ touch b
300 $ touch b
301 $ hg add b
301 $ hg add b
302 $ hg mv fourth fifth
302 $ hg mv fourth fifth
303 $ hg rm a
303 $ hg rm a
304 $ hg ci -m "Modify, add, remove, rename"
304 $ hg ci -m "Modify, add, remove, rename"
305
305
306 Pass generator object created by template function to filter
306 Pass generator object created by template function to filter
307
307
308 $ hg log -l 1 --template '{if(author, author)|user}\n'
308 $ hg log -l 1 --template '{if(author, author)|user}\n'
309 test
309 test
310
310
311 Test diff function:
311 Test diff function:
312
312
313 $ hg diff -c 8
313 $ hg diff -c 8
314 diff -r 29114dbae42b -r 95c24699272e fourth
314 diff -r 29114dbae42b -r 95c24699272e fourth
315 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
315 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
316 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
316 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
317 @@ -0,0 +1,1 @@
317 @@ -0,0 +1,1 @@
318 +second
318 +second
319 diff -r 29114dbae42b -r 95c24699272e second
319 diff -r 29114dbae42b -r 95c24699272e second
320 --- a/second Mon Jan 12 13:46:40 1970 +0000
320 --- a/second Mon Jan 12 13:46:40 1970 +0000
321 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
321 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
322 @@ -1,1 +0,0 @@
322 @@ -1,1 +0,0 @@
323 -second
323 -second
324 diff -r 29114dbae42b -r 95c24699272e third
324 diff -r 29114dbae42b -r 95c24699272e third
325 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
325 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
326 +++ b/third Wed Jan 01 10:01:00 2020 +0000
326 +++ b/third Wed Jan 01 10:01:00 2020 +0000
327 @@ -0,0 +1,1 @@
327 @@ -0,0 +1,1 @@
328 +third
328 +third
329
329
330 $ hg log -r 8 -T "{diff()}"
330 $ hg log -r 8 -T "{diff()}"
331 diff -r 29114dbae42b -r 95c24699272e fourth
331 diff -r 29114dbae42b -r 95c24699272e fourth
332 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
332 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
333 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
333 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
334 @@ -0,0 +1,1 @@
334 @@ -0,0 +1,1 @@
335 +second
335 +second
336 diff -r 29114dbae42b -r 95c24699272e second
336 diff -r 29114dbae42b -r 95c24699272e second
337 --- a/second Mon Jan 12 13:46:40 1970 +0000
337 --- a/second Mon Jan 12 13:46:40 1970 +0000
338 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
338 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
339 @@ -1,1 +0,0 @@
339 @@ -1,1 +0,0 @@
340 -second
340 -second
341 diff -r 29114dbae42b -r 95c24699272e third
341 diff -r 29114dbae42b -r 95c24699272e third
342 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
342 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
343 +++ b/third Wed Jan 01 10:01:00 2020 +0000
343 +++ b/third Wed Jan 01 10:01:00 2020 +0000
344 @@ -0,0 +1,1 @@
344 @@ -0,0 +1,1 @@
345 +third
345 +third
346
346
347 $ hg log -r 8 -T "{diff('glob:f*')}"
347 $ hg log -r 8 -T "{diff('glob:f*')}"
348 diff -r 29114dbae42b -r 95c24699272e fourth
348 diff -r 29114dbae42b -r 95c24699272e fourth
349 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
349 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
350 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
350 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
351 @@ -0,0 +1,1 @@
351 @@ -0,0 +1,1 @@
352 +second
352 +second
353
353
354 $ hg log -r 8 -T "{diff('', 'glob:f*')}"
354 $ hg log -r 8 -T "{diff('', 'glob:f*')}"
355 diff -r 29114dbae42b -r 95c24699272e second
355 diff -r 29114dbae42b -r 95c24699272e second
356 --- a/second Mon Jan 12 13:46:40 1970 +0000
356 --- a/second Mon Jan 12 13:46:40 1970 +0000
357 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
357 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
358 @@ -1,1 +0,0 @@
358 @@ -1,1 +0,0 @@
359 -second
359 -second
360 diff -r 29114dbae42b -r 95c24699272e third
360 diff -r 29114dbae42b -r 95c24699272e third
361 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
361 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
362 +++ b/third Wed Jan 01 10:01:00 2020 +0000
362 +++ b/third Wed Jan 01 10:01:00 2020 +0000
363 @@ -0,0 +1,1 @@
363 @@ -0,0 +1,1 @@
364 +third
364 +third
365
365
366 $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
366 $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
367 diff -r 29114dbae42b -r 95c24699272e fourth
367 diff -r 29114dbae42b -r 95c24699272e fourth
368 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
368 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
369 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
369 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
370 @@ -0,0 +1,1 @@
370 @@ -0,0 +1,1 @@
371 +second
371 +second
372
372
373 $ hg --config diff.git=true log -r 8 -T "{diff()}"
374 diff --git a/second b/fourth
375 rename from second
376 rename to fourth
377 diff --git a/third b/third
378 new file mode 100644
379 --- /dev/null
380 +++ b/third
381 @@ -0,0 +1,1 @@
382 +third
383
373 $ cd ..
384 $ cd ..
374
385
375 latesttag() function:
386 latesttag() function:
376
387
377 $ hg init latesttag
388 $ hg init latesttag
378 $ cd latesttag
389 $ cd latesttag
379
390
380 $ echo a > file
391 $ echo a > file
381 $ hg ci -Am a -d '0 0'
392 $ hg ci -Am a -d '0 0'
382 adding file
393 adding file
383
394
384 $ echo b >> file
395 $ echo b >> file
385 $ hg ci -m b -d '1 0'
396 $ hg ci -m b -d '1 0'
386
397
387 $ echo c >> head1
398 $ echo c >> head1
388 $ hg ci -Am h1c -d '2 0'
399 $ hg ci -Am h1c -d '2 0'
389 adding head1
400 adding head1
390
401
391 $ hg update -q 1
402 $ hg update -q 1
392 $ echo d >> head2
403 $ echo d >> head2
393 $ hg ci -Am h2d -d '3 0'
404 $ hg ci -Am h2d -d '3 0'
394 adding head2
405 adding head2
395 created new head
406 created new head
396
407
397 $ echo e >> head2
408 $ echo e >> head2
398 $ hg ci -m h2e -d '4 0'
409 $ hg ci -m h2e -d '4 0'
399
410
400 $ hg merge -q
411 $ hg merge -q
401 $ hg ci -m merge -d '5 -3600'
412 $ hg ci -m merge -d '5 -3600'
402
413
403 $ hg tag -r 1 -m t1 -d '6 0' t1
414 $ hg tag -r 1 -m t1 -d '6 0' t1
404 $ hg tag -r 2 -m t2 -d '7 0' t2
415 $ hg tag -r 2 -m t2 -d '7 0' t2
405 $ hg tag -r 3 -m t3 -d '8 0' t3
416 $ hg tag -r 3 -m t3 -d '8 0' t3
406 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
417 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
407 $ hg tag -r 5 -m t5 -d '9 0' t5
418 $ hg tag -r 5 -m t5 -d '9 0' t5
408 $ hg tag -r 3 -m at3 -d '10 0' at3
419 $ hg tag -r 3 -m at3 -d '10 0' at3
409
420
410 $ hg log -G --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
421 $ hg log -G --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
411 @ 11: t3, C: 9, D: 8
422 @ 11: t3, C: 9, D: 8
412 |
423 |
413 o 10: t3, C: 8, D: 7
424 o 10: t3, C: 8, D: 7
414 |
425 |
415 o 9: t3, C: 7, D: 6
426 o 9: t3, C: 7, D: 6
416 |
427 |
417 o 8: t3, C: 6, D: 5
428 o 8: t3, C: 6, D: 5
418 |
429 |
419 o 7: t3, C: 5, D: 4
430 o 7: t3, C: 5, D: 4
420 |
431 |
421 o 6: t3, C: 4, D: 3
432 o 6: t3, C: 4, D: 3
422 |
433 |
423 o 5: t3, C: 3, D: 2
434 o 5: t3, C: 3, D: 2
424 |\
435 |\
425 | o 4: t3, C: 1, D: 1
436 | o 4: t3, C: 1, D: 1
426 | |
437 | |
427 | o 3: t3, C: 0, D: 0
438 | o 3: t3, C: 0, D: 0
428 | |
439 | |
429 o | 2: t1, C: 1, D: 1
440 o | 2: t1, C: 1, D: 1
430 |/
441 |/
431 o 1: t1, C: 0, D: 0
442 o 1: t1, C: 0, D: 0
432 |
443 |
433 o 0: null, C: 1, D: 1
444 o 0: null, C: 1, D: 1
434
445
435
446
436 $ cd ..
447 $ cd ..
437
448
438 Test filter() empty values:
449 Test filter() empty values:
439
450
440 $ hg log -R a -r 1 -T '{filter(desc|splitlines) % "{line}\n"}'
451 $ hg log -R a -r 1 -T '{filter(desc|splitlines) % "{line}\n"}'
441 other 1
452 other 1
442 other 2
453 other 2
443 other 3
454 other 3
444 $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1) % "{ifeq(key, "a", "{value}\n")}")}'
455 $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1) % "{ifeq(key, "a", "{value}\n")}")}'
445 0
456 0
446
457
447 0 should not be falsy
458 0 should not be falsy
448
459
449 $ hg log -R a -r 0 -T '{filter(revset("0:2"))}\n'
460 $ hg log -R a -r 0 -T '{filter(revset("0:2"))}\n'
450 0 1 2
461 0 1 2
451
462
452 Test filter() by expression:
463 Test filter() by expression:
453
464
454 $ hg log -R a -r 1 -T '{filter(desc|splitlines, ifcontains("1", line, "t"))}\n'
465 $ hg log -R a -r 1 -T '{filter(desc|splitlines, ifcontains("1", line, "t"))}\n'
455 other 1
466 other 1
456 $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1), ifeq(key, "b", "t"))}\n'
467 $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1), ifeq(key, "b", "t"))}\n'
457 b=1
468 b=1
458
469
459 Test filter() shouldn't crash:
470 Test filter() shouldn't crash:
460
471
461 $ hg log -R a -r 0 -T '{filter(extras)}\n'
472 $ hg log -R a -r 0 -T '{filter(extras)}\n'
462 branch=default
473 branch=default
463 $ hg log -R a -r 0 -T '{filter(files)}\n'
474 $ hg log -R a -r 0 -T '{filter(files)}\n'
464 a
475 a
465
476
466 Test filter() unsupported arguments:
477 Test filter() unsupported arguments:
467
478
468 $ hg log -R a -r 0 -T '{filter()}\n'
479 $ hg log -R a -r 0 -T '{filter()}\n'
469 hg: parse error: filter expects one or two arguments
480 hg: parse error: filter expects one or two arguments
470 [255]
481 [255]
471 $ hg log -R a -r 0 -T '{filter(date)}\n'
482 $ hg log -R a -r 0 -T '{filter(date)}\n'
472 hg: parse error: date is not iterable
483 hg: parse error: date is not iterable
473 [255]
484 [255]
474 $ hg log -R a -r 0 -T '{filter(rev)}\n'
485 $ hg log -R a -r 0 -T '{filter(rev)}\n'
475 hg: parse error: 0 is not iterable
486 hg: parse error: 0 is not iterable
476 [255]
487 [255]
477 $ hg log -R a -r 0 -T '{filter(desc|firstline)}\n'
488 $ hg log -R a -r 0 -T '{filter(desc|firstline)}\n'
478 hg: parse error: 'line 1' is not filterable
489 hg: parse error: 'line 1' is not filterable
479 [255]
490 [255]
480 $ hg log -R a -r 0 -T '{filter(manifest)}\n'
491 $ hg log -R a -r 0 -T '{filter(manifest)}\n'
481 hg: parse error: '0:a0c8bcbbb45c' is not filterable
492 hg: parse error: '0:a0c8bcbbb45c' is not filterable
482 [255]
493 [255]
483 $ hg log -R a -r 0 -T '{filter(succsandmarkers)}\n'
494 $ hg log -R a -r 0 -T '{filter(succsandmarkers)}\n'
484 hg: parse error: not filterable without template
495 hg: parse error: not filterable without template
485 [255]
496 [255]
486 $ hg log -R a -r 0 -T '{filter(desc|splitlines % "{line}", "")}\n'
497 $ hg log -R a -r 0 -T '{filter(desc|splitlines % "{line}", "")}\n'
487 hg: parse error: not filterable by expression
498 hg: parse error: not filterable by expression
488 [255]
499 [255]
489
500
490 Test manifest/get() can be join()-ed as string, though it's silly:
501 Test manifest/get() can be join()-ed as string, though it's silly:
491
502
492 $ hg log -R latesttag -r tip -T '{join(manifest, ".")}\n'
503 $ hg log -R latesttag -r tip -T '{join(manifest, ".")}\n'
493 1.1.:.2.b.c.6.e.9.0.0.6.c.e.2
504 1.1.:.2.b.c.6.e.9.0.0.6.c.e.2
494 $ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), ".")}\n'
505 $ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), ".")}\n'
495 d.e.f.a.u.l.t
506 d.e.f.a.u.l.t
496
507
497 Test join() over string
508 Test join() over string
498
509
499 $ hg log -R latesttag -r tip -T '{join(rev|stringify, ".")}\n'
510 $ hg log -R latesttag -r tip -T '{join(rev|stringify, ".")}\n'
500 1.1
511 1.1
501
512
502 Test join() over uniterable
513 Test join() over uniterable
503
514
504 $ hg log -R latesttag -r tip -T '{join(rev, "")}\n'
515 $ hg log -R latesttag -r tip -T '{join(rev, "")}\n'
505 hg: parse error: 11 is not iterable
516 hg: parse error: 11 is not iterable
506 [255]
517 [255]
507
518
508 Test min/max of integers
519 Test min/max of integers
509
520
510 $ hg log -R latesttag -l1 -T '{min(revset("9:10"))}\n'
521 $ hg log -R latesttag -l1 -T '{min(revset("9:10"))}\n'
511 9
522 9
512 $ hg log -R latesttag -l1 -T '{max(revset("9:10"))}\n'
523 $ hg log -R latesttag -l1 -T '{max(revset("9:10"))}\n'
513 10
524 10
514
525
515 Test min/max over map operation:
526 Test min/max over map operation:
516
527
517 $ hg log -R latesttag -r3 -T '{min(tags % "{tag}")}\n'
528 $ hg log -R latesttag -r3 -T '{min(tags % "{tag}")}\n'
518 at3
529 at3
519 $ hg log -R latesttag -r3 -T '{max(tags % "{tag}")}\n'
530 $ hg log -R latesttag -r3 -T '{max(tags % "{tag}")}\n'
520 t3
531 t3
521
532
522 Test min/max of strings:
533 Test min/max of strings:
523
534
524 $ hg log -R latesttag -l1 -T '{min(desc)}\n'
535 $ hg log -R latesttag -l1 -T '{min(desc)}\n'
525 3
536 3
526 $ hg log -R latesttag -l1 -T '{max(desc)}\n'
537 $ hg log -R latesttag -l1 -T '{max(desc)}\n'
527 t
538 t
528
539
529 Test min/max of non-iterable:
540 Test min/max of non-iterable:
530
541
531 $ hg debugtemplate '{min(1)}'
542 $ hg debugtemplate '{min(1)}'
532 hg: parse error: 1 is not iterable
543 hg: parse error: 1 is not iterable
533 (min first argument should be an iterable)
544 (min first argument should be an iterable)
534 [255]
545 [255]
535 $ hg debugtemplate '{max(2)}'
546 $ hg debugtemplate '{max(2)}'
536 hg: parse error: 2 is not iterable
547 hg: parse error: 2 is not iterable
537 (max first argument should be an iterable)
548 (max first argument should be an iterable)
538 [255]
549 [255]
539
550
540 $ hg log -R latesttag -l1 -T '{min(date)}'
551 $ hg log -R latesttag -l1 -T '{min(date)}'
541 hg: parse error: date is not iterable
552 hg: parse error: date is not iterable
542 (min first argument should be an iterable)
553 (min first argument should be an iterable)
543 [255]
554 [255]
544 $ hg log -R latesttag -l1 -T '{max(date)}'
555 $ hg log -R latesttag -l1 -T '{max(date)}'
545 hg: parse error: date is not iterable
556 hg: parse error: date is not iterable
546 (max first argument should be an iterable)
557 (max first argument should be an iterable)
547 [255]
558 [255]
548
559
549 Test min/max of empty sequence:
560 Test min/max of empty sequence:
550
561
551 $ hg debugtemplate '{min("")}'
562 $ hg debugtemplate '{min("")}'
552 hg: parse error: empty string
563 hg: parse error: empty string
553 (min first argument should be an iterable)
564 (min first argument should be an iterable)
554 [255]
565 [255]
555 $ hg debugtemplate '{max("")}'
566 $ hg debugtemplate '{max("")}'
556 hg: parse error: empty string
567 hg: parse error: empty string
557 (max first argument should be an iterable)
568 (max first argument should be an iterable)
558 [255]
569 [255]
559 $ hg debugtemplate '{min(dict())}'
570 $ hg debugtemplate '{min(dict())}'
560 hg: parse error: empty sequence
571 hg: parse error: empty sequence
561 (min first argument should be an iterable)
572 (min first argument should be an iterable)
562 [255]
573 [255]
563 $ hg debugtemplate '{max(dict())}'
574 $ hg debugtemplate '{max(dict())}'
564 hg: parse error: empty sequence
575 hg: parse error: empty sequence
565 (max first argument should be an iterable)
576 (max first argument should be an iterable)
566 [255]
577 [255]
567 $ hg debugtemplate '{min(dict() % "")}'
578 $ hg debugtemplate '{min(dict() % "")}'
568 hg: parse error: empty sequence
579 hg: parse error: empty sequence
569 (min first argument should be an iterable)
580 (min first argument should be an iterable)
570 [255]
581 [255]
571 $ hg debugtemplate '{max(dict() % "")}'
582 $ hg debugtemplate '{max(dict() % "")}'
572 hg: parse error: empty sequence
583 hg: parse error: empty sequence
573 (max first argument should be an iterable)
584 (max first argument should be an iterable)
574 [255]
585 [255]
575
586
576 Test min/max of if() result
587 Test min/max of if() result
577
588
578 $ cd latesttag
589 $ cd latesttag
579 $ hg log -l1 -T '{min(if(true, revset("9:10"), ""))}\n'
590 $ hg log -l1 -T '{min(if(true, revset("9:10"), ""))}\n'
580 9
591 9
581 $ hg log -l1 -T '{max(if(false, "", revset("9:10")))}\n'
592 $ hg log -l1 -T '{max(if(false, "", revset("9:10")))}\n'
582 10
593 10
583 $ hg log -l1 -T '{min(ifcontains("a", "aa", revset("9:10"), ""))}\n'
594 $ hg log -l1 -T '{min(ifcontains("a", "aa", revset("9:10"), ""))}\n'
584 9
595 9
585 $ hg log -l1 -T '{max(ifcontains("a", "bb", "", revset("9:10")))}\n'
596 $ hg log -l1 -T '{max(ifcontains("a", "bb", "", revset("9:10")))}\n'
586 10
597 10
587 $ hg log -l1 -T '{min(ifeq(0, 0, revset("9:10"), ""))}\n'
598 $ hg log -l1 -T '{min(ifeq(0, 0, revset("9:10"), ""))}\n'
588 9
599 9
589 $ hg log -l1 -T '{max(ifeq(0, 1, "", revset("9:10")))}\n'
600 $ hg log -l1 -T '{max(ifeq(0, 1, "", revset("9:10")))}\n'
590 10
601 10
591 $ cd ..
602 $ cd ..
592
603
593 Test laziness of if() then/else clause
604 Test laziness of if() then/else clause
594
605
595 $ hg debugtemplate '{count(0)}'
606 $ hg debugtemplate '{count(0)}'
596 hg: parse error: not countable
607 hg: parse error: not countable
597 (incompatible use of template filter 'count')
608 (incompatible use of template filter 'count')
598 [255]
609 [255]
599 $ hg debugtemplate '{if(true, "", count(0))}'
610 $ hg debugtemplate '{if(true, "", count(0))}'
600 $ hg debugtemplate '{if(false, count(0), "")}'
611 $ hg debugtemplate '{if(false, count(0), "")}'
601 $ hg debugtemplate '{ifcontains("a", "aa", "", count(0))}'
612 $ hg debugtemplate '{ifcontains("a", "aa", "", count(0))}'
602 $ hg debugtemplate '{ifcontains("a", "bb", count(0), "")}'
613 $ hg debugtemplate '{ifcontains("a", "bb", count(0), "")}'
603 $ hg debugtemplate '{ifeq(0, 0, "", count(0))}'
614 $ hg debugtemplate '{ifeq(0, 0, "", count(0))}'
604 $ hg debugtemplate '{ifeq(0, 1, count(0), "")}'
615 $ hg debugtemplate '{ifeq(0, 1, count(0), "")}'
605
616
606 Test search() function:
617 Test search() function:
607
618
608 $ hg log -R a -r2 -T '{desc}\n'
619 $ hg log -R a -r2 -T '{desc}\n'
609 no person
620 no person
610
621
611 $ hg log -R a -r2 -T '{search(r"p.*", desc)}\n'
622 $ hg log -R a -r2 -T '{search(r"p.*", desc)}\n'
612 person
623 person
613
624
614 as bool
625 as bool
615
626
616 $ hg log -R a -r2 -T '{if(search(r"p.*", desc), "", "not ")}found\n'
627 $ hg log -R a -r2 -T '{if(search(r"p.*", desc), "", "not ")}found\n'
617 found
628 found
618 $ hg log -R a -r2 -T '{if(search(r"q", desc), "", "not ")}found\n'
629 $ hg log -R a -r2 -T '{if(search(r"q", desc), "", "not ")}found\n'
619 not found
630 not found
620
631
621 match as json
632 match as json
622
633
623 $ hg log -R a -r2 -T '{search(r"(no) p.*", desc)|json}\n'
634 $ hg log -R a -r2 -T '{search(r"(no) p.*", desc)|json}\n'
624 {"0": "no person", "1": "no"}
635 {"0": "no person", "1": "no"}
625 $ hg log -R a -r2 -T '{search(r"q", desc)|json}\n'
636 $ hg log -R a -r2 -T '{search(r"q", desc)|json}\n'
626 null
637 null
627
638
628 group reference
639 group reference
629
640
630 $ hg log -R a -r2 -T '{search(r"(no) (p.*)", desc) % "{1|upper} {2|hex}"}\n'
641 $ hg log -R a -r2 -T '{search(r"(no) (p.*)", desc) % "{1|upper} {2|hex}"}\n'
631 NO 706572736f6e
642 NO 706572736f6e
632 $ hg log -R a -r2 -T '{search(r"(?P<foo>[a-z]*)", desc) % "{foo}"}\n'
643 $ hg log -R a -r2 -T '{search(r"(?P<foo>[a-z]*)", desc) % "{foo}"}\n'
633 no
644 no
634 $ hg log -R a -r2 -T '{search(r"(?P<foo>[a-z]*)", desc).foo}\n'
645 $ hg log -R a -r2 -T '{search(r"(?P<foo>[a-z]*)", desc).foo}\n'
635 no
646 no
636
647
637 group reference with no match
648 group reference with no match
638
649
639 $ hg log -R a -r2 -T '{search(r"q", desc) % "match: {0}"}\n'
650 $ hg log -R a -r2 -T '{search(r"q", desc) % "match: {0}"}\n'
640
651
641
652
642 bad group names
653 bad group names
643
654
644 $ hg log -R a -r2 -T '{search(r"(?P<0>.)", desc) % "{0}"}\n'
655 $ hg log -R a -r2 -T '{search(r"(?P<0>.)", desc) % "{0}"}\n'
645 hg: parse error: search got an invalid pattern: (?P<0>.)
656 hg: parse error: search got an invalid pattern: (?P<0>.)
646 [255]
657 [255]
647 $ hg log -R a -r2 -T '{search(r"(?P<repo>.)", desc) % "{repo}"}\n'
658 $ hg log -R a -r2 -T '{search(r"(?P<repo>.)", desc) % "{repo}"}\n'
648 hg: parse error: invalid group 'repo' in search pattern: (?P<repo>.)
659 hg: parse error: invalid group 'repo' in search pattern: (?P<repo>.)
649 [255]
660 [255]
650
661
651 Test the sub function of templating for expansion:
662 Test the sub function of templating for expansion:
652
663
653 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
664 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
654 xx
665 xx
655
666
656 $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
667 $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
657 hg: parse error: sub got an invalid pattern: [
668 hg: parse error: sub got an invalid pattern: [
658 [255]
669 [255]
659 $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
670 $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
660 hg: parse error: sub got an invalid replacement: \1
671 hg: parse error: sub got an invalid replacement: \1
661 [255]
672 [255]
662
673
663 Test the strip function with chars specified:
674 Test the strip function with chars specified:
664
675
665 $ hg log -R latesttag --template '{desc}\n'
676 $ hg log -R latesttag --template '{desc}\n'
666 at3
677 at3
667 t5
678 t5
668 t4
679 t4
669 t3
680 t3
670 t2
681 t2
671 t1
682 t1
672 merge
683 merge
673 h2e
684 h2e
674 h2d
685 h2d
675 h1c
686 h1c
676 b
687 b
677 a
688 a
678
689
679 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
690 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
680 at3
691 at3
681 5
692 5
682 4
693 4
683 3
694 3
684 2
695 2
685 1
696 1
686 merg
697 merg
687 h2
698 h2
688 h2d
699 h2d
689 h1c
700 h1c
690 b
701 b
691 a
702 a
692
703
693 Test date format:
704 Test date format:
694
705
695 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
706 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
696 date: 70 01 01 10 +0000
707 date: 70 01 01 10 +0000
697 date: 70 01 01 09 +0000
708 date: 70 01 01 09 +0000
698 date: 70 01 01 04 +0000
709 date: 70 01 01 04 +0000
699 date: 70 01 01 08 +0000
710 date: 70 01 01 08 +0000
700 date: 70 01 01 07 +0000
711 date: 70 01 01 07 +0000
701 date: 70 01 01 06 +0000
712 date: 70 01 01 06 +0000
702 date: 70 01 01 05 +0100
713 date: 70 01 01 05 +0100
703 date: 70 01 01 04 +0000
714 date: 70 01 01 04 +0000
704 date: 70 01 01 03 +0000
715 date: 70 01 01 03 +0000
705 date: 70 01 01 02 +0000
716 date: 70 01 01 02 +0000
706 date: 70 01 01 01 +0000
717 date: 70 01 01 01 +0000
707 date: 70 01 01 00 +0000
718 date: 70 01 01 00 +0000
708
719
709 Test invalid date:
720 Test invalid date:
710
721
711 $ hg log -R latesttag -T '{date(rev)}\n'
722 $ hg log -R latesttag -T '{date(rev)}\n'
712 hg: parse error: date expects a date information
723 hg: parse error: date expects a date information
713 [255]
724 [255]
714
725
715 Set up repository containing template fragments in commit metadata:
726 Set up repository containing template fragments in commit metadata:
716
727
717 $ hg init r
728 $ hg init r
718 $ cd r
729 $ cd r
719 $ echo a > a
730 $ echo a > a
720 $ hg ci -Am '{rev}'
731 $ hg ci -Am '{rev}'
721 adding a
732 adding a
722
733
723 $ hg branch -q 'text.{rev}'
734 $ hg branch -q 'text.{rev}'
724 $ echo aa >> aa
735 $ echo aa >> aa
725 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
736 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
726
737
727 color effect can be specified without quoting:
738 color effect can be specified without quoting:
728
739
729 $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
740 $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
730 \x1b[0;31mtext\x1b[0m (esc)
741 \x1b[0;31mtext\x1b[0m (esc)
731
742
732 color effects can be nested (issue5413)
743 color effects can be nested (issue5413)
733
744
734 $ hg debugtemplate --color=always \
745 $ hg debugtemplate --color=always \
735 > '{label(red, "red{label(magenta, "ma{label(cyan, "cyan")}{label(yellow, "yellow")}genta")}")}\n'
746 > '{label(red, "red{label(magenta, "ma{label(cyan, "cyan")}{label(yellow, "yellow")}genta")}")}\n'
736 \x1b[0;31mred\x1b[0;35mma\x1b[0;36mcyan\x1b[0m\x1b[0;31m\x1b[0;35m\x1b[0;33myellow\x1b[0m\x1b[0;31m\x1b[0;35mgenta\x1b[0m (esc)
747 \x1b[0;31mred\x1b[0;35mma\x1b[0;36mcyan\x1b[0m\x1b[0;31m\x1b[0;35m\x1b[0;33myellow\x1b[0m\x1b[0;31m\x1b[0;35mgenta\x1b[0m (esc)
737
748
738 pad() should interact well with color codes (issue5416)
749 pad() should interact well with color codes (issue5416)
739
750
740 $ hg debugtemplate --color=always \
751 $ hg debugtemplate --color=always \
741 > '{pad(label(red, "red"), 5, label(cyan, "-"))}\n'
752 > '{pad(label(red, "red"), 5, label(cyan, "-"))}\n'
742 \x1b[0;31mred\x1b[0m\x1b[0;36m-\x1b[0m\x1b[0;36m-\x1b[0m (esc)
753 \x1b[0;31mred\x1b[0m\x1b[0;36m-\x1b[0m\x1b[0;36m-\x1b[0m (esc)
743
754
744 pad() with truncate has to strip color codes, though
755 pad() with truncate has to strip color codes, though
745
756
746 $ hg debugtemplate --color=always \
757 $ hg debugtemplate --color=always \
747 > '{pad(label(red, "scarlet"), 5, truncate=true)}\n'
758 > '{pad(label(red, "scarlet"), 5, truncate=true)}\n'
748 scarl
759 scarl
749
760
750 label should be no-op if color is disabled:
761 label should be no-op if color is disabled:
751
762
752 $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
763 $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
753 text
764 text
754 $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
765 $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
755 text
766 text
756
767
757 Test branches inside if statement:
768 Test branches inside if statement:
758
769
759 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
770 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
760 no
771 no
761
772
762 Test dict constructor:
773 Test dict constructor:
763
774
764 $ hg log -r 0 -T '{dict(y=node|short, x=rev)}\n'
775 $ hg log -r 0 -T '{dict(y=node|short, x=rev)}\n'
765 y=f7769ec2ab97 x=0
776 y=f7769ec2ab97 x=0
766 $ hg log -r 0 -T '{dict(x=rev, y=node|short) % "{key}={value}\n"}'
777 $ hg log -r 0 -T '{dict(x=rev, y=node|short) % "{key}={value}\n"}'
767 x=0
778 x=0
768 y=f7769ec2ab97
779 y=f7769ec2ab97
769 $ hg log -r 0 -T '{dict(x=rev, y=node|short)|json}\n'
780 $ hg log -r 0 -T '{dict(x=rev, y=node|short)|json}\n'
770 {"x": 0, "y": "f7769ec2ab97"}
781 {"x": 0, "y": "f7769ec2ab97"}
771 $ hg log -r 0 -T '{dict()|json}\n'
782 $ hg log -r 0 -T '{dict()|json}\n'
772 {}
783 {}
773
784
774 $ hg log -r 0 -T '{dict(rev, node=node|short)}\n'
785 $ hg log -r 0 -T '{dict(rev, node=node|short)}\n'
775 rev=0 node=f7769ec2ab97
786 rev=0 node=f7769ec2ab97
776 $ hg log -r 0 -T '{dict(rev, node|short)}\n'
787 $ hg log -r 0 -T '{dict(rev, node|short)}\n'
777 rev=0 node=f7769ec2ab97
788 rev=0 node=f7769ec2ab97
778
789
779 $ hg log -r 0 -T '{dict(rev, rev=rev)}\n'
790 $ hg log -r 0 -T '{dict(rev, rev=rev)}\n'
780 hg: parse error: duplicated dict key 'rev' inferred
791 hg: parse error: duplicated dict key 'rev' inferred
781 [255]
792 [255]
782 $ hg log -r 0 -T '{dict(node, node|short)}\n'
793 $ hg log -r 0 -T '{dict(node, node|short)}\n'
783 hg: parse error: duplicated dict key 'node' inferred
794 hg: parse error: duplicated dict key 'node' inferred
784 [255]
795 [255]
785 $ hg log -r 0 -T '{dict(1 + 2)}'
796 $ hg log -r 0 -T '{dict(1 + 2)}'
786 hg: parse error: dict key cannot be inferred
797 hg: parse error: dict key cannot be inferred
787 [255]
798 [255]
788
799
789 $ hg log -r 0 -T '{dict(x=rev, x=node)}'
800 $ hg log -r 0 -T '{dict(x=rev, x=node)}'
790 hg: parse error: dict got multiple values for keyword argument 'x'
801 hg: parse error: dict got multiple values for keyword argument 'x'
791 [255]
802 [255]
792
803
793 Test get function:
804 Test get function:
794
805
795 $ hg log -r 0 --template '{get(extras, "branch")}\n'
806 $ hg log -r 0 --template '{get(extras, "branch")}\n'
796 default
807 default
797 $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
808 $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
798 default
809 default
799 $ hg log -r 0 --template '{get(files, "should_fail")}\n'
810 $ hg log -r 0 --template '{get(files, "should_fail")}\n'
800 hg: parse error: not a dictionary
811 hg: parse error: not a dictionary
801 (get() expects a dict as first argument)
812 (get() expects a dict as first argument)
802 [255]
813 [255]
803
814
804 Test json filter applied to wrapped object:
815 Test json filter applied to wrapped object:
805
816
806 $ hg log -r0 -T '{files|json}\n'
817 $ hg log -r0 -T '{files|json}\n'
807 ["a"]
818 ["a"]
808 $ hg log -r0 -T '{extras|json}\n'
819 $ hg log -r0 -T '{extras|json}\n'
809 {"branch": "default"}
820 {"branch": "default"}
810 $ hg log -r0 -T '{date|json}\n'
821 $ hg log -r0 -T '{date|json}\n'
811 [0, 0]
822 [0, 0]
812
823
813 Test json filter applied to map result:
824 Test json filter applied to map result:
814
825
815 $ hg log -r0 -T '{json(extras % "{key}")}\n'
826 $ hg log -r0 -T '{json(extras % "{key}")}\n'
816 ["branch"]
827 ["branch"]
817
828
818 Test localdate(date, tz) function:
829 Test localdate(date, tz) function:
819
830
820 $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
831 $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
821 1970-01-01 09:00 +0900
832 1970-01-01 09:00 +0900
822 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
833 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
823 1970-01-01 00:00 +0000
834 1970-01-01 00:00 +0000
824 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
835 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
825 hg: parse error: localdate expects a timezone
836 hg: parse error: localdate expects a timezone
826 [255]
837 [255]
827 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
838 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
828 1970-01-01 02:00 +0200
839 1970-01-01 02:00 +0200
829 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
840 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
830 1970-01-01 00:00 +0000
841 1970-01-01 00:00 +0000
831 $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
842 $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
832 1970-01-01 00:00 +0000
843 1970-01-01 00:00 +0000
833 $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
844 $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
834 hg: parse error: localdate expects a timezone
845 hg: parse error: localdate expects a timezone
835 [255]
846 [255]
836 $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
847 $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
837 hg: parse error: localdate expects a timezone
848 hg: parse error: localdate expects a timezone
838 [255]
849 [255]
839
850
840 Test shortest(node) function:
851 Test shortest(node) function:
841
852
842 $ echo b > b
853 $ echo b > b
843 $ hg ci -qAm b
854 $ hg ci -qAm b
844 $ hg log --template '{shortest(node)}\n'
855 $ hg log --template '{shortest(node)}\n'
845 e777
856 e777
846 bcc7
857 bcc7
847 f776
858 f776
848 $ hg log --template '{shortest(node, 10)}\n'
859 $ hg log --template '{shortest(node, 10)}\n'
849 e777603221
860 e777603221
850 bcc7ff960b
861 bcc7ff960b
851 f7769ec2ab
862 f7769ec2ab
852 $ hg log --template '{shortest(node, 1)}\n' -r null
863 $ hg log --template '{shortest(node, 1)}\n' -r null
853 00
864 00
854 $ hg log --template '{node|shortest}\n' -l1
865 $ hg log --template '{node|shortest}\n' -l1
855 e777
866 e777
856
867
857 $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
868 $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
858 f7769ec2ab
869 f7769ec2ab
859 $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
870 $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
860 hg: parse error: shortest() expects an integer minlength
871 hg: parse error: shortest() expects an integer minlength
861 [255]
872 [255]
862
873
863 $ hg log -r 'wdir()' -T '{node|shortest}\n'
874 $ hg log -r 'wdir()' -T '{node|shortest}\n'
864 ffff
875 ffff
865
876
866 $ hg log --template '{shortest("f")}\n' -l1
877 $ hg log --template '{shortest("f")}\n' -l1
867 f
878 f
868
879
869 $ hg log --template '{shortest("0123456789012345678901234567890123456789")}\n' -l1
880 $ hg log --template '{shortest("0123456789012345678901234567890123456789")}\n' -l1
870 0123456789012345678901234567890123456789
881 0123456789012345678901234567890123456789
871
882
872 $ hg log --template '{shortest("01234567890123456789012345678901234567890123456789")}\n' -l1
883 $ hg log --template '{shortest("01234567890123456789012345678901234567890123456789")}\n' -l1
873 01234567890123456789012345678901234567890123456789
884 01234567890123456789012345678901234567890123456789
874
885
875 $ hg log --template '{shortest("not a hex string")}\n' -l1
886 $ hg log --template '{shortest("not a hex string")}\n' -l1
876 not a hex string
887 not a hex string
877
888
878 $ hg log --template '{shortest("not a hex string, but it'\''s 40 bytes long")}\n' -l1
889 $ hg log --template '{shortest("not a hex string, but it'\''s 40 bytes long")}\n' -l1
879 not a hex string, but it's 40 bytes long
890 not a hex string, but it's 40 bytes long
880
891
881 $ hg log --template '{shortest("ffffffffffffffffffffffffffffffffffffffff")}\n' -l1
892 $ hg log --template '{shortest("ffffffffffffffffffffffffffffffffffffffff")}\n' -l1
882 ffff
893 ffff
883
894
884 $ hg log --template '{shortest("fffffff")}\n' -l1
895 $ hg log --template '{shortest("fffffff")}\n' -l1
885 ffff
896 ffff
886
897
887 $ hg log --template '{shortest("ff")}\n' -l1
898 $ hg log --template '{shortest("ff")}\n' -l1
888 ffff
899 ffff
889
900
890 $ cd ..
901 $ cd ..
891
902
892 Test shortest(node) with the repo having short hash collision:
903 Test shortest(node) with the repo having short hash collision:
893
904
894 $ hg init hashcollision
905 $ hg init hashcollision
895 $ cd hashcollision
906 $ cd hashcollision
896 $ cat <<EOF >> .hg/hgrc
907 $ cat <<EOF >> .hg/hgrc
897 > [experimental]
908 > [experimental]
898 > evolution.createmarkers=True
909 > evolution.createmarkers=True
899 > EOF
910 > EOF
900 $ echo 0 > a
911 $ echo 0 > a
901 $ hg ci -qAm 0
912 $ hg ci -qAm 0
902 $ for i in 17 129 248 242 480 580 617 1057 2857 4025; do
913 $ for i in 17 129 248 242 480 580 617 1057 2857 4025; do
903 > hg up -q 0
914 > hg up -q 0
904 > echo $i > a
915 > echo $i > a
905 > hg ci -qm $i
916 > hg ci -qm $i
906 > done
917 > done
907 $ hg up -q null
918 $ hg up -q null
908 $ hg log -r0: -T '{rev}:{node}\n'
919 $ hg log -r0: -T '{rev}:{node}\n'
909 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
920 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
910 1:11424df6dc1dd4ea255eae2b58eaca7831973bbc
921 1:11424df6dc1dd4ea255eae2b58eaca7831973bbc
911 2:11407b3f1b9c3e76a79c1ec5373924df096f0499
922 2:11407b3f1b9c3e76a79c1ec5373924df096f0499
912 3:11dd92fe0f39dfdaacdaa5f3997edc533875cfc4
923 3:11dd92fe0f39dfdaacdaa5f3997edc533875cfc4
913 4:10776689e627b465361ad5c296a20a487e153ca4
924 4:10776689e627b465361ad5c296a20a487e153ca4
914 5:a00be79088084cb3aff086ab799f8790e01a976b
925 5:a00be79088084cb3aff086ab799f8790e01a976b
915 6:a0b0acd79b4498d0052993d35a6a748dd51d13e6
926 6:a0b0acd79b4498d0052993d35a6a748dd51d13e6
916 7:a0457b3450b8e1b778f1163b31a435802987fe5d
927 7:a0457b3450b8e1b778f1163b31a435802987fe5d
917 8:c56256a09cd28e5764f32e8e2810d0f01e2e357a
928 8:c56256a09cd28e5764f32e8e2810d0f01e2e357a
918 9:c5623987d205cd6d9d8389bfc40fff9dbb670b48
929 9:c5623987d205cd6d9d8389bfc40fff9dbb670b48
919 10:c562ddd9c94164376c20b86b0b4991636a3bf84f
930 10:c562ddd9c94164376c20b86b0b4991636a3bf84f
920 $ hg debugobsolete a00be79088084cb3aff086ab799f8790e01a976b
931 $ hg debugobsolete a00be79088084cb3aff086ab799f8790e01a976b
921 1 new obsolescence markers
932 1 new obsolescence markers
922 obsoleted 1 changesets
933 obsoleted 1 changesets
923 $ hg debugobsolete c5623987d205cd6d9d8389bfc40fff9dbb670b48
934 $ hg debugobsolete c5623987d205cd6d9d8389bfc40fff9dbb670b48
924 1 new obsolescence markers
935 1 new obsolescence markers
925 obsoleted 1 changesets
936 obsoleted 1 changesets
926 $ hg debugobsolete c562ddd9c94164376c20b86b0b4991636a3bf84f
937 $ hg debugobsolete c562ddd9c94164376c20b86b0b4991636a3bf84f
927 1 new obsolescence markers
938 1 new obsolescence markers
928 obsoleted 1 changesets
939 obsoleted 1 changesets
929
940
930 nodes starting with '11' (we don't have the revision number '11' though)
941 nodes starting with '11' (we don't have the revision number '11' though)
931
942
932 $ hg log -r 1:3 -T '{rev}:{shortest(node, 0)}\n'
943 $ hg log -r 1:3 -T '{rev}:{shortest(node, 0)}\n'
933 1:1142
944 1:1142
934 2:1140
945 2:1140
935 3:11d
946 3:11d
936
947
937 '5:a00' is hidden, but still we have two nodes starting with 'a0'
948 '5:a00' is hidden, but still we have two nodes starting with 'a0'
938
949
939 $ hg log -r 6:7 -T '{rev}:{shortest(node, 0)}\n'
950 $ hg log -r 6:7 -T '{rev}:{shortest(node, 0)}\n'
940 6:a0b
951 6:a0b
941 7:a04
952 7:a04
942
953
943 node '10' conflicts with the revision number '10' even if it is hidden
954 node '10' conflicts with the revision number '10' even if it is hidden
944 (we could exclude hidden revision numbers, but currently we don't)
955 (we could exclude hidden revision numbers, but currently we don't)
945
956
946 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n'
957 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n'
947 4:107
958 4:107
948 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
959 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
949 4:107
960 4:107
950
961
951 $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n'
962 $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n'
952 4:x10
963 4:x10
953 $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
964 $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
954 4:x10
965 4:x10
955
966
956 node 'c562' should be unique if the other 'c562' nodes are hidden
967 node 'c562' should be unique if the other 'c562' nodes are hidden
957 (but we don't try the slow path to filter out hidden nodes for now)
968 (but we don't try the slow path to filter out hidden nodes for now)
958
969
959 $ hg log -r 8 -T '{rev}:{node|shortest}\n'
970 $ hg log -r 8 -T '{rev}:{node|shortest}\n'
960 8:c5625
971 8:c5625
961 $ hg log -r 8:10 -T '{rev}:{node|shortest}\n' --hidden
972 $ hg log -r 8:10 -T '{rev}:{node|shortest}\n' --hidden
962 8:c5625
973 8:c5625
963 9:c5623
974 9:c5623
964 10:c562d
975 10:c562d
965
976
966 $ cd ..
977 $ cd ..
967
978
968 Test prefixhexnode when the first character of the hash is 0.
979 Test prefixhexnode when the first character of the hash is 0.
969 $ hg init hashcollision2
980 $ hg init hashcollision2
970 $ cd hashcollision2
981 $ cd hashcollision2
971 $ cat <<EOF >> .hg/hgrc
982 $ cat <<EOF >> .hg/hgrc
972 > [experimental]
983 > [experimental]
973 > evolution.createmarkers=True
984 > evolution.createmarkers=True
974 > EOF
985 > EOF
975 $ echo 0 > a
986 $ echo 0 > a
976 $ hg ci -qAm 0
987 $ hg ci -qAm 0
977 $ echo 21 > a
988 $ echo 21 > a
978 $ hg ci -qm 21
989 $ hg ci -qm 21
979 $ hg up -q null
990 $ hg up -q null
980 $ hg log -r0: -T '{rev}:{node}\n'
991 $ hg log -r0: -T '{rev}:{node}\n'
981 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
992 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
982 1:0cf177ba2b1dc3862a00fb81715fec90950201be
993 1:0cf177ba2b1dc3862a00fb81715fec90950201be
983
994
984 we need the 'x' prefix to ensure we aren't colliding with rev0. We identify
995 we need the 'x' prefix to ensure we aren't colliding with rev0. We identify
985 the collision with nullid if we aren't using disambiguatewithin, so we need to set
996 the collision with nullid if we aren't using disambiguatewithin, so we need to set
986 that as well.
997 that as well.
987 $ hg --config experimental.revisions.disambiguatewithin='descendants(0)' \
998 $ hg --config experimental.revisions.disambiguatewithin='descendants(0)' \
988 > --config experimental.revisions.prefixhexnode=yes \
999 > --config experimental.revisions.prefixhexnode=yes \
989 > log -r 1 -T '{rev}:{shortest(node, 0)}\n'
1000 > log -r 1 -T '{rev}:{shortest(node, 0)}\n'
990 1:x0
1001 1:x0
991
1002
992 $ hg debugobsolete 0cf177ba2b1dc3862a00fb81715fec90950201be
1003 $ hg debugobsolete 0cf177ba2b1dc3862a00fb81715fec90950201be
993 1 new obsolescence markers
1004 1 new obsolescence markers
994 obsoleted 1 changesets
1005 obsoleted 1 changesets
995 $ hg up -q 0
1006 $ hg up -q 0
996 $ echo 61 > a
1007 $ echo 61 > a
997 $ hg ci -m 61
1008 $ hg ci -m 61
998 $ hg log -r0: -T '{rev}:{node}\n'
1009 $ hg log -r0: -T '{rev}:{node}\n'
999 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
1010 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
1000 2:01384dde84b3a511ae0835f35ac40bd806c99bb8
1011 2:01384dde84b3a511ae0835f35ac40bd806c99bb8
1001
1012
1002 we still have the 'x' prefix because '0' is still the shortest prefix, since
1013 we still have the 'x' prefix because '0' is still the shortest prefix, since
1003 rev1's '0c' is hidden.
1014 rev1's '0c' is hidden.
1004 $ hg --config experimental.revisions.disambiguatewithin=0:-1-0 \
1015 $ hg --config experimental.revisions.disambiguatewithin=0:-1-0 \
1005 > --config experimental.revisions.prefixhexnode=yes \
1016 > --config experimental.revisions.prefixhexnode=yes \
1006 > log -r 0:-1-0 -T '{rev}:{shortest(node, 0)}\n'
1017 > log -r 0:-1-0 -T '{rev}:{shortest(node, 0)}\n'
1007 2:x0
1018 2:x0
1008
1019
1009 we don't have the 'x' prefix on 2 because '01' is not a synonym for rev1.
1020 we don't have the 'x' prefix on 2 because '01' is not a synonym for rev1.
1010 $ hg --config experimental.revisions.disambiguatewithin=0:-1-0 \
1021 $ hg --config experimental.revisions.disambiguatewithin=0:-1-0 \
1011 > --config experimental.revisions.prefixhexnode=yes \
1022 > --config experimental.revisions.prefixhexnode=yes \
1012 > log -r 0:-1-0 -T '{rev}:{shortest(node, 0)}\n' --hidden
1023 > log -r 0:-1-0 -T '{rev}:{shortest(node, 0)}\n' --hidden
1013 1:0c
1024 1:0c
1014 2:01
1025 2:01
1015
1026
1016 $ cd ..
1027 $ cd ..
1017
1028
1018 Test pad function
1029 Test pad function
1019
1030
1020 $ cd r
1031 $ cd r
1021
1032
1022 $ hg log --template '{pad(rev, 20)} {author|user}\n'
1033 $ hg log --template '{pad(rev, 20)} {author|user}\n'
1023 2 test
1034 2 test
1024 1 {node|short}
1035 1 {node|short}
1025 0 test
1036 0 test
1026
1037
1027 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
1038 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
1028 2 test
1039 2 test
1029 1 {node|short}
1040 1 {node|short}
1030 0 test
1041 0 test
1031
1042
1032 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
1043 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
1033 2------------------- test
1044 2------------------- test
1034 1------------------- {node|short}
1045 1------------------- {node|short}
1035 0------------------- test
1046 0------------------- test
1036
1047
1037 $ hg log --template '{pad(author, 5, "-", False, True)}\n'
1048 $ hg log --template '{pad(author, 5, "-", False, True)}\n'
1038 test-
1049 test-
1039 {node
1050 {node
1040 test-
1051 test-
1041 $ hg log --template '{pad(author, 5, "-", True, True)}\n'
1052 $ hg log --template '{pad(author, 5, "-", True, True)}\n'
1042 -test
1053 -test
1043 hort}
1054 hort}
1044 -test
1055 -test
1045
1056
1046 Test template string in pad function
1057 Test template string in pad function
1047
1058
1048 $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
1059 $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
1049 {0} test
1060 {0} test
1050
1061
1051 $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
1062 $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
1052 \{rev} test
1063 \{rev} test
1053
1064
1054 Test width argument passed to pad function
1065 Test width argument passed to pad function
1055
1066
1056 $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
1067 $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
1057 0 test
1068 0 test
1058 $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
1069 $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
1059 hg: parse error: pad() expects an integer width
1070 hg: parse error: pad() expects an integer width
1060 [255]
1071 [255]
1061
1072
1062 Test invalid fillchar passed to pad function
1073 Test invalid fillchar passed to pad function
1063
1074
1064 $ hg log -r 0 -T '{pad(rev, 10, "")}\n'
1075 $ hg log -r 0 -T '{pad(rev, 10, "")}\n'
1065 hg: parse error: pad() expects a single fill character
1076 hg: parse error: pad() expects a single fill character
1066 [255]
1077 [255]
1067 $ hg log -r 0 -T '{pad(rev, 10, "--")}\n'
1078 $ hg log -r 0 -T '{pad(rev, 10, "--")}\n'
1068 hg: parse error: pad() expects a single fill character
1079 hg: parse error: pad() expects a single fill character
1069 [255]
1080 [255]
1070
1081
1071 Test boolean argument passed to pad function
1082 Test boolean argument passed to pad function
1072
1083
1073 no crash
1084 no crash
1074
1085
1075 $ hg log -r 0 -T '{pad(rev, 10, "-", "f{"oo"}")}\n'
1086 $ hg log -r 0 -T '{pad(rev, 10, "-", "f{"oo"}")}\n'
1076 ---------0
1087 ---------0
1077
1088
1078 string/literal
1089 string/literal
1079
1090
1080 $ hg log -r 0 -T '{pad(rev, 10, "-", "false")}\n'
1091 $ hg log -r 0 -T '{pad(rev, 10, "-", "false")}\n'
1081 ---------0
1092 ---------0
1082 $ hg log -r 0 -T '{pad(rev, 10, "-", false)}\n'
1093 $ hg log -r 0 -T '{pad(rev, 10, "-", false)}\n'
1083 0---------
1094 0---------
1084 $ hg log -r 0 -T '{pad(rev, 10, "-", "")}\n'
1095 $ hg log -r 0 -T '{pad(rev, 10, "-", "")}\n'
1085 0---------
1096 0---------
1086
1097
1087 unknown keyword is evaluated to ''
1098 unknown keyword is evaluated to ''
1088
1099
1089 $ hg log -r 0 -T '{pad(rev, 10, "-", unknownkeyword)}\n'
1100 $ hg log -r 0 -T '{pad(rev, 10, "-", unknownkeyword)}\n'
1090 0---------
1101 0---------
1091
1102
1092 Test separate function
1103 Test separate function
1093
1104
1094 $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
1105 $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
1095 a-b-c
1106 a-b-c
1096 $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
1107 $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
1097 0:f7769ec2ab97 test default
1108 0:f7769ec2ab97 test default
1098 $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
1109 $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
1099 a \x1b[0;31mb\x1b[0m c d (esc)
1110 a \x1b[0;31mb\x1b[0m c d (esc)
1100
1111
1101 Test boolean expression/literal passed to if function
1112 Test boolean expression/literal passed to if function
1102
1113
1103 $ hg log -r 0 -T '{if(rev, "rev 0 is True")}\n'
1114 $ hg log -r 0 -T '{if(rev, "rev 0 is True")}\n'
1104 rev 0 is True
1115 rev 0 is True
1105 $ hg log -r 0 -T '{if(0, "literal 0 is True as well")}\n'
1116 $ hg log -r 0 -T '{if(0, "literal 0 is True as well")}\n'
1106 literal 0 is True as well
1117 literal 0 is True as well
1107 $ hg log -r 0 -T '{if(min(revset(r"0")), "0 of hybriditem is also True")}\n'
1118 $ hg log -r 0 -T '{if(min(revset(r"0")), "0 of hybriditem is also True")}\n'
1108 0 of hybriditem is also True
1119 0 of hybriditem is also True
1109 $ hg log -r 0 -T '{if("", "", "empty string is False")}\n'
1120 $ hg log -r 0 -T '{if("", "", "empty string is False")}\n'
1110 empty string is False
1121 empty string is False
1111 $ hg log -r 0 -T '{if(revset(r"0 - 0"), "", "empty list is False")}\n'
1122 $ hg log -r 0 -T '{if(revset(r"0 - 0"), "", "empty list is False")}\n'
1112 empty list is False
1123 empty list is False
1113 $ hg log -r 0 -T '{if(revset(r"0"), "non-empty list is True")}\n'
1124 $ hg log -r 0 -T '{if(revset(r"0"), "non-empty list is True")}\n'
1114 non-empty list is True
1125 non-empty list is True
1115 $ hg log -r 0 -T '{if(revset(r"0") % "", "list of empty strings is True")}\n'
1126 $ hg log -r 0 -T '{if(revset(r"0") % "", "list of empty strings is True")}\n'
1116 list of empty strings is True
1127 list of empty strings is True
1117 $ hg log -r 0 -T '{if(true, "true is True")}\n'
1128 $ hg log -r 0 -T '{if(true, "true is True")}\n'
1118 true is True
1129 true is True
1119 $ hg log -r 0 -T '{if(false, "", "false is False")}\n'
1130 $ hg log -r 0 -T '{if(false, "", "false is False")}\n'
1120 false is False
1131 false is False
1121 $ hg log -r 0 -T '{if("false", "non-empty string is True")}\n'
1132 $ hg log -r 0 -T '{if("false", "non-empty string is True")}\n'
1122 non-empty string is True
1133 non-empty string is True
1123
1134
1124 Test ifcontains function
1135 Test ifcontains function
1125
1136
1126 $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
1137 $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
1127 2 is in the string
1138 2 is in the string
1128 1 is not
1139 1 is not
1129 0 is in the string
1140 0 is in the string
1130
1141
1131 $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
1142 $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
1132 2 is in the string
1143 2 is in the string
1133 1 is not
1144 1 is not
1134 0 is in the string
1145 0 is in the string
1135
1146
1136 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
1147 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
1137 2 did not add a
1148 2 did not add a
1138 1 did not add a
1149 1 did not add a
1139 0 added a
1150 0 added a
1140
1151
1141 $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
1152 $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
1142 2 is parent of 1
1153 2 is parent of 1
1143 1
1154 1
1144 0
1155 0
1145
1156
1146 $ hg log -l1 -T '{ifcontains("branch", extras, "t", "f")}\n'
1157 $ hg log -l1 -T '{ifcontains("branch", extras, "t", "f")}\n'
1147 t
1158 t
1148 $ hg log -l1 -T '{ifcontains("branch", extras % "{key}", "t", "f")}\n'
1159 $ hg log -l1 -T '{ifcontains("branch", extras % "{key}", "t", "f")}\n'
1149 t
1160 t
1150 $ hg log -l1 -T '{ifcontains("branc", extras % "{key}", "t", "f")}\n'
1161 $ hg log -l1 -T '{ifcontains("branc", extras % "{key}", "t", "f")}\n'
1151 f
1162 f
1152 $ hg log -l1 -T '{ifcontains("branc", stringify(extras % "{key}"), "t", "f")}\n'
1163 $ hg log -l1 -T '{ifcontains("branc", stringify(extras % "{key}"), "t", "f")}\n'
1153 t
1164 t
1154
1165
1155 Test revset function
1166 Test revset function
1156
1167
1157 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
1168 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
1158 2 current rev
1169 2 current rev
1159 1 not current rev
1170 1 not current rev
1160 0 not current rev
1171 0 not current rev
1161
1172
1162 $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
1173 $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
1163 2 match rev
1174 2 match rev
1164 1 match rev
1175 1 match rev
1165 0 not match rev
1176 0 not match rev
1166
1177
1167 $ hg log -T '{ifcontains(desc, revset(":"), "", "type not match")}\n' -l1
1178 $ hg log -T '{ifcontains(desc, revset(":"), "", "type not match")}\n' -l1
1168 type not match
1179 type not match
1169
1180
1170 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
1181 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
1171 2 Parents: 1
1182 2 Parents: 1
1172 1 Parents: 0
1183 1 Parents: 0
1173 0 Parents:
1184 0 Parents:
1174
1185
1175 $ cat >> .hg/hgrc <<EOF
1186 $ cat >> .hg/hgrc <<EOF
1176 > [revsetalias]
1187 > [revsetalias]
1177 > myparents(\$1) = parents(\$1)
1188 > myparents(\$1) = parents(\$1)
1178 > EOF
1189 > EOF
1179 $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
1190 $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
1180 2 Parents: 1
1191 2 Parents: 1
1181 1 Parents: 0
1192 1 Parents: 0
1182 0 Parents:
1193 0 Parents:
1183
1194
1184 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
1195 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
1185 Rev: 2
1196 Rev: 2
1186 Ancestor: 0
1197 Ancestor: 0
1187 Ancestor: 1
1198 Ancestor: 1
1188 Ancestor: 2
1199 Ancestor: 2
1189
1200
1190 Rev: 1
1201 Rev: 1
1191 Ancestor: 0
1202 Ancestor: 0
1192 Ancestor: 1
1203 Ancestor: 1
1193
1204
1194 Rev: 0
1205 Rev: 0
1195 Ancestor: 0
1206 Ancestor: 0
1196
1207
1197 $ hg log --template '{revset("TIP"|lower)}\n' -l1
1208 $ hg log --template '{revset("TIP"|lower)}\n' -l1
1198 2
1209 2
1199
1210
1200 $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
1211 $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
1201 2
1212 2
1202
1213
1203 a list template is evaluated for each item of revset/parents
1214 a list template is evaluated for each item of revset/parents
1204
1215
1205 $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
1216 $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
1206 2 p: 1:bcc7ff960b8e
1217 2 p: 1:bcc7ff960b8e
1207 1 p: 0:f7769ec2ab97
1218 1 p: 0:f7769ec2ab97
1208 0 p:
1219 0 p:
1209
1220
1210 $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
1221 $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
1211 2 p: 1:bcc7ff960b8e -1:000000000000
1222 2 p: 1:bcc7ff960b8e -1:000000000000
1212 1 p: 0:f7769ec2ab97 -1:000000000000
1223 1 p: 0:f7769ec2ab97 -1:000000000000
1213 0 p: -1:000000000000 -1:000000000000
1224 0 p: -1:000000000000 -1:000000000000
1214
1225
1215 therefore, 'revcache' should be recreated for each rev
1226 therefore, 'revcache' should be recreated for each rev
1216
1227
1217 $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
1228 $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
1218 2 aa b
1229 2 aa b
1219 p
1230 p
1220 1
1231 1
1221 p a
1232 p a
1222 0 a
1233 0 a
1223 p
1234 p
1224
1235
1225 $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
1236 $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
1226 2 aa b
1237 2 aa b
1227 p
1238 p
1228 1
1239 1
1229 p a
1240 p a
1230 0 a
1241 0 a
1231 p
1242 p
1232
1243
1233 a revset item must be evaluated as an integer revision, not an offset from tip
1244 a revset item must be evaluated as an integer revision, not an offset from tip
1234
1245
1235 $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
1246 $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
1236 -1:000000000000
1247 -1:000000000000
1237 $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
1248 $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
1238 -1:000000000000
1249 -1:000000000000
1239
1250
1240 join() should pick '{rev}' from revset items:
1251 join() should pick '{rev}' from revset items:
1241
1252
1242 $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
1253 $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
1243 4, 5
1254 4, 5
1244
1255
1245 on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
1256 on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
1246 default. join() should agree with the default formatting:
1257 default. join() should agree with the default formatting:
1247
1258
1248 $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
1259 $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
1249 5:13207e5a10d9, 4:bbe44766e73d
1260 5:13207e5a10d9, 4:bbe44766e73d
1250
1261
1251 $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
1262 $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
1252 5:13207e5a10d9fd28ec424934298e176197f2c67f,
1263 5:13207e5a10d9fd28ec424934298e176197f2c67f,
1253 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1264 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1254
1265
1255 Invalid arguments passed to revset()
1266 Invalid arguments passed to revset()
1256
1267
1257 $ hg log -T '{revset("%whatever", 0)}\n'
1268 $ hg log -T '{revset("%whatever", 0)}\n'
1258 hg: parse error: unexpected revspec format character w
1269 hg: parse error: unexpected revspec format character w
1259 [255]
1270 [255]
1260 $ hg log -T '{revset("%lwhatever", files)}\n'
1271 $ hg log -T '{revset("%lwhatever", files)}\n'
1261 hg: parse error: unexpected revspec format character w
1272 hg: parse error: unexpected revspec format character w
1262 [255]
1273 [255]
1263 $ hg log -T '{revset("%s %s", 0)}\n'
1274 $ hg log -T '{revset("%s %s", 0)}\n'
1264 hg: parse error: missing argument for revspec
1275 hg: parse error: missing argument for revspec
1265 [255]
1276 [255]
1266 $ hg log -T '{revset("", 0)}\n'
1277 $ hg log -T '{revset("", 0)}\n'
1267 hg: parse error: too many revspec arguments specified
1278 hg: parse error: too many revspec arguments specified
1268 [255]
1279 [255]
1269 $ hg log -T '{revset("%s", 0, 1)}\n'
1280 $ hg log -T '{revset("%s", 0, 1)}\n'
1270 hg: parse error: too many revspec arguments specified
1281 hg: parse error: too many revspec arguments specified
1271 [255]
1282 [255]
1272 $ hg log -T '{revset("%", 0)}\n'
1283 $ hg log -T '{revset("%", 0)}\n'
1273 hg: parse error: incomplete revspec format character
1284 hg: parse error: incomplete revspec format character
1274 [255]
1285 [255]
1275 $ hg log -T '{revset("%l", 0)}\n'
1286 $ hg log -T '{revset("%l", 0)}\n'
1276 hg: parse error: incomplete revspec format character
1287 hg: parse error: incomplete revspec format character
1277 [255]
1288 [255]
1278 $ hg log -T '{revset("%d", 'foo')}\n'
1289 $ hg log -T '{revset("%d", 'foo')}\n'
1279 hg: parse error: invalid argument for revspec
1290 hg: parse error: invalid argument for revspec
1280 [255]
1291 [255]
1281 $ hg log -T '{revset("%ld", files)}\n'
1292 $ hg log -T '{revset("%ld", files)}\n'
1282 hg: parse error: invalid argument for revspec
1293 hg: parse error: invalid argument for revspec
1283 [255]
1294 [255]
1284 $ hg log -T '{revset("%ls", 0)}\n'
1295 $ hg log -T '{revset("%ls", 0)}\n'
1285 hg: parse error: invalid argument for revspec
1296 hg: parse error: invalid argument for revspec
1286 [255]
1297 [255]
1287 $ hg log -T '{revset("%b", 'foo')}\n'
1298 $ hg log -T '{revset("%b", 'foo')}\n'
1288 hg: parse error: invalid argument for revspec
1299 hg: parse error: invalid argument for revspec
1289 [255]
1300 [255]
1290 $ hg log -T '{revset("%lb", files)}\n'
1301 $ hg log -T '{revset("%lb", files)}\n'
1291 hg: parse error: invalid argument for revspec
1302 hg: parse error: invalid argument for revspec
1292 [255]
1303 [255]
1293 $ hg log -T '{revset("%r", 0)}\n'
1304 $ hg log -T '{revset("%r", 0)}\n'
1294 hg: parse error: invalid argument for revspec
1305 hg: parse error: invalid argument for revspec
1295 [255]
1306 [255]
1296
1307
1297 Test files function
1308 Test files function
1298
1309
1299 $ hg log -T "{rev}\n{join(files('*'), '\n')}\n"
1310 $ hg log -T "{rev}\n{join(files('*'), '\n')}\n"
1300 2
1311 2
1301 a
1312 a
1302 aa
1313 aa
1303 b
1314 b
1304 1
1315 1
1305 a
1316 a
1306 0
1317 0
1307 a
1318 a
1308
1319
1309 $ hg log -T "{rev}\n{join(files('aa'), '\n')}\n"
1320 $ hg log -T "{rev}\n{join(files('aa'), '\n')}\n"
1310 2
1321 2
1311 aa
1322 aa
1312 1
1323 1
1313
1324
1314 0
1325 0
1315
1326
1316
1327
1317 $ hg log -l1 -T "{files('aa') % '{file}\n'}"
1328 $ hg log -l1 -T "{files('aa') % '{file}\n'}"
1318 aa
1329 aa
1319 $ hg log -l1 -T "{files('aa') % '{path}\n'}"
1330 $ hg log -l1 -T "{files('aa') % '{path}\n'}"
1320 aa
1331 aa
1321
1332
1322 $ hg rm a
1333 $ hg rm a
1323 $ hg log -r "wdir()" -T "{rev}\n{join(files('*'), '\n')}\n"
1334 $ hg log -r "wdir()" -T "{rev}\n{join(files('*'), '\n')}\n"
1324 2147483647
1335 2147483647
1325 aa
1336 aa
1326 b
1337 b
1327 $ hg revert a
1338 $ hg revert a
1328
1339
1329 Test relpath function
1340 Test relpath function
1330
1341
1331 $ hg log -r0 -T '{files % "{file|relpath}\n"}'
1342 $ hg log -r0 -T '{files % "{file|relpath}\n"}'
1332 a
1343 a
1333 $ cd ..
1344 $ cd ..
1334 $ hg log -R r -r0 -T '{files % "{file|relpath}\n"}'
1345 $ hg log -R r -r0 -T '{files % "{file|relpath}\n"}'
1335 r/a
1346 r/a
1336
1347
1337 Test stringify on sub expressions
1348 Test stringify on sub expressions
1338
1349
1339 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
1350 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
1340 fourth, second, third
1351 fourth, second, third
1341 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
1352 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
1342 abc
1353 abc
1343
1354
1344 Test splitlines
1355 Test splitlines
1345
1356
1346 $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
1357 $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
1347 @ foo Modify, add, remove, rename
1358 @ foo Modify, add, remove, rename
1348 |
1359 |
1349 o foo future
1360 o foo future
1350 |
1361 |
1351 o foo third
1362 o foo third
1352 |
1363 |
1353 o foo second
1364 o foo second
1354
1365
1355 o foo merge
1366 o foo merge
1356 |\
1367 |\
1357 | o foo new head
1368 | o foo new head
1358 | |
1369 | |
1359 o | foo new branch
1370 o | foo new branch
1360 |/
1371 |/
1361 o foo no user, no domain
1372 o foo no user, no domain
1362 |
1373 |
1363 o foo no person
1374 o foo no person
1364 |
1375 |
1365 o foo other 1
1376 o foo other 1
1366 | foo other 2
1377 | foo other 2
1367 | foo
1378 | foo
1368 | foo other 3
1379 | foo other 3
1369 o foo line 1
1380 o foo line 1
1370 foo line 2
1381 foo line 2
1371
1382
1372 $ hg log -R a -r0 -T '{desc|splitlines}\n'
1383 $ hg log -R a -r0 -T '{desc|splitlines}\n'
1373 line 1 line 2
1384 line 1 line 2
1374 $ hg log -R a -r0 -T '{join(desc|splitlines, "|")}\n'
1385 $ hg log -R a -r0 -T '{join(desc|splitlines, "|")}\n'
1375 line 1|line 2
1386 line 1|line 2
1376
1387
1377 Test startswith
1388 Test startswith
1378 $ hg log -Gv -R a --template "{startswith(desc)}"
1389 $ hg log -Gv -R a --template "{startswith(desc)}"
1379 hg: parse error: startswith expects two arguments
1390 hg: parse error: startswith expects two arguments
1380 [255]
1391 [255]
1381
1392
1382 $ hg log -Gv -R a --template "{startswith('line', desc)}"
1393 $ hg log -Gv -R a --template "{startswith('line', desc)}"
1383 @
1394 @
1384 |
1395 |
1385 o
1396 o
1386 |
1397 |
1387 o
1398 o
1388 |
1399 |
1389 o
1400 o
1390
1401
1391 o
1402 o
1392 |\
1403 |\
1393 | o
1404 | o
1394 | |
1405 | |
1395 o |
1406 o |
1396 |/
1407 |/
1397 o
1408 o
1398 |
1409 |
1399 o
1410 o
1400 |
1411 |
1401 o
1412 o
1402 |
1413 |
1403 o line 1
1414 o line 1
1404 line 2
1415 line 2
1405
1416
1406 Test word function (including index out of bounds graceful failure)
1417 Test word function (including index out of bounds graceful failure)
1407
1418
1408 $ hg log -Gv -R a --template "{word('1', desc)}"
1419 $ hg log -Gv -R a --template "{word('1', desc)}"
1409 @ add,
1420 @ add,
1410 |
1421 |
1411 o
1422 o
1412 |
1423 |
1413 o
1424 o
1414 |
1425 |
1415 o
1426 o
1416
1427
1417 o
1428 o
1418 |\
1429 |\
1419 | o head
1430 | o head
1420 | |
1431 | |
1421 o | branch
1432 o | branch
1422 |/
1433 |/
1423 o user,
1434 o user,
1424 |
1435 |
1425 o person
1436 o person
1426 |
1437 |
1427 o 1
1438 o 1
1428 |
1439 |
1429 o 1
1440 o 1
1430
1441
1431
1442
1432 Test word third parameter used as splitter
1443 Test word third parameter used as splitter
1433
1444
1434 $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
1445 $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
1435 @ M
1446 @ M
1436 |
1447 |
1437 o future
1448 o future
1438 |
1449 |
1439 o third
1450 o third
1440 |
1451 |
1441 o sec
1452 o sec
1442
1453
1443 o merge
1454 o merge
1444 |\
1455 |\
1445 | o new head
1456 | o new head
1446 | |
1457 | |
1447 o | new branch
1458 o | new branch
1448 |/
1459 |/
1449 o n
1460 o n
1450 |
1461 |
1451 o n
1462 o n
1452 |
1463 |
1453 o
1464 o
1454 |
1465 |
1455 o line 1
1466 o line 1
1456 line 2
1467 line 2
1457
1468
1458 Test word error messages for not enough and too many arguments
1469 Test word error messages for not enough and too many arguments
1459
1470
1460 $ hg log -Gv -R a --template "{word('0')}"
1471 $ hg log -Gv -R a --template "{word('0')}"
1461 hg: parse error: word expects two or three arguments, got 1
1472 hg: parse error: word expects two or three arguments, got 1
1462 [255]
1473 [255]
1463
1474
1464 $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
1475 $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
1465 hg: parse error: word expects two or three arguments, got 7
1476 hg: parse error: word expects two or three arguments, got 7
1466 [255]
1477 [255]
1467
1478
1468 Test word for integer literal
1479 Test word for integer literal
1469
1480
1470 $ hg log -R a --template "{word(2, desc)}\n" -r0
1481 $ hg log -R a --template "{word(2, desc)}\n" -r0
1471 line
1482 line
1472
1483
1473 Test word for invalid numbers
1484 Test word for invalid numbers
1474
1485
1475 $ hg log -Gv -R a --template "{word('a', desc)}"
1486 $ hg log -Gv -R a --template "{word('a', desc)}"
1476 hg: parse error: word expects an integer index
1487 hg: parse error: word expects an integer index
1477 [255]
1488 [255]
1478
1489
1479 Test word for out of range
1490 Test word for out of range
1480
1491
1481 $ hg log -R a --template "{word(10000, desc)}"
1492 $ hg log -R a --template "{word(10000, desc)}"
1482 $ hg log -R a --template "{word(-10000, desc)}"
1493 $ hg log -R a --template "{word(-10000, desc)}"
1483
1494
1484 Test indent and not adding to empty lines
1495 Test indent and not adding to empty lines
1485
1496
1486 $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
1497 $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
1487 -----
1498 -----
1488 > line 1
1499 > line 1
1489 >> line 2
1500 >> line 2
1490 -----
1501 -----
1491 > other 1
1502 > other 1
1492 >> other 2
1503 >> other 2
1493
1504
1494 >> other 3
1505 >> other 3
1495
1506
1496 Test with non-strings like dates
1507 Test with non-strings like dates
1497
1508
1498 $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a
1509 $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a
1499 1200000.00
1510 1200000.00
1500 1300000.00
1511 1300000.00
1501
1512
1502 Test cbor filter:
1513 Test cbor filter:
1503
1514
1504 $ cat <<'EOF' > "$TESTTMP/decodecbor.py"
1515 $ cat <<'EOF' > "$TESTTMP/decodecbor.py"
1505 > from __future__ import absolute_import
1516 > from __future__ import absolute_import
1506 > from mercurial import (
1517 > from mercurial import (
1507 > dispatch,
1518 > dispatch,
1508 > pycompat,
1519 > pycompat,
1509 > )
1520 > )
1510 > from mercurial.utils import (
1521 > from mercurial.utils import (
1511 > cborutil,
1522 > cborutil,
1512 > stringutil,
1523 > stringutil,
1513 > )
1524 > )
1514 > dispatch.initstdio()
1525 > dispatch.initstdio()
1515 > items = cborutil.decodeall(pycompat.stdin.read())
1526 > items = cborutil.decodeall(pycompat.stdin.read())
1516 > pycompat.stdout.write(stringutil.pprint(items, indent=1) + b'\n')
1527 > pycompat.stdout.write(stringutil.pprint(items, indent=1) + b'\n')
1517 > EOF
1528 > EOF
1518
1529
1519 $ hg log -T "{rev|cbor}" -R a -l2 | "$PYTHON" "$TESTTMP/decodecbor.py"
1530 $ hg log -T "{rev|cbor}" -R a -l2 | "$PYTHON" "$TESTTMP/decodecbor.py"
1520 [
1531 [
1521 10,
1532 10,
1522 9
1533 9
1523 ]
1534 ]
1524
1535
1525 $ hg log -T "{extras|cbor}" -R a -l1 | "$PYTHON" "$TESTTMP/decodecbor.py"
1536 $ hg log -T "{extras|cbor}" -R a -l1 | "$PYTHON" "$TESTTMP/decodecbor.py"
1526 [
1537 [
1527 {
1538 {
1528 'branch': 'default'
1539 'branch': 'default'
1529 }
1540 }
1530 ]
1541 ]
1531
1542
1532 json filter should escape HTML tags so that the output can be embedded in hgweb:
1543 json filter should escape HTML tags so that the output can be embedded in hgweb:
1533
1544
1534 $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
1545 $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
1535 "\u003cfoo@example.org\u003e"
1546 "\u003cfoo@example.org\u003e"
1536
1547
1537 Set up repository for non-ascii encoding tests:
1548 Set up repository for non-ascii encoding tests:
1538
1549
1539 $ hg init nonascii
1550 $ hg init nonascii
1540 $ cd nonascii
1551 $ cd nonascii
1541 $ "$PYTHON" <<EOF
1552 $ "$PYTHON" <<EOF
1542 > open('latin1', 'wb').write(b'\xe9')
1553 > open('latin1', 'wb').write(b'\xe9')
1543 > open('utf-8', 'wb').write(b'\xc3\xa9')
1554 > open('utf-8', 'wb').write(b'\xc3\xa9')
1544 > EOF
1555 > EOF
1545 $ HGENCODING=utf-8 hg branch -q `cat utf-8`
1556 $ HGENCODING=utf-8 hg branch -q `cat utf-8`
1546 $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
1557 $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
1547
1558
1548 json filter should try round-trip conversion to utf-8:
1559 json filter should try round-trip conversion to utf-8:
1549
1560
1550 $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
1561 $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
1551 "\u00e9"
1562 "\u00e9"
1552 $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
1563 $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
1553 "non-ascii branch: \u00e9"
1564 "non-ascii branch: \u00e9"
1554
1565
1555 json filter should take input as utf-8 if it was converted from utf-8:
1566 json filter should take input as utf-8 if it was converted from utf-8:
1556
1567
1557 $ HGENCODING=latin-1 hg log -T "{branch|json}\n" -r0
1568 $ HGENCODING=latin-1 hg log -T "{branch|json}\n" -r0
1558 "\u00e9"
1569 "\u00e9"
1559 $ HGENCODING=latin-1 hg log -T "{desc|json}\n" -r0
1570 $ HGENCODING=latin-1 hg log -T "{desc|json}\n" -r0
1560 "non-ascii branch: \u00e9"
1571 "non-ascii branch: \u00e9"
1561
1572
1562 json filter takes input as utf-8b:
1573 json filter takes input as utf-8b:
1563
1574
1564 $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
1575 $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
1565 "\u00e9"
1576 "\u00e9"
1566 $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
1577 $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
1567 "\udce9"
1578 "\udce9"
1568
1579
1569 cbor filter is bytes transparent, which should handle bytes subtypes
1580 cbor filter is bytes transparent, which should handle bytes subtypes
1570 as bytes:
1581 as bytes:
1571
1582
1572 $ HGENCODING=ascii hg log -T "{branch|cbor}" -r0 \
1583 $ HGENCODING=ascii hg log -T "{branch|cbor}" -r0 \
1573 > | "$PYTHON" "$TESTTMP/decodecbor.py"
1584 > | "$PYTHON" "$TESTTMP/decodecbor.py"
1574 [
1585 [
1575 '?'
1586 '?'
1576 ]
1587 ]
1577 $ HGENCODING=latin-1 hg log -T "{branch|cbor}" -r0 \
1588 $ HGENCODING=latin-1 hg log -T "{branch|cbor}" -r0 \
1578 > | "$PYTHON" "$TESTTMP/decodecbor.py"
1589 > | "$PYTHON" "$TESTTMP/decodecbor.py"
1579 [
1590 [
1580 '\xe9'
1591 '\xe9'
1581 ]
1592 ]
1582
1593
1583 utf8 filter:
1594 utf8 filter:
1584
1595
1585 $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
1596 $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
1586 round-trip: c3a9
1597 round-trip: c3a9
1587 $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
1598 $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
1588 decoded: c3a9
1599 decoded: c3a9
1589 $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
1600 $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
1590 abort: decoding near * (glob)
1601 abort: decoding near * (glob)
1591 [255]
1602 [255]
1592 $ hg log -T "coerced to string: {rev|utf8}\n" -r0
1603 $ hg log -T "coerced to string: {rev|utf8}\n" -r0
1593 coerced to string: 0
1604 coerced to string: 0
1594
1605
1595 pad width:
1606 pad width:
1596
1607
1597 $ HGENCODING=utf-8 hg debugtemplate "{pad('`cat utf-8`', 2, '-')}\n"
1608 $ HGENCODING=utf-8 hg debugtemplate "{pad('`cat utf-8`', 2, '-')}\n"
1598 \xc3\xa9- (esc)
1609 \xc3\xa9- (esc)
1599
1610
1600 read config options:
1611 read config options:
1601
1612
1602 $ hg log -T "{config('templateconfig', 'knob', 'foo')}\n"
1613 $ hg log -T "{config('templateconfig', 'knob', 'foo')}\n"
1603 foo
1614 foo
1604 $ hg log -T "{config('templateconfig', 'knob', 'foo')}\n" \
1615 $ hg log -T "{config('templateconfig', 'knob', 'foo')}\n" \
1605 > --config templateconfig.knob=bar
1616 > --config templateconfig.knob=bar
1606 bar
1617 bar
1607 $ hg log -T "{configbool('templateconfig', 'knob', True)}\n"
1618 $ hg log -T "{configbool('templateconfig', 'knob', True)}\n"
1608 True
1619 True
1609 $ hg log -T "{configbool('templateconfig', 'knob', True)}\n" \
1620 $ hg log -T "{configbool('templateconfig', 'knob', True)}\n" \
1610 > --config templateconfig.knob=0
1621 > --config templateconfig.knob=0
1611 False
1622 False
1612 $ hg log -T "{configint('templateconfig', 'knob', 123)}\n"
1623 $ hg log -T "{configint('templateconfig', 'knob', 123)}\n"
1613 123
1624 123
1614 $ hg log -T "{configint('templateconfig', 'knob', 123)}\n" \
1625 $ hg log -T "{configint('templateconfig', 'knob', 123)}\n" \
1615 > --config templateconfig.knob=456
1626 > --config templateconfig.knob=456
1616 456
1627 456
1617 $ hg log -T "{config('templateconfig', 'knob')}\n"
1628 $ hg log -T "{config('templateconfig', 'knob')}\n"
1618 devel-warn: config item requires an explicit default value: 'templateconfig.knob' at: * (glob)
1629 devel-warn: config item requires an explicit default value: 'templateconfig.knob' at: * (glob)
1619
1630
1620 $ hg log -T "{configbool('ui', 'interactive')}\n"
1631 $ hg log -T "{configbool('ui', 'interactive')}\n"
1621 False
1632 False
1622 $ hg log -T "{configbool('ui', 'interactive')}\n" --config ui.interactive=1
1633 $ hg log -T "{configbool('ui', 'interactive')}\n" --config ui.interactive=1
1623 True
1634 True
1624 $ hg log -T "{config('templateconfig', 'knob', if(true, 'foo', 'bar'))}\n"
1635 $ hg log -T "{config('templateconfig', 'knob', if(true, 'foo', 'bar'))}\n"
1625 foo
1636 foo
1626
1637
1627 $ cd ..
1638 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now