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