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