##// END OF EJS Templates
templater: load template fragments from [templates] section in map file...
Yuya Nishihara -
r34715:f4aeb952 default
parent child Browse files
Show More
@@ -1,1515 +1,1516 b''
1 1 # templater.py - template expansion for output
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, print_function
9 9
10 10 import os
11 11 import re
12 12 import types
13 13
14 14 from .i18n import _
15 15 from . import (
16 16 color,
17 17 config,
18 18 encoding,
19 19 error,
20 20 minirst,
21 21 obsutil,
22 22 parser,
23 23 pycompat,
24 24 registrar,
25 25 revset as revsetmod,
26 26 revsetlang,
27 27 scmutil,
28 28 templatefilters,
29 29 templatekw,
30 30 util,
31 31 )
32 32
33 33 # template parsing
34 34
35 35 elements = {
36 36 # token-type: binding-strength, primary, prefix, infix, suffix
37 37 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
38 38 ".": (18, None, None, (".", 18), None),
39 39 "%": (15, None, None, ("%", 15), None),
40 40 "|": (15, None, None, ("|", 15), None),
41 41 "*": (5, None, None, ("*", 5), None),
42 42 "/": (5, None, None, ("/", 5), None),
43 43 "+": (4, None, None, ("+", 4), None),
44 44 "-": (4, None, ("negate", 19), ("-", 4), None),
45 45 "=": (3, None, None, ("keyvalue", 3), None),
46 46 ",": (2, None, None, ("list", 2), None),
47 47 ")": (0, None, None, None, None),
48 48 "integer": (0, "integer", None, None, None),
49 49 "symbol": (0, "symbol", None, None, None),
50 50 "string": (0, "string", None, None, None),
51 51 "template": (0, "template", None, None, None),
52 52 "end": (0, None, None, None, None),
53 53 }
54 54
55 55 def tokenize(program, start, end, term=None):
56 56 """Parse a template expression into a stream of tokens, which must end
57 57 with term if specified"""
58 58 pos = start
59 59 program = pycompat.bytestr(program)
60 60 while pos < end:
61 61 c = program[pos]
62 62 if c.isspace(): # skip inter-token whitespace
63 63 pass
64 64 elif c in "(=,).%|+-*/": # handle simple operators
65 65 yield (c, None, pos)
66 66 elif c in '"\'': # handle quoted templates
67 67 s = pos + 1
68 68 data, pos = _parsetemplate(program, s, end, c)
69 69 yield ('template', data, s)
70 70 pos -= 1
71 71 elif c == 'r' and program[pos:pos + 2] in ("r'", 'r"'):
72 72 # handle quoted strings
73 73 c = program[pos + 1]
74 74 s = pos = pos + 2
75 75 while pos < end: # find closing quote
76 76 d = program[pos]
77 77 if d == '\\': # skip over escaped characters
78 78 pos += 2
79 79 continue
80 80 if d == c:
81 81 yield ('string', program[s:pos], s)
82 82 break
83 83 pos += 1
84 84 else:
85 85 raise error.ParseError(_("unterminated string"), s)
86 86 elif c.isdigit():
87 87 s = pos
88 88 while pos < end:
89 89 d = program[pos]
90 90 if not d.isdigit():
91 91 break
92 92 pos += 1
93 93 yield ('integer', program[s:pos], s)
94 94 pos -= 1
95 95 elif (c == '\\' and program[pos:pos + 2] in (r"\'", r'\"')
96 96 or c == 'r' and program[pos:pos + 3] in (r"r\'", r'r\"')):
97 97 # handle escaped quoted strings for compatibility with 2.9.2-3.4,
98 98 # where some of nested templates were preprocessed as strings and
99 99 # then compiled. therefore, \"...\" was allowed. (issue4733)
100 100 #
101 101 # processing flow of _evalifliteral() at 5ab28a2e9962:
102 102 # outer template string -> stringify() -> compiletemplate()
103 103 # ------------------------ ------------ ------------------
104 104 # {f("\\\\ {g(\"\\\"\")}"} \\ {g("\"")} [r'\\', {g("\"")}]
105 105 # ~~~~~~~~
106 106 # escaped quoted string
107 107 if c == 'r':
108 108 pos += 1
109 109 token = 'string'
110 110 else:
111 111 token = 'template'
112 112 quote = program[pos:pos + 2]
113 113 s = pos = pos + 2
114 114 while pos < end: # find closing escaped quote
115 115 if program.startswith('\\\\\\', pos, end):
116 116 pos += 4 # skip over double escaped characters
117 117 continue
118 118 if program.startswith(quote, pos, end):
119 119 # interpret as if it were a part of an outer string
120 120 data = parser.unescapestr(program[s:pos])
121 121 if token == 'template':
122 122 data = _parsetemplate(data, 0, len(data))[0]
123 123 yield (token, data, s)
124 124 pos += 1
125 125 break
126 126 pos += 1
127 127 else:
128 128 raise error.ParseError(_("unterminated string"), s)
129 129 elif c.isalnum() or c in '_':
130 130 s = pos
131 131 pos += 1
132 132 while pos < end: # find end of symbol
133 133 d = program[pos]
134 134 if not (d.isalnum() or d == "_"):
135 135 break
136 136 pos += 1
137 137 sym = program[s:pos]
138 138 yield ('symbol', sym, s)
139 139 pos -= 1
140 140 elif c == term:
141 141 yield ('end', None, pos + 1)
142 142 return
143 143 else:
144 144 raise error.ParseError(_("syntax error"), pos)
145 145 pos += 1
146 146 if term:
147 147 raise error.ParseError(_("unterminated template expansion"), start)
148 148 yield ('end', None, pos)
149 149
150 150 def _parsetemplate(tmpl, start, stop, quote=''):
151 151 r"""
152 152 >>> _parsetemplate(b'foo{bar}"baz', 0, 12)
153 153 ([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12)
154 154 >>> _parsetemplate(b'foo{bar}"baz', 0, 12, quote=b'"')
155 155 ([('string', 'foo'), ('symbol', 'bar')], 9)
156 156 >>> _parsetemplate(b'foo"{bar}', 0, 9, quote=b'"')
157 157 ([('string', 'foo')], 4)
158 158 >>> _parsetemplate(br'foo\"bar"baz', 0, 12, quote=b'"')
159 159 ([('string', 'foo"'), ('string', 'bar')], 9)
160 160 >>> _parsetemplate(br'foo\\"bar', 0, 10, quote=b'"')
161 161 ([('string', 'foo\\')], 6)
162 162 """
163 163 parsed = []
164 164 sepchars = '{' + quote
165 165 pos = start
166 166 p = parser.parser(elements)
167 167 while pos < stop:
168 168 n = min((tmpl.find(c, pos, stop) for c in sepchars),
169 169 key=lambda n: (n < 0, n))
170 170 if n < 0:
171 171 parsed.append(('string', parser.unescapestr(tmpl[pos:stop])))
172 172 pos = stop
173 173 break
174 174 c = tmpl[n:n + 1]
175 175 bs = (n - pos) - len(tmpl[pos:n].rstrip('\\'))
176 176 if bs % 2 == 1:
177 177 # escaped (e.g. '\{', '\\\{', but not '\\{')
178 178 parsed.append(('string', parser.unescapestr(tmpl[pos:n - 1]) + c))
179 179 pos = n + 1
180 180 continue
181 181 if n > pos:
182 182 parsed.append(('string', parser.unescapestr(tmpl[pos:n])))
183 183 if c == quote:
184 184 return parsed, n + 1
185 185
186 186 parseres, pos = p.parse(tokenize(tmpl, n + 1, stop, '}'))
187 187 parsed.append(parseres)
188 188
189 189 if quote:
190 190 raise error.ParseError(_("unterminated string"), start)
191 191 return parsed, pos
192 192
193 193 def _unnesttemplatelist(tree):
194 194 """Expand list of templates to node tuple
195 195
196 196 >>> def f(tree):
197 197 ... print(pycompat.sysstr(prettyformat(_unnesttemplatelist(tree))))
198 198 >>> f((b'template', []))
199 199 (string '')
200 200 >>> f((b'template', [(b'string', b'foo')]))
201 201 (string 'foo')
202 202 >>> f((b'template', [(b'string', b'foo'), (b'symbol', b'rev')]))
203 203 (template
204 204 (string 'foo')
205 205 (symbol 'rev'))
206 206 >>> f((b'template', [(b'symbol', b'rev')])) # template(rev) -> str
207 207 (template
208 208 (symbol 'rev'))
209 209 >>> f((b'template', [(b'template', [(b'string', b'foo')])]))
210 210 (string 'foo')
211 211 """
212 212 if not isinstance(tree, tuple):
213 213 return tree
214 214 op = tree[0]
215 215 if op != 'template':
216 216 return (op,) + tuple(_unnesttemplatelist(x) for x in tree[1:])
217 217
218 218 assert len(tree) == 2
219 219 xs = tuple(_unnesttemplatelist(x) for x in tree[1])
220 220 if not xs:
221 221 return ('string', '') # empty template ""
222 222 elif len(xs) == 1 and xs[0][0] == 'string':
223 223 return xs[0] # fast path for string with no template fragment "x"
224 224 else:
225 225 return (op,) + xs
226 226
227 227 def parse(tmpl):
228 228 """Parse template string into tree"""
229 229 parsed, pos = _parsetemplate(tmpl, 0, len(tmpl))
230 230 assert pos == len(tmpl), 'unquoted template should be consumed'
231 231 return _unnesttemplatelist(('template', parsed))
232 232
233 233 def _parseexpr(expr):
234 234 """Parse a template expression into tree
235 235
236 236 >>> _parseexpr(b'"foo"')
237 237 ('string', 'foo')
238 238 >>> _parseexpr(b'foo(bar)')
239 239 ('func', ('symbol', 'foo'), ('symbol', 'bar'))
240 240 >>> _parseexpr(b'foo(')
241 241 Traceback (most recent call last):
242 242 ...
243 243 ParseError: ('not a prefix: end', 4)
244 244 >>> _parseexpr(b'"foo" "bar"')
245 245 Traceback (most recent call last):
246 246 ...
247 247 ParseError: ('invalid token', 7)
248 248 """
249 249 p = parser.parser(elements)
250 250 tree, pos = p.parse(tokenize(expr, 0, len(expr)))
251 251 if pos != len(expr):
252 252 raise error.ParseError(_('invalid token'), pos)
253 253 return _unnesttemplatelist(tree)
254 254
255 255 def prettyformat(tree):
256 256 return parser.prettyformat(tree, ('integer', 'string', 'symbol'))
257 257
258 258 def compileexp(exp, context, curmethods):
259 259 """Compile parsed template tree to (func, data) pair"""
260 260 t = exp[0]
261 261 if t in curmethods:
262 262 return curmethods[t](exp, context)
263 263 raise error.ParseError(_("unknown method '%s'") % t)
264 264
265 265 # template evaluation
266 266
267 267 def getsymbol(exp):
268 268 if exp[0] == 'symbol':
269 269 return exp[1]
270 270 raise error.ParseError(_("expected a symbol, got '%s'") % exp[0])
271 271
272 272 def getlist(x):
273 273 if not x:
274 274 return []
275 275 if x[0] == 'list':
276 276 return getlist(x[1]) + [x[2]]
277 277 return [x]
278 278
279 279 def gettemplate(exp, context):
280 280 """Compile given template tree or load named template from map file;
281 281 returns (func, data) pair"""
282 282 if exp[0] in ('template', 'string'):
283 283 return compileexp(exp, context, methods)
284 284 if exp[0] == 'symbol':
285 285 # unlike runsymbol(), here 'symbol' is always taken as template name
286 286 # even if it exists in mapping. this allows us to override mapping
287 287 # by web templates, e.g. 'changelogtag' is redefined in map file.
288 288 return context._load(exp[1])
289 289 raise error.ParseError(_("expected template specifier"))
290 290
291 291 def findsymbolicname(arg):
292 292 """Find symbolic name for the given compiled expression; returns None
293 293 if nothing found reliably"""
294 294 while True:
295 295 func, data = arg
296 296 if func is runsymbol:
297 297 return data
298 298 elif func is runfilter:
299 299 arg = data[0]
300 300 else:
301 301 return None
302 302
303 303 def evalrawexp(context, mapping, arg):
304 304 """Evaluate given argument as a bare template object which may require
305 305 further processing (such as folding generator of strings)"""
306 306 func, data = arg
307 307 return func(context, mapping, data)
308 308
309 309 def evalfuncarg(context, mapping, arg):
310 310 """Evaluate given argument as value type"""
311 311 thing = evalrawexp(context, mapping, arg)
312 312 thing = templatekw.unwrapvalue(thing)
313 313 # evalrawexp() may return string, generator of strings or arbitrary object
314 314 # such as date tuple, but filter does not want generator.
315 315 if isinstance(thing, types.GeneratorType):
316 316 thing = stringify(thing)
317 317 return thing
318 318
319 319 def evalboolean(context, mapping, arg):
320 320 """Evaluate given argument as boolean, but also takes boolean literals"""
321 321 func, data = arg
322 322 if func is runsymbol:
323 323 thing = func(context, mapping, data, default=None)
324 324 if thing is None:
325 325 # not a template keyword, takes as a boolean literal
326 326 thing = util.parsebool(data)
327 327 else:
328 328 thing = func(context, mapping, data)
329 329 thing = templatekw.unwrapvalue(thing)
330 330 if isinstance(thing, bool):
331 331 return thing
332 332 # other objects are evaluated as strings, which means 0 is True, but
333 333 # empty dict/list should be False as they are expected to be ''
334 334 return bool(stringify(thing))
335 335
336 336 def evalinteger(context, mapping, arg, err=None):
337 337 v = evalfuncarg(context, mapping, arg)
338 338 try:
339 339 return int(v)
340 340 except (TypeError, ValueError):
341 341 raise error.ParseError(err or _('not an integer'))
342 342
343 343 def evalstring(context, mapping, arg):
344 344 return stringify(evalrawexp(context, mapping, arg))
345 345
346 346 def evalstringliteral(context, mapping, arg):
347 347 """Evaluate given argument as string template, but returns symbol name
348 348 if it is unknown"""
349 349 func, data = arg
350 350 if func is runsymbol:
351 351 thing = func(context, mapping, data, default=data)
352 352 else:
353 353 thing = func(context, mapping, data)
354 354 return stringify(thing)
355 355
356 356 _evalfuncbytype = {
357 357 bool: evalboolean,
358 358 bytes: evalstring,
359 359 int: evalinteger,
360 360 }
361 361
362 362 def evalastype(context, mapping, arg, typ):
363 363 """Evaluate given argument and coerce its type"""
364 364 try:
365 365 f = _evalfuncbytype[typ]
366 366 except KeyError:
367 367 raise error.ProgrammingError('invalid type specified: %r' % typ)
368 368 return f(context, mapping, arg)
369 369
370 370 def runinteger(context, mapping, data):
371 371 return int(data)
372 372
373 373 def runstring(context, mapping, data):
374 374 return data
375 375
376 376 def _recursivesymbolblocker(key):
377 377 def showrecursion(**args):
378 378 raise error.Abort(_("recursive reference '%s' in template") % key)
379 379 return showrecursion
380 380
381 381 def _runrecursivesymbol(context, mapping, key):
382 382 raise error.Abort(_("recursive reference '%s' in template") % key)
383 383
384 384 def runsymbol(context, mapping, key, default=''):
385 385 v = mapping.get(key)
386 386 if v is None:
387 387 v = context._defaults.get(key)
388 388 if v is None:
389 389 # put poison to cut recursion. we can't move this to parsing phase
390 390 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
391 391 safemapping = mapping.copy()
392 392 safemapping[key] = _recursivesymbolblocker(key)
393 393 try:
394 394 v = context.process(key, safemapping)
395 395 except TemplateNotFound:
396 396 v = default
397 397 if callable(v):
398 398 return v(**pycompat.strkwargs(mapping))
399 399 return v
400 400
401 401 def buildtemplate(exp, context):
402 402 ctmpl = [compileexp(e, context, methods) for e in exp[1:]]
403 403 return (runtemplate, ctmpl)
404 404
405 405 def runtemplate(context, mapping, template):
406 406 for arg in template:
407 407 yield evalrawexp(context, mapping, arg)
408 408
409 409 def buildfilter(exp, context):
410 410 n = getsymbol(exp[2])
411 411 if n in context._filters:
412 412 filt = context._filters[n]
413 413 arg = compileexp(exp[1], context, methods)
414 414 return (runfilter, (arg, filt))
415 415 if n in funcs:
416 416 f = funcs[n]
417 417 args = _buildfuncargs(exp[1], context, methods, n, f._argspec)
418 418 return (f, args)
419 419 raise error.ParseError(_("unknown function '%s'") % n)
420 420
421 421 def runfilter(context, mapping, data):
422 422 arg, filt = data
423 423 thing = evalfuncarg(context, mapping, arg)
424 424 try:
425 425 return filt(thing)
426 426 except (ValueError, AttributeError, TypeError):
427 427 sym = findsymbolicname(arg)
428 428 if sym:
429 429 msg = (_("template filter '%s' is not compatible with keyword '%s'")
430 430 % (filt.__name__, sym))
431 431 else:
432 432 msg = _("incompatible use of template filter '%s'") % filt.__name__
433 433 raise error.Abort(msg)
434 434
435 435 def buildmap(exp, context):
436 436 darg = compileexp(exp[1], context, methods)
437 437 targ = gettemplate(exp[2], context)
438 438 return (runmap, (darg, targ))
439 439
440 440 def runmap(context, mapping, data):
441 441 darg, targ = data
442 442 d = evalrawexp(context, mapping, darg)
443 443 if util.safehasattr(d, 'itermaps'):
444 444 diter = d.itermaps()
445 445 else:
446 446 try:
447 447 diter = iter(d)
448 448 except TypeError:
449 449 sym = findsymbolicname(darg)
450 450 if sym:
451 451 raise error.ParseError(_("keyword '%s' is not iterable") % sym)
452 452 else:
453 453 raise error.ParseError(_("%r is not iterable") % d)
454 454
455 455 for i, v in enumerate(diter):
456 456 lm = mapping.copy()
457 457 lm['index'] = i
458 458 if isinstance(v, dict):
459 459 lm.update(v)
460 460 lm['originalnode'] = mapping.get('node')
461 461 yield evalrawexp(context, lm, targ)
462 462 else:
463 463 # v is not an iterable of dicts, this happen when 'key'
464 464 # has been fully expanded already and format is useless.
465 465 # If so, return the expanded value.
466 466 yield v
467 467
468 468 def buildmember(exp, context):
469 469 darg = compileexp(exp[1], context, methods)
470 470 memb = getsymbol(exp[2])
471 471 return (runmember, (darg, memb))
472 472
473 473 def runmember(context, mapping, data):
474 474 darg, memb = data
475 475 d = evalrawexp(context, mapping, darg)
476 476 if util.safehasattr(d, 'tomap'):
477 477 lm = mapping.copy()
478 478 lm.update(d.tomap())
479 479 return runsymbol(context, lm, memb)
480 480 if util.safehasattr(d, 'get'):
481 481 return _getdictitem(d, memb)
482 482
483 483 sym = findsymbolicname(darg)
484 484 if sym:
485 485 raise error.ParseError(_("keyword '%s' has no member") % sym)
486 486 else:
487 487 raise error.ParseError(_("%r has no member") % d)
488 488
489 489 def buildnegate(exp, context):
490 490 arg = compileexp(exp[1], context, exprmethods)
491 491 return (runnegate, arg)
492 492
493 493 def runnegate(context, mapping, data):
494 494 data = evalinteger(context, mapping, data,
495 495 _('negation needs an integer argument'))
496 496 return -data
497 497
498 498 def buildarithmetic(exp, context, func):
499 499 left = compileexp(exp[1], context, exprmethods)
500 500 right = compileexp(exp[2], context, exprmethods)
501 501 return (runarithmetic, (func, left, right))
502 502
503 503 def runarithmetic(context, mapping, data):
504 504 func, left, right = data
505 505 left = evalinteger(context, mapping, left,
506 506 _('arithmetic only defined on integers'))
507 507 right = evalinteger(context, mapping, right,
508 508 _('arithmetic only defined on integers'))
509 509 try:
510 510 return func(left, right)
511 511 except ZeroDivisionError:
512 512 raise error.Abort(_('division by zero is not defined'))
513 513
514 514 def buildfunc(exp, context):
515 515 n = getsymbol(exp[1])
516 516 if n in funcs:
517 517 f = funcs[n]
518 518 args = _buildfuncargs(exp[2], context, exprmethods, n, f._argspec)
519 519 return (f, args)
520 520 if n in context._filters:
521 521 args = _buildfuncargs(exp[2], context, exprmethods, n, argspec=None)
522 522 if len(args) != 1:
523 523 raise error.ParseError(_("filter %s expects one argument") % n)
524 524 f = context._filters[n]
525 525 return (runfilter, (args[0], f))
526 526 raise error.ParseError(_("unknown function '%s'") % n)
527 527
528 528 def _buildfuncargs(exp, context, curmethods, funcname, argspec):
529 529 """Compile parsed tree of function arguments into list or dict of
530 530 (func, data) pairs
531 531
532 532 >>> context = engine(lambda t: (runsymbol, t))
533 533 >>> def fargs(expr, argspec):
534 534 ... x = _parseexpr(expr)
535 535 ... n = getsymbol(x[1])
536 536 ... return _buildfuncargs(x[2], context, exprmethods, n, argspec)
537 537 >>> list(fargs(b'a(l=1, k=2)', b'k l m').keys())
538 538 ['l', 'k']
539 539 >>> args = fargs(b'a(opts=1, k=2)', b'**opts')
540 540 >>> list(args.keys()), list(args[b'opts'].keys())
541 541 (['opts'], ['opts', 'k'])
542 542 """
543 543 def compiledict(xs):
544 544 return util.sortdict((k, compileexp(x, context, curmethods))
545 545 for k, x in xs.iteritems())
546 546 def compilelist(xs):
547 547 return [compileexp(x, context, curmethods) for x in xs]
548 548
549 549 if not argspec:
550 550 # filter or function with no argspec: return list of positional args
551 551 return compilelist(getlist(exp))
552 552
553 553 # function with argspec: return dict of named args
554 554 _poskeys, varkey, _keys, optkey = argspec = parser.splitargspec(argspec)
555 555 treeargs = parser.buildargsdict(getlist(exp), funcname, argspec,
556 556 keyvaluenode='keyvalue', keynode='symbol')
557 557 compargs = util.sortdict()
558 558 if varkey:
559 559 compargs[varkey] = compilelist(treeargs.pop(varkey))
560 560 if optkey:
561 561 compargs[optkey] = compiledict(treeargs.pop(optkey))
562 562 compargs.update(compiledict(treeargs))
563 563 return compargs
564 564
565 565 def buildkeyvaluepair(exp, content):
566 566 raise error.ParseError(_("can't use a key-value pair in this context"))
567 567
568 568 # dict of template built-in functions
569 569 funcs = {}
570 570
571 571 templatefunc = registrar.templatefunc(funcs)
572 572
573 573 @templatefunc('date(date[, fmt])')
574 574 def date(context, mapping, args):
575 575 """Format a date. See :hg:`help dates` for formatting
576 576 strings. The default is a Unix date format, including the timezone:
577 577 "Mon Sep 04 15:13:13 2006 0700"."""
578 578 if not (1 <= len(args) <= 2):
579 579 # i18n: "date" is a keyword
580 580 raise error.ParseError(_("date expects one or two arguments"))
581 581
582 582 date = evalfuncarg(context, mapping, args[0])
583 583 fmt = None
584 584 if len(args) == 2:
585 585 fmt = evalstring(context, mapping, args[1])
586 586 try:
587 587 if fmt is None:
588 588 return util.datestr(date)
589 589 else:
590 590 return util.datestr(date, fmt)
591 591 except (TypeError, ValueError):
592 592 # i18n: "date" is a keyword
593 593 raise error.ParseError(_("date expects a date information"))
594 594
595 595 @templatefunc('dict([[key=]value...])', argspec='*args **kwargs')
596 596 def dict_(context, mapping, args):
597 597 """Construct a dict from key-value pairs. A key may be omitted if
598 598 a value expression can provide an unambiguous name."""
599 599 data = util.sortdict()
600 600
601 601 for v in args['args']:
602 602 k = findsymbolicname(v)
603 603 if not k:
604 604 raise error.ParseError(_('dict key cannot be inferred'))
605 605 if k in data or k in args['kwargs']:
606 606 raise error.ParseError(_("duplicated dict key '%s' inferred") % k)
607 607 data[k] = evalfuncarg(context, mapping, v)
608 608
609 609 data.update((k, evalfuncarg(context, mapping, v))
610 610 for k, v in args['kwargs'].iteritems())
611 611 return templatekw.hybriddict(data)
612 612
613 613 @templatefunc('diff([includepattern [, excludepattern]])')
614 614 def diff(context, mapping, args):
615 615 """Show a diff, optionally
616 616 specifying files to include or exclude."""
617 617 if len(args) > 2:
618 618 # i18n: "diff" is a keyword
619 619 raise error.ParseError(_("diff expects zero, one, or two arguments"))
620 620
621 621 def getpatterns(i):
622 622 if i < len(args):
623 623 s = evalstring(context, mapping, args[i]).strip()
624 624 if s:
625 625 return [s]
626 626 return []
627 627
628 628 ctx = mapping['ctx']
629 629 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
630 630
631 631 return ''.join(chunks)
632 632
633 633 @templatefunc('extdata(source)', argspec='source')
634 634 def extdata(context, mapping, args):
635 635 """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
636 636 if 'source' not in args:
637 637 # i18n: "extdata" is a keyword
638 638 raise error.ParseError(_('extdata expects one argument'))
639 639
640 640 source = evalstring(context, mapping, args['source'])
641 641 cache = mapping['cache'].setdefault('extdata', {})
642 642 ctx = mapping['ctx']
643 643 if source in cache:
644 644 data = cache[source]
645 645 else:
646 646 data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
647 647 return data.get(ctx.rev(), '')
648 648
649 649 @templatefunc('files(pattern)')
650 650 def files(context, mapping, args):
651 651 """All files of the current changeset matching the pattern. See
652 652 :hg:`help patterns`."""
653 653 if not len(args) == 1:
654 654 # i18n: "files" is a keyword
655 655 raise error.ParseError(_("files expects one argument"))
656 656
657 657 raw = evalstring(context, mapping, args[0])
658 658 ctx = mapping['ctx']
659 659 m = ctx.match([raw])
660 660 files = list(ctx.matches(m))
661 661 return templatekw.showlist("file", files, mapping)
662 662
663 663 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
664 664 def fill(context, mapping, args):
665 665 """Fill many
666 666 paragraphs with optional indentation. See the "fill" filter."""
667 667 if not (1 <= len(args) <= 4):
668 668 # i18n: "fill" is a keyword
669 669 raise error.ParseError(_("fill expects one to four arguments"))
670 670
671 671 text = evalstring(context, mapping, args[0])
672 672 width = 76
673 673 initindent = ''
674 674 hangindent = ''
675 675 if 2 <= len(args) <= 4:
676 676 width = evalinteger(context, mapping, args[1],
677 677 # i18n: "fill" is a keyword
678 678 _("fill expects an integer width"))
679 679 try:
680 680 initindent = evalstring(context, mapping, args[2])
681 681 hangindent = evalstring(context, mapping, args[3])
682 682 except IndexError:
683 683 pass
684 684
685 685 return templatefilters.fill(text, width, initindent, hangindent)
686 686
687 687 @templatefunc('formatnode(node)')
688 688 def formatnode(context, mapping, args):
689 689 """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
690 690 if len(args) != 1:
691 691 # i18n: "formatnode" is a keyword
692 692 raise error.ParseError(_("formatnode expects one argument"))
693 693
694 694 ui = mapping['ui']
695 695 node = evalstring(context, mapping, args[0])
696 696 if ui.debugflag:
697 697 return node
698 698 return templatefilters.short(node)
699 699
700 700 @templatefunc('pad(text, width[, fillchar=\' \'[, left=False]])',
701 701 argspec='text width fillchar left')
702 702 def pad(context, mapping, args):
703 703 """Pad text with a
704 704 fill character."""
705 705 if 'text' not in args or 'width' not in args:
706 706 # i18n: "pad" is a keyword
707 707 raise error.ParseError(_("pad() expects two to four arguments"))
708 708
709 709 width = evalinteger(context, mapping, args['width'],
710 710 # i18n: "pad" is a keyword
711 711 _("pad() expects an integer width"))
712 712
713 713 text = evalstring(context, mapping, args['text'])
714 714
715 715 left = False
716 716 fillchar = ' '
717 717 if 'fillchar' in args:
718 718 fillchar = evalstring(context, mapping, args['fillchar'])
719 719 if len(color.stripeffects(fillchar)) != 1:
720 720 # i18n: "pad" is a keyword
721 721 raise error.ParseError(_("pad() expects a single fill character"))
722 722 if 'left' in args:
723 723 left = evalboolean(context, mapping, args['left'])
724 724
725 725 fillwidth = width - encoding.colwidth(color.stripeffects(text))
726 726 if fillwidth <= 0:
727 727 return text
728 728 if left:
729 729 return fillchar * fillwidth + text
730 730 else:
731 731 return text + fillchar * fillwidth
732 732
733 733 @templatefunc('indent(text, indentchars[, firstline])')
734 734 def indent(context, mapping, args):
735 735 """Indents all non-empty lines
736 736 with the characters given in the indentchars string. An optional
737 737 third parameter will override the indent for the first line only
738 738 if present."""
739 739 if not (2 <= len(args) <= 3):
740 740 # i18n: "indent" is a keyword
741 741 raise error.ParseError(_("indent() expects two or three arguments"))
742 742
743 743 text = evalstring(context, mapping, args[0])
744 744 indent = evalstring(context, mapping, args[1])
745 745
746 746 if len(args) == 3:
747 747 firstline = evalstring(context, mapping, args[2])
748 748 else:
749 749 firstline = indent
750 750
751 751 # the indent function doesn't indent the first line, so we do it here
752 752 return templatefilters.indent(firstline + text, indent)
753 753
754 754 @templatefunc('get(dict, key)')
755 755 def get(context, mapping, args):
756 756 """Get an attribute/key from an object. Some keywords
757 757 are complex types. This function allows you to obtain the value of an
758 758 attribute on these types."""
759 759 if len(args) != 2:
760 760 # i18n: "get" is a keyword
761 761 raise error.ParseError(_("get() expects two arguments"))
762 762
763 763 dictarg = evalfuncarg(context, mapping, args[0])
764 764 if not util.safehasattr(dictarg, 'get'):
765 765 # i18n: "get" is a keyword
766 766 raise error.ParseError(_("get() expects a dict as first argument"))
767 767
768 768 key = evalfuncarg(context, mapping, args[1])
769 769 return _getdictitem(dictarg, key)
770 770
771 771 def _getdictitem(dictarg, key):
772 772 val = dictarg.get(key)
773 773 if val is None:
774 774 return
775 775 return templatekw.wraphybridvalue(dictarg, key, val)
776 776
777 777 @templatefunc('if(expr, then[, else])')
778 778 def if_(context, mapping, args):
779 779 """Conditionally execute based on the result of
780 780 an expression."""
781 781 if not (2 <= len(args) <= 3):
782 782 # i18n: "if" is a keyword
783 783 raise error.ParseError(_("if expects two or three arguments"))
784 784
785 785 test = evalboolean(context, mapping, args[0])
786 786 if test:
787 787 yield evalrawexp(context, mapping, args[1])
788 788 elif len(args) == 3:
789 789 yield evalrawexp(context, mapping, args[2])
790 790
791 791 @templatefunc('ifcontains(needle, haystack, then[, else])')
792 792 def ifcontains(context, mapping, args):
793 793 """Conditionally execute based
794 794 on whether the item "needle" is in "haystack"."""
795 795 if not (3 <= len(args) <= 4):
796 796 # i18n: "ifcontains" is a keyword
797 797 raise error.ParseError(_("ifcontains expects three or four arguments"))
798 798
799 799 haystack = evalfuncarg(context, mapping, args[1])
800 800 try:
801 801 needle = evalastype(context, mapping, args[0],
802 802 getattr(haystack, 'keytype', None) or bytes)
803 803 found = (needle in haystack)
804 804 except error.ParseError:
805 805 found = False
806 806
807 807 if found:
808 808 yield evalrawexp(context, mapping, args[2])
809 809 elif len(args) == 4:
810 810 yield evalrawexp(context, mapping, args[3])
811 811
812 812 @templatefunc('ifeq(expr1, expr2, then[, else])')
813 813 def ifeq(context, mapping, args):
814 814 """Conditionally execute based on
815 815 whether 2 items are equivalent."""
816 816 if not (3 <= len(args) <= 4):
817 817 # i18n: "ifeq" is a keyword
818 818 raise error.ParseError(_("ifeq expects three or four arguments"))
819 819
820 820 test = evalstring(context, mapping, args[0])
821 821 match = evalstring(context, mapping, args[1])
822 822 if test == match:
823 823 yield evalrawexp(context, mapping, args[2])
824 824 elif len(args) == 4:
825 825 yield evalrawexp(context, mapping, args[3])
826 826
827 827 @templatefunc('join(list, sep)')
828 828 def join(context, mapping, args):
829 829 """Join items in a list with a delimiter."""
830 830 if not (1 <= len(args) <= 2):
831 831 # i18n: "join" is a keyword
832 832 raise error.ParseError(_("join expects one or two arguments"))
833 833
834 834 # TODO: perhaps this should be evalfuncarg(), but it can't because hgweb
835 835 # abuses generator as a keyword that returns a list of dicts.
836 836 joinset = evalrawexp(context, mapping, args[0])
837 837 joinset = templatekw.unwrapvalue(joinset)
838 838 joinfmt = getattr(joinset, 'joinfmt', pycompat.identity)
839 839 joiner = " "
840 840 if len(args) > 1:
841 841 joiner = evalstring(context, mapping, args[1])
842 842
843 843 first = True
844 844 for x in joinset:
845 845 if first:
846 846 first = False
847 847 else:
848 848 yield joiner
849 849 yield joinfmt(x)
850 850
851 851 @templatefunc('label(label, expr)')
852 852 def label(context, mapping, args):
853 853 """Apply a label to generated content. Content with
854 854 a label applied can result in additional post-processing, such as
855 855 automatic colorization."""
856 856 if len(args) != 2:
857 857 # i18n: "label" is a keyword
858 858 raise error.ParseError(_("label expects two arguments"))
859 859
860 860 ui = mapping['ui']
861 861 thing = evalstring(context, mapping, args[1])
862 862 # preserve unknown symbol as literal so effects like 'red', 'bold',
863 863 # etc. don't need to be quoted
864 864 label = evalstringliteral(context, mapping, args[0])
865 865
866 866 return ui.label(thing, label)
867 867
868 868 @templatefunc('latesttag([pattern])')
869 869 def latesttag(context, mapping, args):
870 870 """The global tags matching the given pattern on the
871 871 most recent globally tagged ancestor of this changeset.
872 872 If no such tags exist, the "{tag}" template resolves to
873 873 the string "null"."""
874 874 if len(args) > 1:
875 875 # i18n: "latesttag" is a keyword
876 876 raise error.ParseError(_("latesttag expects at most one argument"))
877 877
878 878 pattern = None
879 879 if len(args) == 1:
880 880 pattern = evalstring(context, mapping, args[0])
881 881
882 882 return templatekw.showlatesttags(pattern, **mapping)
883 883
884 884 @templatefunc('localdate(date[, tz])')
885 885 def localdate(context, mapping, args):
886 886 """Converts a date to the specified timezone.
887 887 The default is local date."""
888 888 if not (1 <= len(args) <= 2):
889 889 # i18n: "localdate" is a keyword
890 890 raise error.ParseError(_("localdate expects one or two arguments"))
891 891
892 892 date = evalfuncarg(context, mapping, args[0])
893 893 try:
894 894 date = util.parsedate(date)
895 895 except AttributeError: # not str nor date tuple
896 896 # i18n: "localdate" is a keyword
897 897 raise error.ParseError(_("localdate expects a date information"))
898 898 if len(args) >= 2:
899 899 tzoffset = None
900 900 tz = evalfuncarg(context, mapping, args[1])
901 901 if isinstance(tz, str):
902 902 tzoffset, remainder = util.parsetimezone(tz)
903 903 if remainder:
904 904 tzoffset = None
905 905 if tzoffset is None:
906 906 try:
907 907 tzoffset = int(tz)
908 908 except (TypeError, ValueError):
909 909 # i18n: "localdate" is a keyword
910 910 raise error.ParseError(_("localdate expects a timezone"))
911 911 else:
912 912 tzoffset = util.makedate()[1]
913 913 return (date[0], tzoffset)
914 914
915 915 @templatefunc('max(iterable)')
916 916 def max_(context, mapping, args, **kwargs):
917 917 """Return the max of an iterable"""
918 918 if len(args) != 1:
919 919 # i18n: "max" is a keyword
920 920 raise error.ParseError(_("max expects one arguments"))
921 921
922 922 iterable = evalfuncarg(context, mapping, args[0])
923 923 try:
924 924 x = max(iterable)
925 925 except (TypeError, ValueError):
926 926 # i18n: "max" is a keyword
927 927 raise error.ParseError(_("max first argument should be an iterable"))
928 928 return templatekw.wraphybridvalue(iterable, x, x)
929 929
930 930 @templatefunc('min(iterable)')
931 931 def min_(context, mapping, args, **kwargs):
932 932 """Return the min of an iterable"""
933 933 if len(args) != 1:
934 934 # i18n: "min" is a keyword
935 935 raise error.ParseError(_("min expects one arguments"))
936 936
937 937 iterable = evalfuncarg(context, mapping, args[0])
938 938 try:
939 939 x = min(iterable)
940 940 except (TypeError, ValueError):
941 941 # i18n: "min" is a keyword
942 942 raise error.ParseError(_("min first argument should be an iterable"))
943 943 return templatekw.wraphybridvalue(iterable, x, x)
944 944
945 945 @templatefunc('mod(a, b)')
946 946 def mod(context, mapping, args):
947 947 """Calculate a mod b such that a / b + a mod b == a"""
948 948 if not len(args) == 2:
949 949 # i18n: "mod" is a keyword
950 950 raise error.ParseError(_("mod expects two arguments"))
951 951
952 952 func = lambda a, b: a % b
953 953 return runarithmetic(context, mapping, (func, args[0], args[1]))
954 954
955 955 @templatefunc('obsfateoperations(markers)')
956 956 def obsfateoperations(context, mapping, args):
957 957 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
958 958 if len(args) != 1:
959 959 # i18n: "obsfateoperations" is a keyword
960 960 raise error.ParseError(_("obsfateoperations expects one arguments"))
961 961
962 962 markers = evalfuncarg(context, mapping, args[0])
963 963
964 964 try:
965 965 data = obsutil.markersoperations(markers)
966 966 return templatekw.hybridlist(data, name='operation')
967 967 except (TypeError, KeyError):
968 968 # i18n: "obsfateoperations" is a keyword
969 969 errmsg = _("obsfateoperations first argument should be an iterable")
970 970 raise error.ParseError(errmsg)
971 971
972 972 @templatefunc('obsfatedate(markers)')
973 973 def obsfatedate(context, mapping, args):
974 974 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
975 975 if len(args) != 1:
976 976 # i18n: "obsfatedate" is a keyword
977 977 raise error.ParseError(_("obsfatedate expects one arguments"))
978 978
979 979 markers = evalfuncarg(context, mapping, args[0])
980 980
981 981 try:
982 982 data = obsutil.markersdates(markers)
983 983 return templatekw.hybridlist(data, name='date', fmt='%d %d')
984 984 except (TypeError, KeyError):
985 985 # i18n: "obsfatedate" is a keyword
986 986 errmsg = _("obsfatedate first argument should be an iterable")
987 987 raise error.ParseError(errmsg)
988 988
989 989 @templatefunc('obsfateusers(markers)')
990 990 def obsfateusers(context, mapping, args):
991 991 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
992 992 if len(args) != 1:
993 993 # i18n: "obsfateusers" is a keyword
994 994 raise error.ParseError(_("obsfateusers expects one arguments"))
995 995
996 996 markers = evalfuncarg(context, mapping, args[0])
997 997
998 998 try:
999 999 data = obsutil.markersusers(markers)
1000 1000 return templatekw.hybridlist(data, name='user')
1001 1001 except (TypeError, KeyError, ValueError):
1002 1002 # i18n: "obsfateusers" is a keyword
1003 1003 msg = _("obsfateusers first argument should be an iterable of "
1004 1004 "obsmakers")
1005 1005 raise error.ParseError(msg)
1006 1006
1007 1007 @templatefunc('obsfateverb(successors)')
1008 1008 def obsfateverb(context, mapping, args):
1009 1009 """Compute obsfate related information based on successors (EXPERIMENTAL)"""
1010 1010 if len(args) != 1:
1011 1011 # i18n: "obsfateverb" is a keyword
1012 1012 raise error.ParseError(_("obsfateverb expects one arguments"))
1013 1013
1014 1014 successors = evalfuncarg(context, mapping, args[0])
1015 1015
1016 1016 try:
1017 1017 return obsutil.successorsetverb(successors)
1018 1018 except TypeError:
1019 1019 # i18n: "obsfateverb" is a keyword
1020 1020 errmsg = _("obsfateverb first argument should be countable")
1021 1021 raise error.ParseError(errmsg)
1022 1022
1023 1023 @templatefunc('relpath(path)')
1024 1024 def relpath(context, mapping, args):
1025 1025 """Convert a repository-absolute path into a filesystem path relative to
1026 1026 the current working directory."""
1027 1027 if len(args) != 1:
1028 1028 # i18n: "relpath" is a keyword
1029 1029 raise error.ParseError(_("relpath expects one argument"))
1030 1030
1031 1031 repo = mapping['ctx'].repo()
1032 1032 path = evalstring(context, mapping, args[0])
1033 1033 return repo.pathto(path)
1034 1034
1035 1035 @templatefunc('revset(query[, formatargs...])')
1036 1036 def revset(context, mapping, args):
1037 1037 """Execute a revision set query. See
1038 1038 :hg:`help revset`."""
1039 1039 if not len(args) > 0:
1040 1040 # i18n: "revset" is a keyword
1041 1041 raise error.ParseError(_("revset expects one or more arguments"))
1042 1042
1043 1043 raw = evalstring(context, mapping, args[0])
1044 1044 ctx = mapping['ctx']
1045 1045 repo = ctx.repo()
1046 1046
1047 1047 def query(expr):
1048 1048 m = revsetmod.match(repo.ui, expr, repo=repo)
1049 1049 return m(repo)
1050 1050
1051 1051 if len(args) > 1:
1052 1052 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
1053 1053 revs = query(revsetlang.formatspec(raw, *formatargs))
1054 1054 revs = list(revs)
1055 1055 else:
1056 1056 revsetcache = mapping['cache'].setdefault("revsetcache", {})
1057 1057 if raw in revsetcache:
1058 1058 revs = revsetcache[raw]
1059 1059 else:
1060 1060 revs = query(raw)
1061 1061 revs = list(revs)
1062 1062 revsetcache[raw] = revs
1063 1063
1064 1064 return templatekw.showrevslist("revision", revs, **mapping)
1065 1065
1066 1066 @templatefunc('rstdoc(text, style)')
1067 1067 def rstdoc(context, mapping, args):
1068 1068 """Format reStructuredText."""
1069 1069 if len(args) != 2:
1070 1070 # i18n: "rstdoc" is a keyword
1071 1071 raise error.ParseError(_("rstdoc expects two arguments"))
1072 1072
1073 1073 text = evalstring(context, mapping, args[0])
1074 1074 style = evalstring(context, mapping, args[1])
1075 1075
1076 1076 return minirst.format(text, style=style, keep=['verbose'])
1077 1077
1078 1078 @templatefunc('separate(sep, args)', argspec='sep *args')
1079 1079 def separate(context, mapping, args):
1080 1080 """Add a separator between non-empty arguments."""
1081 1081 if 'sep' not in args:
1082 1082 # i18n: "separate" is a keyword
1083 1083 raise error.ParseError(_("separate expects at least one argument"))
1084 1084
1085 1085 sep = evalstring(context, mapping, args['sep'])
1086 1086 first = True
1087 1087 for arg in args['args']:
1088 1088 argstr = evalstring(context, mapping, arg)
1089 1089 if not argstr:
1090 1090 continue
1091 1091 if first:
1092 1092 first = False
1093 1093 else:
1094 1094 yield sep
1095 1095 yield argstr
1096 1096
1097 1097 @templatefunc('shortest(node, minlength=4)')
1098 1098 def shortest(context, mapping, args):
1099 1099 """Obtain the shortest representation of
1100 1100 a node."""
1101 1101 if not (1 <= len(args) <= 2):
1102 1102 # i18n: "shortest" is a keyword
1103 1103 raise error.ParseError(_("shortest() expects one or two arguments"))
1104 1104
1105 1105 node = evalstring(context, mapping, args[0])
1106 1106
1107 1107 minlength = 4
1108 1108 if len(args) > 1:
1109 1109 minlength = evalinteger(context, mapping, args[1],
1110 1110 # i18n: "shortest" is a keyword
1111 1111 _("shortest() expects an integer minlength"))
1112 1112
1113 1113 # _partialmatch() of filtered changelog could take O(len(repo)) time,
1114 1114 # which would be unacceptably slow. so we look for hash collision in
1115 1115 # unfiltered space, which means some hashes may be slightly longer.
1116 1116 cl = mapping['ctx']._repo.unfiltered().changelog
1117 1117 return cl.shortest(node, minlength)
1118 1118
1119 1119 @templatefunc('strip(text[, chars])')
1120 1120 def strip(context, mapping, args):
1121 1121 """Strip characters from a string. By default,
1122 1122 strips all leading and trailing whitespace."""
1123 1123 if not (1 <= len(args) <= 2):
1124 1124 # i18n: "strip" is a keyword
1125 1125 raise error.ParseError(_("strip expects one or two arguments"))
1126 1126
1127 1127 text = evalstring(context, mapping, args[0])
1128 1128 if len(args) == 2:
1129 1129 chars = evalstring(context, mapping, args[1])
1130 1130 return text.strip(chars)
1131 1131 return text.strip()
1132 1132
1133 1133 @templatefunc('sub(pattern, replacement, expression)')
1134 1134 def sub(context, mapping, args):
1135 1135 """Perform text substitution
1136 1136 using regular expressions."""
1137 1137 if len(args) != 3:
1138 1138 # i18n: "sub" is a keyword
1139 1139 raise error.ParseError(_("sub expects three arguments"))
1140 1140
1141 1141 pat = evalstring(context, mapping, args[0])
1142 1142 rpl = evalstring(context, mapping, args[1])
1143 1143 src = evalstring(context, mapping, args[2])
1144 1144 try:
1145 1145 patre = re.compile(pat)
1146 1146 except re.error:
1147 1147 # i18n: "sub" is a keyword
1148 1148 raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
1149 1149 try:
1150 1150 yield patre.sub(rpl, src)
1151 1151 except re.error:
1152 1152 # i18n: "sub" is a keyword
1153 1153 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
1154 1154
1155 1155 @templatefunc('startswith(pattern, text)')
1156 1156 def startswith(context, mapping, args):
1157 1157 """Returns the value from the "text" argument
1158 1158 if it begins with the content from the "pattern" argument."""
1159 1159 if len(args) != 2:
1160 1160 # i18n: "startswith" is a keyword
1161 1161 raise error.ParseError(_("startswith expects two arguments"))
1162 1162
1163 1163 patn = evalstring(context, mapping, args[0])
1164 1164 text = evalstring(context, mapping, args[1])
1165 1165 if text.startswith(patn):
1166 1166 return text
1167 1167 return ''
1168 1168
1169 1169 @templatefunc('word(number, text[, separator])')
1170 1170 def word(context, mapping, args):
1171 1171 """Return the nth word from a string."""
1172 1172 if not (2 <= len(args) <= 3):
1173 1173 # i18n: "word" is a keyword
1174 1174 raise error.ParseError(_("word expects two or three arguments, got %d")
1175 1175 % len(args))
1176 1176
1177 1177 num = evalinteger(context, mapping, args[0],
1178 1178 # i18n: "word" is a keyword
1179 1179 _("word expects an integer index"))
1180 1180 text = evalstring(context, mapping, args[1])
1181 1181 if len(args) == 3:
1182 1182 splitter = evalstring(context, mapping, args[2])
1183 1183 else:
1184 1184 splitter = None
1185 1185
1186 1186 tokens = text.split(splitter)
1187 1187 if num >= len(tokens) or num < -len(tokens):
1188 1188 return ''
1189 1189 else:
1190 1190 return tokens[num]
1191 1191
1192 1192 # methods to interpret function arguments or inner expressions (e.g. {_(x)})
1193 1193 exprmethods = {
1194 1194 "integer": lambda e, c: (runinteger, e[1]),
1195 1195 "string": lambda e, c: (runstring, e[1]),
1196 1196 "symbol": lambda e, c: (runsymbol, e[1]),
1197 1197 "template": buildtemplate,
1198 1198 "group": lambda e, c: compileexp(e[1], c, exprmethods),
1199 1199 ".": buildmember,
1200 1200 "|": buildfilter,
1201 1201 "%": buildmap,
1202 1202 "func": buildfunc,
1203 1203 "keyvalue": buildkeyvaluepair,
1204 1204 "+": lambda e, c: buildarithmetic(e, c, lambda a, b: a + b),
1205 1205 "-": lambda e, c: buildarithmetic(e, c, lambda a, b: a - b),
1206 1206 "negate": buildnegate,
1207 1207 "*": lambda e, c: buildarithmetic(e, c, lambda a, b: a * b),
1208 1208 "/": lambda e, c: buildarithmetic(e, c, lambda a, b: a // b),
1209 1209 }
1210 1210
1211 1211 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})
1212 1212 methods = exprmethods.copy()
1213 1213 methods["integer"] = exprmethods["symbol"] # '{1}' as variable
1214 1214
1215 1215 class _aliasrules(parser.basealiasrules):
1216 1216 """Parsing and expansion rule set of template aliases"""
1217 1217 _section = _('template alias')
1218 1218 _parse = staticmethod(_parseexpr)
1219 1219
1220 1220 @staticmethod
1221 1221 def _trygetfunc(tree):
1222 1222 """Return (name, args) if tree is func(...) or ...|filter; otherwise
1223 1223 None"""
1224 1224 if tree[0] == 'func' and tree[1][0] == 'symbol':
1225 1225 return tree[1][1], getlist(tree[2])
1226 1226 if tree[0] == '|' and tree[2][0] == 'symbol':
1227 1227 return tree[2][1], [tree[1]]
1228 1228
1229 1229 def expandaliases(tree, aliases):
1230 1230 """Return new tree of aliases are expanded"""
1231 1231 aliasmap = _aliasrules.buildmap(aliases)
1232 1232 return _aliasrules.expand(aliasmap, tree)
1233 1233
1234 1234 # template engine
1235 1235
1236 1236 stringify = templatefilters.stringify
1237 1237
1238 1238 def _flatten(thing):
1239 1239 '''yield a single stream from a possibly nested set of iterators'''
1240 1240 thing = templatekw.unwraphybrid(thing)
1241 1241 if isinstance(thing, bytes):
1242 1242 yield thing
1243 1243 elif thing is None:
1244 1244 pass
1245 1245 elif not util.safehasattr(thing, '__iter__'):
1246 1246 yield pycompat.bytestr(thing)
1247 1247 else:
1248 1248 for i in thing:
1249 1249 i = templatekw.unwraphybrid(i)
1250 1250 if isinstance(i, bytes):
1251 1251 yield i
1252 1252 elif i is None:
1253 1253 pass
1254 1254 elif not util.safehasattr(i, '__iter__'):
1255 1255 yield pycompat.bytestr(i)
1256 1256 else:
1257 1257 for j in _flatten(i):
1258 1258 yield j
1259 1259
1260 1260 def unquotestring(s):
1261 1261 '''unwrap quotes if any; otherwise returns unmodified string'''
1262 1262 if len(s) < 2 or s[0] not in "'\"" or s[0] != s[-1]:
1263 1263 return s
1264 1264 return s[1:-1]
1265 1265
1266 1266 class engine(object):
1267 1267 '''template expansion engine.
1268 1268
1269 1269 template expansion works like this. a map file contains key=value
1270 1270 pairs. if value is quoted, it is treated as string. otherwise, it
1271 1271 is treated as name of template file.
1272 1272
1273 1273 templater is asked to expand a key in map. it looks up key, and
1274 1274 looks for strings like this: {foo}. it expands {foo} by looking up
1275 1275 foo in map, and substituting it. expansion is recursive: it stops
1276 1276 when there is no more {foo} to replace.
1277 1277
1278 1278 expansion also allows formatting and filtering.
1279 1279
1280 1280 format uses key to expand each item in list. syntax is
1281 1281 {key%format}.
1282 1282
1283 1283 filter uses function to transform value. syntax is
1284 1284 {key|filter1|filter2|...}.'''
1285 1285
1286 1286 def __init__(self, loader, filters=None, defaults=None, aliases=()):
1287 1287 self._loader = loader
1288 1288 if filters is None:
1289 1289 filters = {}
1290 1290 self._filters = filters
1291 1291 if defaults is None:
1292 1292 defaults = {}
1293 1293 self._defaults = defaults
1294 1294 self._aliasmap = _aliasrules.buildmap(aliases)
1295 1295 self._cache = {} # key: (func, data)
1296 1296
1297 1297 def _load(self, t):
1298 1298 '''load, parse, and cache a template'''
1299 1299 if t not in self._cache:
1300 1300 # put poison to cut recursion while compiling 't'
1301 1301 self._cache[t] = (_runrecursivesymbol, t)
1302 1302 try:
1303 1303 x = parse(self._loader(t))
1304 1304 if self._aliasmap:
1305 1305 x = _aliasrules.expand(self._aliasmap, x)
1306 1306 self._cache[t] = compileexp(x, self, methods)
1307 1307 except: # re-raises
1308 1308 del self._cache[t]
1309 1309 raise
1310 1310 return self._cache[t]
1311 1311
1312 1312 def process(self, t, mapping):
1313 1313 '''Perform expansion. t is name of map element to expand.
1314 1314 mapping contains added elements for use during expansion. Is a
1315 1315 generator.'''
1316 1316 func, data = self._load(t)
1317 1317 return _flatten(func(self, mapping, data))
1318 1318
1319 1319 engines = {'default': engine}
1320 1320
1321 1321 def stylelist():
1322 1322 paths = templatepaths()
1323 1323 if not paths:
1324 1324 return _('no templates found, try `hg debuginstall` for more info')
1325 1325 dirlist = os.listdir(paths[0])
1326 1326 stylelist = []
1327 1327 for file in dirlist:
1328 1328 split = file.split(".")
1329 1329 if split[-1] in ('orig', 'rej'):
1330 1330 continue
1331 1331 if split[0] == "map-cmdline":
1332 1332 stylelist.append(split[1])
1333 1333 return ", ".join(sorted(stylelist))
1334 1334
1335 1335 def _readmapfile(mapfile):
1336 1336 """Load template elements from the given map file"""
1337 1337 if not os.path.exists(mapfile):
1338 1338 raise error.Abort(_("style '%s' not found") % mapfile,
1339 1339 hint=_("available styles: %s") % stylelist())
1340 1340
1341 1341 base = os.path.dirname(mapfile)
1342 1342 conf = config.config(includepaths=templatepaths())
1343 conf.read(mapfile)
1343 conf.read(mapfile, remap={'': 'templates'})
1344 1344
1345 1345 cache = {}
1346 1346 tmap = {}
1347 1347
1348 val = conf.get('', '__base__')
1348 val = conf.get('templates', '__base__')
1349 1349 if val and val[0] not in "'\"":
1350 1350 # treat as a pointer to a base class for this style
1351 1351 path = util.normpath(os.path.join(base, val))
1352 1352
1353 1353 # fallback check in template paths
1354 1354 if not os.path.exists(path):
1355 1355 for p in templatepaths():
1356 1356 p2 = util.normpath(os.path.join(p, val))
1357 1357 if os.path.isfile(p2):
1358 1358 path = p2
1359 1359 break
1360 1360 p3 = util.normpath(os.path.join(p2, "map"))
1361 1361 if os.path.isfile(p3):
1362 1362 path = p3
1363 1363 break
1364 1364
1365 1365 cache, tmap = _readmapfile(path)
1366 1366
1367 for key, val in conf[''].items():
1367 for key, val in conf['templates'].items():
1368 1368 if not val:
1369 raise error.ParseError(_('missing value'), conf.source('', key))
1369 raise error.ParseError(_('missing value'),
1370 conf.source('templates', key))
1370 1371 if val[0] in "'\"":
1371 1372 if val[0] != val[-1]:
1372 1373 raise error.ParseError(_('unmatched quotes'),
1373 conf.source('', key))
1374 conf.source('templates', key))
1374 1375 cache[key] = unquotestring(val)
1375 1376 elif key != '__base__':
1376 1377 val = 'default', val
1377 1378 if ':' in val[1]:
1378 1379 val = val[1].split(':', 1)
1379 1380 tmap[key] = val[0], os.path.join(base, val[1])
1380 1381 return cache, tmap
1381 1382
1382 1383 class TemplateNotFound(error.Abort):
1383 1384 pass
1384 1385
1385 1386 class templater(object):
1386 1387
1387 1388 def __init__(self, filters=None, defaults=None, cache=None, aliases=(),
1388 1389 minchunk=1024, maxchunk=65536):
1389 1390 '''set up template engine.
1390 1391 filters is dict of functions. each transforms a value into another.
1391 1392 defaults is dict of default map definitions.
1392 1393 aliases is list of alias (name, replacement) pairs.
1393 1394 '''
1394 1395 if filters is None:
1395 1396 filters = {}
1396 1397 if defaults is None:
1397 1398 defaults = {}
1398 1399 if cache is None:
1399 1400 cache = {}
1400 1401 self.cache = cache.copy()
1401 1402 self.map = {}
1402 1403 self.filters = templatefilters.filters.copy()
1403 1404 self.filters.update(filters)
1404 1405 self.defaults = defaults
1405 1406 self._aliases = aliases
1406 1407 self.minchunk, self.maxchunk = minchunk, maxchunk
1407 1408 self.ecache = {}
1408 1409
1409 1410 @classmethod
1410 1411 def frommapfile(cls, mapfile, filters=None, defaults=None, cache=None,
1411 1412 minchunk=1024, maxchunk=65536):
1412 1413 """Create templater from the specified map file"""
1413 1414 t = cls(filters, defaults, cache, [], minchunk, maxchunk)
1414 1415 cache, tmap = _readmapfile(mapfile)
1415 1416 t.cache.update(cache)
1416 1417 t.map = tmap
1417 1418 return t
1418 1419
1419 1420 def __contains__(self, key):
1420 1421 return key in self.cache or key in self.map
1421 1422
1422 1423 def load(self, t):
1423 1424 '''Get the template for the given template name. Use a local cache.'''
1424 1425 if t not in self.cache:
1425 1426 try:
1426 1427 self.cache[t] = util.readfile(self.map[t][1])
1427 1428 except KeyError as inst:
1428 1429 raise TemplateNotFound(_('"%s" not in template map') %
1429 1430 inst.args[0])
1430 1431 except IOError as inst:
1431 1432 raise IOError(inst.args[0], _('template file %s: %s') %
1432 1433 (self.map[t][1], inst.args[1]))
1433 1434 return self.cache[t]
1434 1435
1435 1436 def render(self, mapping):
1436 1437 """Render the default unnamed template and return result as string"""
1437 1438 mapping = pycompat.strkwargs(mapping)
1438 1439 return stringify(self('', **mapping))
1439 1440
1440 1441 def __call__(self, t, **mapping):
1441 1442 mapping = pycompat.byteskwargs(mapping)
1442 1443 ttype = t in self.map and self.map[t][0] or 'default'
1443 1444 if ttype not in self.ecache:
1444 1445 try:
1445 1446 ecls = engines[ttype]
1446 1447 except KeyError:
1447 1448 raise error.Abort(_('invalid template engine: %s') % ttype)
1448 1449 self.ecache[ttype] = ecls(self.load, self.filters, self.defaults,
1449 1450 self._aliases)
1450 1451 proc = self.ecache[ttype]
1451 1452
1452 1453 stream = proc.process(t, mapping)
1453 1454 if self.minchunk:
1454 1455 stream = util.increasingchunks(stream, min=self.minchunk,
1455 1456 max=self.maxchunk)
1456 1457 return stream
1457 1458
1458 1459 def templatepaths():
1459 1460 '''return locations used for template files.'''
1460 1461 pathsrel = ['templates']
1461 1462 paths = [os.path.normpath(os.path.join(util.datapath, f))
1462 1463 for f in pathsrel]
1463 1464 return [p for p in paths if os.path.isdir(p)]
1464 1465
1465 1466 def templatepath(name):
1466 1467 '''return location of template file. returns None if not found.'''
1467 1468 for p in templatepaths():
1468 1469 f = os.path.join(p, name)
1469 1470 if os.path.exists(f):
1470 1471 return f
1471 1472 return None
1472 1473
1473 1474 def stylemap(styles, paths=None):
1474 1475 """Return path to mapfile for a given style.
1475 1476
1476 1477 Searches mapfile in the following locations:
1477 1478 1. templatepath/style/map
1478 1479 2. templatepath/map-style
1479 1480 3. templatepath/map
1480 1481 """
1481 1482
1482 1483 if paths is None:
1483 1484 paths = templatepaths()
1484 1485 elif isinstance(paths, str):
1485 1486 paths = [paths]
1486 1487
1487 1488 if isinstance(styles, str):
1488 1489 styles = [styles]
1489 1490
1490 1491 for style in styles:
1491 1492 # only plain name is allowed to honor template paths
1492 1493 if (not style
1493 1494 or style in (os.curdir, os.pardir)
1494 1495 or pycompat.ossep in style
1495 1496 or pycompat.osaltsep and pycompat.osaltsep in style):
1496 1497 continue
1497 1498 locations = [os.path.join(style, 'map'), 'map-' + style]
1498 1499 locations.append('map')
1499 1500
1500 1501 for path in paths:
1501 1502 for location in locations:
1502 1503 mapfile = os.path.join(path, location)
1503 1504 if os.path.isfile(mapfile):
1504 1505 return style, mapfile
1505 1506
1506 1507 raise RuntimeError("No hgweb templates found in %r" % paths)
1507 1508
1508 1509 def loadfunction(ui, extname, registrarobj):
1509 1510 """Load template function from specified registrarobj
1510 1511 """
1511 1512 for name, func in registrarobj._table.iteritems():
1512 1513 funcs[name] = func
1513 1514
1514 1515 # tell hggettext to extract docstrings from these functions:
1515 1516 i18nfunctions = funcs.values()
@@ -1,14 +1,15 b''
1 1 %include map-cmdline.default
2 2
3 [templates]
3 4 changeset = '{cset}{lbisect}{branches}{bookmarks}{tags}{parents}{user}{ldate}{summary}\n'
4 5 changeset_quiet = '{lshortbisect} {rev}:{node|short}\n'
5 6 changeset_verbose = '{cset}{lbisect}{branches}{bookmarks}{tags}{parents}{user}{ldate}{lfiles}{lfile_copies_switch}{description}\n'
6 7 changeset_debug = '{fullcset}{lbisect}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{lfile_mods}{lfile_adds}{lfile_dels}{lfile_copies_switch}{extras}{description}\n'
7 8
8 9 # We take the zeroth word in order to omit "(implicit)" in the label
9 10 bisectlabel = ' bisect.{word('0', bisect)}'
10 11
11 12 lbisect ='{label("log.bisect{if(bisect, bisectlabel)}",
12 13 "bisect: {bisect}\n")}'
13 14 lshortbisect ='{label("log.bisect{if(bisect, bisectlabel)}",
14 15 "{bisect|shortbisect}")}'
@@ -1,17 +1,18 b''
1 [templates]
1 2 header = '{date|shortdate} {author|person} <{author|email}>\n\n'
2 3 header_verbose = ''
3 4 changeset = '\t* {files|stringify|fill68|tabindent}{desc|fill68|tabindent|strip}\n\t[{node|short}]{tags}{branches}\n\n'
4 5 changeset_quiet = '\t* {desc|firstline|fill68|tabindent|strip}\n\n'
5 6 changeset_verbose = '{date|isodate} {author|person} <{author|email}> ({node|short}{tags}{branches})\n\n\t* {file_adds|stringify|fill68|tabindent}{file_dels|stringify|fill68|tabindent}{files|stringify|fill68|tabindent}{desc|fill68|tabindent|strip}\n\n'
6 7 start_tags = ' ['
7 8 tag = '{tag}, '
8 9 last_tag = '{tag}]'
9 10 start_branches = ' <'
10 11 branch = '{branch}, '
11 12 last_branch = '{branch}>'
12 13 file = '{file}, '
13 14 last_file = '{file}:\n\t'
14 15 file_add = '{file_add}, '
15 16 last_file_add = '{file_add}: new file.\n* '
16 17 file_del = '{file_del}, '
17 18 last_file_del = '{file_del}: deleted file.\n* '
@@ -1,30 +1,31 b''
1 [templates]
1 2 ldate = '{label("log.date",
2 3 "{date|isodate}")}'
3 4
4 5 ldesc = '{label('ui.note log.description',
5 6 '{desc|strip}')}'
6 7 ldescfirst = '{label('ui.note log.description',
7 8 '{desc|firstline|strip}')}'
8 9
9 10 changeset = '{lrev}{tags}{bookmarks}{parents} {lnode} {ldate} {luser}\n {ldescfirst}\n\n'
10 11 changeset_quiet = '{lrev}:{lnode}\n'
11 12 changeset_verbose = '{lrev}{tags}{parents} {lnode} {ldate} {lauthor}\n {ldesc}\n\n'
12 13 lrev = '{label("log.changeset changeset.{phase}",
13 14 "{rev}")}'
14 15 lnode = '{label("log.node",
15 16 "{node|short}")}'
16 17 lauthor = '{label("log.user",
17 18 "{author}")}'
18 19 luser = '{label("log.user",
19 20 "{author|user}")}'
20 21 start_tags = '['
21 22 tag = '{label("log.tag",
22 23 "{tag},")}'
23 24 last_tag = '{tag}]'
24 25 start_parents = ':'
25 26 parent = '{lrev},'
26 27 last_parent = '{lrev}'
27 28 start_bookmarks = '['
28 29 bookmark = '{label("log.bookmark",
29 30 "{bookmark},")}'
30 31 last_bookmark = '{bookmark}]'
@@ -1,82 +1,84 b''
1 1 # Base templates. Due to name clashes with existing keywords, we have
2 2 # to replace some keywords with 'lkeyword', for 'labelled keyword'
3
4 [templates]
3 5 changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{summary}\n'
4 6 changeset_quiet = '{lnode}'
5 7 changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{lfiles}{lfile_copies_switch}{description}\n'
6 8 changeset_debug = '{fullcset}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{ltroubles}{lfile_mods}{lfile_adds}{lfile_dels}{lfile_copies_switch}{extras}{description}\n'
7 9
8 10 # File templates
9 11 lfiles = '{if(files,
10 12 label("ui.note log.files",
11 13 "files: {files}\n"))}'
12 14
13 15 lfile_mods = '{if(file_mods,
14 16 label("ui.debug log.files",
15 17 "files: {file_mods}\n"))}'
16 18
17 19 lfile_adds = '{if(file_adds,
18 20 label("ui.debug log.files",
19 21 "files+: {file_adds}\n"))}'
20 22
21 23 lfile_dels = '{if(file_dels,
22 24 label("ui.debug log.files",
23 25 "files-: {file_dels}\n"))}'
24 26
25 27 lfile_copies_switch = '{if(file_copies_switch,
26 28 label("ui.note log.copies",
27 29 "copies: {file_copies_switch
28 30 % ' {name} ({source})'}\n"))}'
29 31
30 32 # General templates
31 33 _instability_label = 'instability.{instability}'
32 34 _troubles_labels = '{if(instabilities, "changeset.unstable {instabilities%_instability_label}")}'
33 35 _obsolete_label = '{if(obsolete, "changeset.obsolete")}'
34 36 _cset_labels = '{separate(" ", "log.changeset", "changeset.{phase}", "{_obsolete_label}", "{_troubles_labels}")}'
35 37 cset = '{label("{_cset_labels}",
36 38 "changeset: {rev}:{node|short}")}\n'
37 39
38 40 lphase = '{label("log.phase",
39 41 "phase: {phase}")}\n'
40 42
41 43 fullcset = '{label("{_cset_labels}",
42 44 "changeset: {rev}:{node}")}\n'
43 45
44 46 parent = '{label("log.parent changeset.{phase}",
45 47 "parent: {rev}:{node|formatnode}")}\n'
46 48
47 49 lnode = '{label("log.node",
48 50 "{rev}:{node|short}")}\n'
49 51
50 52 manifest = '{label("ui.debug log.manifest",
51 53 "manifest: {rev}:{node}")}\n'
52 54
53 55 branch = '{label("log.branch",
54 56 "branch: {branch}")}\n'
55 57
56 58 tag = '{label("log.tag",
57 59 "tag: {tag}")}\n'
58 60
59 61 bookmark = '{label("log.bookmark",
60 62 "bookmark: {bookmark}")}\n'
61 63
62 64 user = '{label("log.user",
63 65 "user: {author}")}\n'
64 66
65 67 summary = '{if(desc|strip, "{label('log.summary',
66 68 'summary: {desc|firstline}')}\n")}'
67 69
68 70 ldate = '{label("log.date",
69 71 "date: {date|date}")}\n'
70 72
71 73 ltroubles = '{if(instabilities, "{label('log.instability',
72 74 'instability: {join(instabilities, ", ")}')}\n")}'
73 75
74 76 extra = '{label("ui.debug log.extra",
75 77 "extra: {key}={value|stringescape}")}\n'
76 78
77 79 description = '{if(desc|strip, "{label('ui.note log.description',
78 80 'description:')}
79 81 {label('ui.note log.description',
80 82 '{desc|strip}')}\n\n")}'
81 83
82 84 status = '{status} {path}\n{if(copy, " {copy}\n")}'
@@ -1,3 +1,5 b''
1 1 %include map-cmdline.default
2
3 [templates]
2 4 changeset = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{user}{ldate}{summary}\n'
3 5 changeset_verbose = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{user}{ldate}{lfiles}{lfile_copies_switch}{description}\n'
@@ -1,19 +1,21 b''
1 1 # TODO there are a few deficiencies in this file:
2 2 # * The "namespace" of the labels needs to be worked out. We currently
3 3 # piggyback on existing values so color works.
4 4 # * Obsolescence isn't considered for node labels. See _cset_labels in
5 5 # map-cmdline.default.
6
7 [templates]
6 8 showbookmarks = '{if(active, "*", " ")} {pad(bookmark, longestbookmarklen + 4)}{shortest(node, nodelen)}\n'
7 9
8 10 showwork = '{cset_shortnode}{namespaces % cset_namespace} {cset_shortdesc}'
9 11 showstack = '{showwork}'
10 12
11 13 cset_shortnode = '{label("log.changeset changeset.{phase}", shortest(node, nodelen))}'
12 14
13 15 # Treat branch and tags specially so we don't display "default" or "tip"
14 16 cset_namespace = '{ifeq(namespace, "branches", names_branches, ifeq(namespace, "tags", names_tags, names_others))}'
15 17 names_branches = '{ifeq(branch, "default", "", " ({label('log.{colorname}', branch)})")}'
16 18 names_tags = '{if(names % "{ifeq(name, 'tip', '', name)}", " ({label('log.{colorname}', join(names % "{ifeq(name, 'tip', '', name)}", ' '))})")}'
17 19 names_others = '{if(names, " ({label('log.{colorname}', join(names, ' '))})")}'
18 20
19 21 cset_shortdesc = '{label("log.description", desc|firstline)}'
@@ -1,20 +1,21 b''
1 1 %include map-cmdline.default
2 2
3 [templates]
3 4 # Override base templates
4 5 changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{summary}{lfiles}\n'
5 6 changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{description}{lfiles}\n'
6 7 changeset_debug = '{fullcset}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{extras}{description}{lfiles}\n'
7 8
8 9 # Override the file templates
9 10 lfiles = '{if(files,
10 11 label('ui.note log.files',
11 12 'files:\n'))}{lfile_mods}{lfile_adds}{lfile_dels}'
12 13
13 14 lfile_adds = '{file_adds % "{lfile_add}{lfile_src}"}'
14 15 lfile_mods = '{file_mods % "{lfile_mod}{lfile_src}"}'
15 16 lfile_add = '{label("status.added", "A {file}\n")}'
16 17 lfile_mod = '{label("status.modified", "M {file}\n")}'
17 18 lfile_src = '{ifcontains(file, file_copies_switch,
18 19 label("status.copied", " {get(file_copies_switch, file)}\n"))}'
19 20
20 21 lfile_dels = '{file_dels % "{label('status.removed', 'R {file}\n')}"}'
@@ -1,20 +1,21 b''
1 [templates]
1 2 docheader = '<?xml version="1.0"?>\n<log>\n'
2 3 docfooter = '</log>\n'
3 4
4 5 changeset = '<logentry revision="{rev}" node="{node}">\n{branches}{bookmarks}{tags}{parents}<author email="{author|email|xmlescape}">{author|person|xmlescape}</author>\n<date>{date|rfc3339date}</date>\n<msg xml:space="preserve">{desc|xmlescape}</msg>\n</logentry>\n'
5 6 changeset_verbose = '<logentry revision="{rev}" node="{node}">\n{branches}{bookmarks}{tags}{parents}<author email="{author|email|xmlescape}">{author|person|xmlescape}</author>\n<date>{date|rfc3339date}</date>\n<msg xml:space="preserve">{desc|xmlescape}</msg>\n<paths>\n{file_adds}{file_dels}{file_mods}</paths>\n{file_copies}</logentry>\n'
6 7 changeset_debug = '<logentry revision="{rev}" node="{node}">\n{branches}{bookmarks}{tags}{parents}<author email="{author|email|xmlescape}">{author|person|xmlescape}</author>\n<date>{date|rfc3339date}</date>\n<msg xml:space="preserve">{desc|xmlescape}</msg>\n<paths>\n{file_adds}{file_dels}{file_mods}</paths>\n{file_copies}{extras}</logentry>\n'
7 8
8 9 file_add = '<path action="A">{file_add|xmlescape}</path>\n'
9 10 file_mod = '<path action="M">{file_mod|xmlescape}</path>\n'
10 11 file_del = '<path action="R">{file_del|xmlescape}</path>\n'
11 12
12 13 start_file_copies = '<copies>\n'
13 14 file_copy = '<copy source="{source|xmlescape}">{name|xmlescape}</copy>\n'
14 15 end_file_copies = '</copies>\n'
15 16
16 17 parent = '<parent revision="{rev}" node="{node}" />\n'
17 18 branch = '<branch>{branch|xmlescape}</branch>\n'
18 19 tag = '<tag>{tag|xmlescape}</tag>\n'
19 20 bookmark = '<bookmark>{bookmark|xmlescape}</bookmark>\n'
20 21 extra = '<extra key="{key|xmlescape}">{value|xmlescape}</extra>\n'
@@ -1,4673 +1,4692 b''
1 1 $ hg init a
2 2 $ cd a
3 3 $ echo a > a
4 4 $ hg add a
5 5 $ echo line 1 > b
6 6 $ echo line 2 >> b
7 7 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
8 8
9 9 $ hg add b
10 10 $ echo other 1 > c
11 11 $ echo other 2 >> c
12 12 $ echo >> c
13 13 $ echo other 3 >> c
14 14 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
15 15
16 16 $ hg add c
17 17 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
18 18 $ echo c >> c
19 19 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
20 20
21 21 $ echo foo > .hg/branch
22 22 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
23 23
24 24 $ hg co -q 3
25 25 $ echo other 4 >> d
26 26 $ hg add d
27 27 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
28 28
29 29 $ hg merge -q foo
30 30 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
31 31
32 32 Test arithmetic operators have the right precedence:
33 33
34 34 $ hg log -l 1 -T '{date(date, "%Y") + 5 * 10} {date(date, "%Y") - 2 * 3}\n'
35 35 2020 1964
36 36 $ hg log -l 1 -T '{date(date, "%Y") * 5 + 10} {date(date, "%Y") * 3 - 2}\n'
37 37 9860 5908
38 38
39 39 Test division:
40 40
41 41 $ hg debugtemplate -r0 -v '{5 / 2} {mod(5, 2)}\n'
42 42 (template
43 43 (/
44 44 (integer '5')
45 45 (integer '2'))
46 46 (string ' ')
47 47 (func
48 48 (symbol 'mod')
49 49 (list
50 50 (integer '5')
51 51 (integer '2')))
52 52 (string '\n'))
53 53 2 1
54 54 $ hg debugtemplate -r0 -v '{5 / -2} {mod(5, -2)}\n'
55 55 (template
56 56 (/
57 57 (integer '5')
58 58 (negate
59 59 (integer '2')))
60 60 (string ' ')
61 61 (func
62 62 (symbol 'mod')
63 63 (list
64 64 (integer '5')
65 65 (negate
66 66 (integer '2'))))
67 67 (string '\n'))
68 68 -3 -1
69 69 $ hg debugtemplate -r0 -v '{-5 / 2} {mod(-5, 2)}\n'
70 70 (template
71 71 (/
72 72 (negate
73 73 (integer '5'))
74 74 (integer '2'))
75 75 (string ' ')
76 76 (func
77 77 (symbol 'mod')
78 78 (list
79 79 (negate
80 80 (integer '5'))
81 81 (integer '2')))
82 82 (string '\n'))
83 83 -3 1
84 84 $ hg debugtemplate -r0 -v '{-5 / -2} {mod(-5, -2)}\n'
85 85 (template
86 86 (/
87 87 (negate
88 88 (integer '5'))
89 89 (negate
90 90 (integer '2')))
91 91 (string ' ')
92 92 (func
93 93 (symbol 'mod')
94 94 (list
95 95 (negate
96 96 (integer '5'))
97 97 (negate
98 98 (integer '2'))))
99 99 (string '\n'))
100 100 2 -1
101 101
102 102 Filters bind closer than arithmetic:
103 103
104 104 $ hg debugtemplate -r0 -v '{revset(".")|count - 1}\n'
105 105 (template
106 106 (-
107 107 (|
108 108 (func
109 109 (symbol 'revset')
110 110 (string '.'))
111 111 (symbol 'count'))
112 112 (integer '1'))
113 113 (string '\n'))
114 114 0
115 115
116 116 But negate binds closer still:
117 117
118 118 $ hg debugtemplate -r0 -v '{1-3|stringify}\n'
119 119 (template
120 120 (-
121 121 (integer '1')
122 122 (|
123 123 (integer '3')
124 124 (symbol 'stringify')))
125 125 (string '\n'))
126 126 hg: parse error: arithmetic only defined on integers
127 127 [255]
128 128 $ hg debugtemplate -r0 -v '{-3|stringify}\n'
129 129 (template
130 130 (|
131 131 (negate
132 132 (integer '3'))
133 133 (symbol 'stringify'))
134 134 (string '\n'))
135 135 -3
136 136
137 137 Filters bind as close as map operator:
138 138
139 139 $ hg debugtemplate -r0 -v '{desc|splitlines % "{line}\n"}'
140 140 (template
141 141 (%
142 142 (|
143 143 (symbol 'desc')
144 144 (symbol 'splitlines'))
145 145 (template
146 146 (symbol 'line')
147 147 (string '\n'))))
148 148 line 1
149 149 line 2
150 150
151 151 Keyword arguments:
152 152
153 153 $ hg debugtemplate -r0 -v '{foo=bar|baz}'
154 154 (template
155 155 (keyvalue
156 156 (symbol 'foo')
157 157 (|
158 158 (symbol 'bar')
159 159 (symbol 'baz'))))
160 160 hg: parse error: can't use a key-value pair in this context
161 161 [255]
162 162
163 163 $ hg debugtemplate '{pad("foo", width=10, left=true)}\n'
164 164 foo
165 165
166 166 Call function which takes named arguments by filter syntax:
167 167
168 168 $ hg debugtemplate '{" "|separate}'
169 169 $ hg debugtemplate '{("not", "an", "argument", "list")|separate}'
170 170 hg: parse error: unknown method 'list'
171 171 [255]
172 172
173 173 Second branch starting at nullrev:
174 174
175 175 $ hg update null
176 176 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
177 177 $ echo second > second
178 178 $ hg add second
179 179 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
180 180 created new head
181 181
182 182 $ echo third > third
183 183 $ hg add third
184 184 $ hg mv second fourth
185 185 $ hg commit -m third -d "2020-01-01 10:01"
186 186
187 187 $ hg log --template '{join(file_copies, ",\n")}\n' -r .
188 188 fourth (second)
189 189 $ hg log -T '{file_copies % "{source} -> {name}\n"}' -r .
190 190 second -> fourth
191 191 $ hg log -T '{rev} {ifcontains("fourth", file_copies, "t", "f")}\n' -r .:7
192 192 8 t
193 193 7 f
194 194
195 195 Working-directory revision has special identifiers, though they are still
196 196 experimental:
197 197
198 198 $ hg log -r 'wdir()' -T '{rev}:{node}\n'
199 199 2147483647:ffffffffffffffffffffffffffffffffffffffff
200 200
201 201 Some keywords are invalid for working-directory revision, but they should
202 202 never cause crash:
203 203
204 204 $ hg log -r 'wdir()' -T '{manifest}\n'
205 205
206 206
207 207 Quoting for ui.logtemplate
208 208
209 209 $ hg tip --config "ui.logtemplate={rev}\n"
210 210 8
211 211 $ hg tip --config "ui.logtemplate='{rev}\n'"
212 212 8
213 213 $ hg tip --config 'ui.logtemplate="{rev}\n"'
214 214 8
215 215 $ hg tip --config 'ui.logtemplate=n{rev}\n'
216 216 n8
217 217
218 218 Make sure user/global hgrc does not affect tests
219 219
220 220 $ echo '[ui]' > .hg/hgrc
221 221 $ echo 'logtemplate =' >> .hg/hgrc
222 222 $ echo 'style =' >> .hg/hgrc
223 223
224 224 Add some simple styles to settings
225 225
226 226 $ cat <<'EOF' >> .hg/hgrc
227 227 > [templates]
228 228 > simple = "{rev}\n"
229 229 > simple2 = {rev}\n
230 230 > rev = "should not precede {rev} keyword\n"
231 231 > EOF
232 232
233 233 $ hg log -l1 -Tsimple
234 234 8
235 235 $ hg log -l1 -Tsimple2
236 236 8
237 237 $ hg log -l1 -Trev
238 238 should not precede 8 keyword
239 239 $ hg log -l1 -T '{simple}'
240 240 8
241 241
242 242 Map file shouldn't see user templates:
243 243
244 244 $ cat <<EOF > tmpl
245 245 > changeset = 'nothing expanded:{simple}\n'
246 246 > EOF
247 247 $ hg log -l1 --style ./tmpl
248 248 nothing expanded:
249 249
250 250 Test templates and style maps in files:
251 251
252 252 $ echo "{rev}" > tmpl
253 253 $ hg log -l1 -T./tmpl
254 254 8
255 255 $ hg log -l1 -Tblah/blah
256 256 blah/blah (no-eol)
257 257
258 258 $ printf 'changeset = "{rev}\\n"\n' > map-simple
259 259 $ hg log -l1 -T./map-simple
260 260 8
261 261
262 a map file may have [templates] section:
263
264 $ cat <<'EOF' > map-simple
265 > [templates]
266 > changeset = "{rev}\n"
267 > EOF
268 $ hg log -l1 -T./map-simple
269 8
270
271 so it can be included in hgrc
272
273 $ cat <<'EOF' > myhgrc
274 > %include map-simple
275 > [templates]
276 > foo = "{changeset}"
277 > EOF
278 $ HGRCPATH=./myhgrc hg log -l1 -Tfoo
279 8
280
262 281 Test template map inheritance
263 282
264 283 $ echo "__base__ = map-cmdline.default" > map-simple
265 284 $ printf 'cset = "changeset: ***{rev}***\\n"\n' >> map-simple
266 285 $ hg log -l1 -T./map-simple
267 286 changeset: ***8***
268 287 tag: tip
269 288 user: test
270 289 date: Wed Jan 01 10:01:00 2020 +0000
271 290 summary: third
272 291
273 292
274 293 Test docheader, docfooter and separator in template map
275 294
276 295 $ cat <<'EOF' > map-myjson
277 296 > docheader = '\{\n'
278 297 > docfooter = '\n}\n'
279 298 > separator = ',\n'
280 299 > changeset = ' {dict(rev, node|short)|json}'
281 300 > EOF
282 301 $ hg log -l2 -T./map-myjson
283 302 {
284 303 {"node": "95c24699272e", "rev": 8},
285 304 {"node": "29114dbae42b", "rev": 7}
286 305 }
287 306
288 307 Test docheader, docfooter and separator in [templates] section
289 308
290 309 $ cat <<'EOF' >> .hg/hgrc
291 310 > [templates]
292 311 > myjson = ' {dict(rev, node|short)|json}'
293 312 > myjson:docheader = '\{\n'
294 313 > myjson:docfooter = '\n}\n'
295 314 > myjson:separator = ',\n'
296 315 > :docheader = 'should not be selected as a docheader for literal templates\n'
297 316 > EOF
298 317 $ hg log -l2 -Tmyjson
299 318 {
300 319 {"node": "95c24699272e", "rev": 8},
301 320 {"node": "29114dbae42b", "rev": 7}
302 321 }
303 322 $ hg log -l1 -T'{rev}\n'
304 323 8
305 324
306 325 Template should precede style option
307 326
308 327 $ hg log -l1 --style default -T '{rev}\n'
309 328 8
310 329
311 330 Add a commit with empty description, to ensure that the templates
312 331 below will omit the description line.
313 332
314 333 $ echo c >> c
315 334 $ hg add c
316 335 $ hg commit -qm ' '
317 336
318 337 Default style is like normal output. Phases style should be the same
319 338 as default style, except for extra phase lines.
320 339
321 340 $ hg log > log.out
322 341 $ hg log --style default > style.out
323 342 $ cmp log.out style.out || diff -u log.out style.out
324 343 $ hg log -T phases > phases.out
325 344 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
326 345 +phase: draft
327 346 +phase: draft
328 347 +phase: draft
329 348 +phase: draft
330 349 +phase: draft
331 350 +phase: draft
332 351 +phase: draft
333 352 +phase: draft
334 353 +phase: draft
335 354 +phase: draft
336 355
337 356 $ hg log -v > log.out
338 357 $ hg log -v --style default > style.out
339 358 $ cmp log.out style.out || diff -u log.out style.out
340 359 $ hg log -v -T phases > phases.out
341 360 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
342 361 +phase: draft
343 362 +phase: draft
344 363 +phase: draft
345 364 +phase: draft
346 365 +phase: draft
347 366 +phase: draft
348 367 +phase: draft
349 368 +phase: draft
350 369 +phase: draft
351 370 +phase: draft
352 371
353 372 $ hg log -q > log.out
354 373 $ hg log -q --style default > style.out
355 374 $ cmp log.out style.out || diff -u log.out style.out
356 375 $ hg log -q -T phases > phases.out
357 376 $ cmp log.out phases.out || diff -u log.out phases.out
358 377
359 378 $ hg log --debug > log.out
360 379 $ hg log --debug --style default > style.out
361 380 $ cmp log.out style.out || diff -u log.out style.out
362 381 $ hg log --debug -T phases > phases.out
363 382 $ cmp log.out phases.out || diff -u log.out phases.out
364 383
365 384 Default style of working-directory revision should also be the same (but
366 385 date may change while running tests):
367 386
368 387 $ hg log -r 'wdir()' | sed 's|^date:.*|date:|' > log.out
369 388 $ hg log -r 'wdir()' --style default | sed 's|^date:.*|date:|' > style.out
370 389 $ cmp log.out style.out || diff -u log.out style.out
371 390
372 391 $ hg log -r 'wdir()' -v | sed 's|^date:.*|date:|' > log.out
373 392 $ hg log -r 'wdir()' -v --style default | sed 's|^date:.*|date:|' > style.out
374 393 $ cmp log.out style.out || diff -u log.out style.out
375 394
376 395 $ hg log -r 'wdir()' -q > log.out
377 396 $ hg log -r 'wdir()' -q --style default > style.out
378 397 $ cmp log.out style.out || diff -u log.out style.out
379 398
380 399 $ hg log -r 'wdir()' --debug | sed 's|^date:.*|date:|' > log.out
381 400 $ hg log -r 'wdir()' --debug --style default \
382 401 > | sed 's|^date:.*|date:|' > style.out
383 402 $ cmp log.out style.out || diff -u log.out style.out
384 403
385 404 Default style should also preserve color information (issue2866):
386 405
387 406 $ cp $HGRCPATH $HGRCPATH-bak
388 407 $ cat <<EOF >> $HGRCPATH
389 408 > [extensions]
390 409 > color=
391 410 > EOF
392 411
393 412 $ hg --color=debug log > log.out
394 413 $ hg --color=debug log --style default > style.out
395 414 $ cmp log.out style.out || diff -u log.out style.out
396 415 $ hg --color=debug log -T phases > phases.out
397 416 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
398 417 +[log.phase|phase: draft]
399 418 +[log.phase|phase: draft]
400 419 +[log.phase|phase: draft]
401 420 +[log.phase|phase: draft]
402 421 +[log.phase|phase: draft]
403 422 +[log.phase|phase: draft]
404 423 +[log.phase|phase: draft]
405 424 +[log.phase|phase: draft]
406 425 +[log.phase|phase: draft]
407 426 +[log.phase|phase: draft]
408 427
409 428 $ hg --color=debug -v log > log.out
410 429 $ hg --color=debug -v log --style default > style.out
411 430 $ cmp log.out style.out || diff -u log.out style.out
412 431 $ hg --color=debug -v log -T phases > phases.out
413 432 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
414 433 +[log.phase|phase: draft]
415 434 +[log.phase|phase: draft]
416 435 +[log.phase|phase: draft]
417 436 +[log.phase|phase: draft]
418 437 +[log.phase|phase: draft]
419 438 +[log.phase|phase: draft]
420 439 +[log.phase|phase: draft]
421 440 +[log.phase|phase: draft]
422 441 +[log.phase|phase: draft]
423 442 +[log.phase|phase: draft]
424 443
425 444 $ hg --color=debug -q log > log.out
426 445 $ hg --color=debug -q log --style default > style.out
427 446 $ cmp log.out style.out || diff -u log.out style.out
428 447 $ hg --color=debug -q log -T phases > phases.out
429 448 $ cmp log.out phases.out || diff -u log.out phases.out
430 449
431 450 $ hg --color=debug --debug log > log.out
432 451 $ hg --color=debug --debug log --style default > style.out
433 452 $ cmp log.out style.out || diff -u log.out style.out
434 453 $ hg --color=debug --debug log -T phases > phases.out
435 454 $ cmp log.out phases.out || diff -u log.out phases.out
436 455
437 456 $ mv $HGRCPATH-bak $HGRCPATH
438 457
439 458 Remove commit with empty commit message, so as to not pollute further
440 459 tests.
441 460
442 461 $ hg --config extensions.strip= strip -q .
443 462
444 463 Revision with no copies (used to print a traceback):
445 464
446 465 $ hg tip -v --template '\n'
447 466
448 467
449 468 Compact style works:
450 469
451 470 $ hg log -Tcompact
452 471 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
453 472 third
454 473
455 474 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
456 475 second
457 476
458 477 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
459 478 merge
460 479
461 480 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
462 481 new head
463 482
464 483 4 bbe44766e73d 1970-01-17 04:53 +0000 person
465 484 new branch
466 485
467 486 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
468 487 no user, no domain
469 488
470 489 2 97054abb4ab8 1970-01-14 21:20 +0000 other
471 490 no person
472 491
473 492 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
474 493 other 1
475 494
476 495 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
477 496 line 1
478 497
479 498
480 499 $ hg log -v --style compact
481 500 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
482 501 third
483 502
484 503 7:-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
485 504 second
486 505
487 506 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
488 507 merge
489 508
490 509 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
491 510 new head
492 511
493 512 4 bbe44766e73d 1970-01-17 04:53 +0000 person
494 513 new branch
495 514
496 515 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
497 516 no user, no domain
498 517
499 518 2 97054abb4ab8 1970-01-14 21:20 +0000 other@place
500 519 no person
501 520
502 521 1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
503 522 other 1
504 523 other 2
505 524
506 525 other 3
507 526
508 527 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
509 528 line 1
510 529 line 2
511 530
512 531
513 532 $ hg log --debug --style compact
514 533 8[tip]:7,-1 95c24699272e 2020-01-01 10:01 +0000 test
515 534 third
516 535
517 536 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
518 537 second
519 538
520 539 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
521 540 merge
522 541
523 542 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person
524 543 new head
525 544
526 545 4:3,-1 bbe44766e73d 1970-01-17 04:53 +0000 person
527 546 new branch
528 547
529 548 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
530 549 no user, no domain
531 550
532 551 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other@place
533 552 no person
534 553
535 554 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
536 555 other 1
537 556 other 2
538 557
539 558 other 3
540 559
541 560 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
542 561 line 1
543 562 line 2
544 563
545 564
546 565 Test xml styles:
547 566
548 567 $ hg log --style xml -r 'not all()'
549 568 <?xml version="1.0"?>
550 569 <log>
551 570 </log>
552 571
553 572 $ hg log --style xml
554 573 <?xml version="1.0"?>
555 574 <log>
556 575 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
557 576 <tag>tip</tag>
558 577 <author email="test">test</author>
559 578 <date>2020-01-01T10:01:00+00:00</date>
560 579 <msg xml:space="preserve">third</msg>
561 580 </logentry>
562 581 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
563 582 <parent revision="-1" node="0000000000000000000000000000000000000000" />
564 583 <author email="user@hostname">User Name</author>
565 584 <date>1970-01-12T13:46:40+00:00</date>
566 585 <msg xml:space="preserve">second</msg>
567 586 </logentry>
568 587 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
569 588 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
570 589 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
571 590 <author email="person">person</author>
572 591 <date>1970-01-18T08:40:01+00:00</date>
573 592 <msg xml:space="preserve">merge</msg>
574 593 </logentry>
575 594 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
576 595 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
577 596 <author email="person">person</author>
578 597 <date>1970-01-18T08:40:00+00:00</date>
579 598 <msg xml:space="preserve">new head</msg>
580 599 </logentry>
581 600 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
582 601 <branch>foo</branch>
583 602 <author email="person">person</author>
584 603 <date>1970-01-17T04:53:20+00:00</date>
585 604 <msg xml:space="preserve">new branch</msg>
586 605 </logentry>
587 606 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
588 607 <author email="person">person</author>
589 608 <date>1970-01-16T01:06:40+00:00</date>
590 609 <msg xml:space="preserve">no user, no domain</msg>
591 610 </logentry>
592 611 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
593 612 <author email="other@place">other</author>
594 613 <date>1970-01-14T21:20:00+00:00</date>
595 614 <msg xml:space="preserve">no person</msg>
596 615 </logentry>
597 616 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
598 617 <author email="other@place">A. N. Other</author>
599 618 <date>1970-01-13T17:33:20+00:00</date>
600 619 <msg xml:space="preserve">other 1
601 620 other 2
602 621
603 622 other 3</msg>
604 623 </logentry>
605 624 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
606 625 <author email="user@hostname">User Name</author>
607 626 <date>1970-01-12T13:46:40+00:00</date>
608 627 <msg xml:space="preserve">line 1
609 628 line 2</msg>
610 629 </logentry>
611 630 </log>
612 631
613 632 $ hg log -v --style xml
614 633 <?xml version="1.0"?>
615 634 <log>
616 635 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
617 636 <tag>tip</tag>
618 637 <author email="test">test</author>
619 638 <date>2020-01-01T10:01:00+00:00</date>
620 639 <msg xml:space="preserve">third</msg>
621 640 <paths>
622 641 <path action="A">fourth</path>
623 642 <path action="A">third</path>
624 643 <path action="R">second</path>
625 644 </paths>
626 645 <copies>
627 646 <copy source="second">fourth</copy>
628 647 </copies>
629 648 </logentry>
630 649 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
631 650 <parent revision="-1" node="0000000000000000000000000000000000000000" />
632 651 <author email="user@hostname">User Name</author>
633 652 <date>1970-01-12T13:46:40+00:00</date>
634 653 <msg xml:space="preserve">second</msg>
635 654 <paths>
636 655 <path action="A">second</path>
637 656 </paths>
638 657 </logentry>
639 658 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
640 659 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
641 660 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
642 661 <author email="person">person</author>
643 662 <date>1970-01-18T08:40:01+00:00</date>
644 663 <msg xml:space="preserve">merge</msg>
645 664 <paths>
646 665 </paths>
647 666 </logentry>
648 667 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
649 668 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
650 669 <author email="person">person</author>
651 670 <date>1970-01-18T08:40:00+00:00</date>
652 671 <msg xml:space="preserve">new head</msg>
653 672 <paths>
654 673 <path action="A">d</path>
655 674 </paths>
656 675 </logentry>
657 676 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
658 677 <branch>foo</branch>
659 678 <author email="person">person</author>
660 679 <date>1970-01-17T04:53:20+00:00</date>
661 680 <msg xml:space="preserve">new branch</msg>
662 681 <paths>
663 682 </paths>
664 683 </logentry>
665 684 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
666 685 <author email="person">person</author>
667 686 <date>1970-01-16T01:06:40+00:00</date>
668 687 <msg xml:space="preserve">no user, no domain</msg>
669 688 <paths>
670 689 <path action="M">c</path>
671 690 </paths>
672 691 </logentry>
673 692 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
674 693 <author email="other@place">other</author>
675 694 <date>1970-01-14T21:20:00+00:00</date>
676 695 <msg xml:space="preserve">no person</msg>
677 696 <paths>
678 697 <path action="A">c</path>
679 698 </paths>
680 699 </logentry>
681 700 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
682 701 <author email="other@place">A. N. Other</author>
683 702 <date>1970-01-13T17:33:20+00:00</date>
684 703 <msg xml:space="preserve">other 1
685 704 other 2
686 705
687 706 other 3</msg>
688 707 <paths>
689 708 <path action="A">b</path>
690 709 </paths>
691 710 </logentry>
692 711 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
693 712 <author email="user@hostname">User Name</author>
694 713 <date>1970-01-12T13:46:40+00:00</date>
695 714 <msg xml:space="preserve">line 1
696 715 line 2</msg>
697 716 <paths>
698 717 <path action="A">a</path>
699 718 </paths>
700 719 </logentry>
701 720 </log>
702 721
703 722 $ hg log --debug --style xml
704 723 <?xml version="1.0"?>
705 724 <log>
706 725 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
707 726 <tag>tip</tag>
708 727 <parent revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453" />
709 728 <parent revision="-1" node="0000000000000000000000000000000000000000" />
710 729 <author email="test">test</author>
711 730 <date>2020-01-01T10:01:00+00:00</date>
712 731 <msg xml:space="preserve">third</msg>
713 732 <paths>
714 733 <path action="A">fourth</path>
715 734 <path action="A">third</path>
716 735 <path action="R">second</path>
717 736 </paths>
718 737 <copies>
719 738 <copy source="second">fourth</copy>
720 739 </copies>
721 740 <extra key="branch">default</extra>
722 741 </logentry>
723 742 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
724 743 <parent revision="-1" node="0000000000000000000000000000000000000000" />
725 744 <parent revision="-1" node="0000000000000000000000000000000000000000" />
726 745 <author email="user@hostname">User Name</author>
727 746 <date>1970-01-12T13:46:40+00:00</date>
728 747 <msg xml:space="preserve">second</msg>
729 748 <paths>
730 749 <path action="A">second</path>
731 750 </paths>
732 751 <extra key="branch">default</extra>
733 752 </logentry>
734 753 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
735 754 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
736 755 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
737 756 <author email="person">person</author>
738 757 <date>1970-01-18T08:40:01+00:00</date>
739 758 <msg xml:space="preserve">merge</msg>
740 759 <paths>
741 760 </paths>
742 761 <extra key="branch">default</extra>
743 762 </logentry>
744 763 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
745 764 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
746 765 <parent revision="-1" node="0000000000000000000000000000000000000000" />
747 766 <author email="person">person</author>
748 767 <date>1970-01-18T08:40:00+00:00</date>
749 768 <msg xml:space="preserve">new head</msg>
750 769 <paths>
751 770 <path action="A">d</path>
752 771 </paths>
753 772 <extra key="branch">default</extra>
754 773 </logentry>
755 774 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
756 775 <branch>foo</branch>
757 776 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
758 777 <parent revision="-1" node="0000000000000000000000000000000000000000" />
759 778 <author email="person">person</author>
760 779 <date>1970-01-17T04:53:20+00:00</date>
761 780 <msg xml:space="preserve">new branch</msg>
762 781 <paths>
763 782 </paths>
764 783 <extra key="branch">foo</extra>
765 784 </logentry>
766 785 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
767 786 <parent revision="2" node="97054abb4ab824450e9164180baf491ae0078465" />
768 787 <parent revision="-1" node="0000000000000000000000000000000000000000" />
769 788 <author email="person">person</author>
770 789 <date>1970-01-16T01:06:40+00:00</date>
771 790 <msg xml:space="preserve">no user, no domain</msg>
772 791 <paths>
773 792 <path action="M">c</path>
774 793 </paths>
775 794 <extra key="branch">default</extra>
776 795 </logentry>
777 796 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
778 797 <parent revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965" />
779 798 <parent revision="-1" node="0000000000000000000000000000000000000000" />
780 799 <author email="other@place">other</author>
781 800 <date>1970-01-14T21:20:00+00:00</date>
782 801 <msg xml:space="preserve">no person</msg>
783 802 <paths>
784 803 <path action="A">c</path>
785 804 </paths>
786 805 <extra key="branch">default</extra>
787 806 </logentry>
788 807 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
789 808 <parent revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f" />
790 809 <parent revision="-1" node="0000000000000000000000000000000000000000" />
791 810 <author email="other@place">A. N. Other</author>
792 811 <date>1970-01-13T17:33:20+00:00</date>
793 812 <msg xml:space="preserve">other 1
794 813 other 2
795 814
796 815 other 3</msg>
797 816 <paths>
798 817 <path action="A">b</path>
799 818 </paths>
800 819 <extra key="branch">default</extra>
801 820 </logentry>
802 821 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
803 822 <parent revision="-1" node="0000000000000000000000000000000000000000" />
804 823 <parent revision="-1" node="0000000000000000000000000000000000000000" />
805 824 <author email="user@hostname">User Name</author>
806 825 <date>1970-01-12T13:46:40+00:00</date>
807 826 <msg xml:space="preserve">line 1
808 827 line 2</msg>
809 828 <paths>
810 829 <path action="A">a</path>
811 830 </paths>
812 831 <extra key="branch">default</extra>
813 832 </logentry>
814 833 </log>
815 834
816 835
817 836 Test JSON style:
818 837
819 838 $ hg log -k nosuch -Tjson
820 839 []
821 840
822 841 $ hg log -qr . -Tjson
823 842 [
824 843 {
825 844 "rev": 8,
826 845 "node": "95c24699272ef57d062b8bccc32c878bf841784a"
827 846 }
828 847 ]
829 848
830 849 $ hg log -vpr . -Tjson --stat
831 850 [
832 851 {
833 852 "rev": 8,
834 853 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
835 854 "branch": "default",
836 855 "phase": "draft",
837 856 "user": "test",
838 857 "date": [1577872860, 0],
839 858 "desc": "third",
840 859 "bookmarks": [],
841 860 "tags": ["tip"],
842 861 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
843 862 "files": ["fourth", "second", "third"],
844 863 "diffstat": " fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n",
845 864 "diff": "diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n"
846 865 }
847 866 ]
848 867
849 868 honor --git but not format-breaking diffopts
850 869 $ hg --config diff.noprefix=True log --git -vpr . -Tjson
851 870 [
852 871 {
853 872 "rev": 8,
854 873 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
855 874 "branch": "default",
856 875 "phase": "draft",
857 876 "user": "test",
858 877 "date": [1577872860, 0],
859 878 "desc": "third",
860 879 "bookmarks": [],
861 880 "tags": ["tip"],
862 881 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
863 882 "files": ["fourth", "second", "third"],
864 883 "diff": "diff --git a/second b/fourth\nrename from second\nrename to fourth\ndiff --git a/third b/third\nnew file mode 100644\n--- /dev/null\n+++ b/third\n@@ -0,0 +1,1 @@\n+third\n"
865 884 }
866 885 ]
867 886
868 887 $ hg log -T json
869 888 [
870 889 {
871 890 "rev": 8,
872 891 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
873 892 "branch": "default",
874 893 "phase": "draft",
875 894 "user": "test",
876 895 "date": [1577872860, 0],
877 896 "desc": "third",
878 897 "bookmarks": [],
879 898 "tags": ["tip"],
880 899 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"]
881 900 },
882 901 {
883 902 "rev": 7,
884 903 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
885 904 "branch": "default",
886 905 "phase": "draft",
887 906 "user": "User Name <user@hostname>",
888 907 "date": [1000000, 0],
889 908 "desc": "second",
890 909 "bookmarks": [],
891 910 "tags": [],
892 911 "parents": ["0000000000000000000000000000000000000000"]
893 912 },
894 913 {
895 914 "rev": 6,
896 915 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
897 916 "branch": "default",
898 917 "phase": "draft",
899 918 "user": "person",
900 919 "date": [1500001, 0],
901 920 "desc": "merge",
902 921 "bookmarks": [],
903 922 "tags": [],
904 923 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"]
905 924 },
906 925 {
907 926 "rev": 5,
908 927 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
909 928 "branch": "default",
910 929 "phase": "draft",
911 930 "user": "person",
912 931 "date": [1500000, 0],
913 932 "desc": "new head",
914 933 "bookmarks": [],
915 934 "tags": [],
916 935 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"]
917 936 },
918 937 {
919 938 "rev": 4,
920 939 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
921 940 "branch": "foo",
922 941 "phase": "draft",
923 942 "user": "person",
924 943 "date": [1400000, 0],
925 944 "desc": "new branch",
926 945 "bookmarks": [],
927 946 "tags": [],
928 947 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"]
929 948 },
930 949 {
931 950 "rev": 3,
932 951 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
933 952 "branch": "default",
934 953 "phase": "draft",
935 954 "user": "person",
936 955 "date": [1300000, 0],
937 956 "desc": "no user, no domain",
938 957 "bookmarks": [],
939 958 "tags": [],
940 959 "parents": ["97054abb4ab824450e9164180baf491ae0078465"]
941 960 },
942 961 {
943 962 "rev": 2,
944 963 "node": "97054abb4ab824450e9164180baf491ae0078465",
945 964 "branch": "default",
946 965 "phase": "draft",
947 966 "user": "other@place",
948 967 "date": [1200000, 0],
949 968 "desc": "no person",
950 969 "bookmarks": [],
951 970 "tags": [],
952 971 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"]
953 972 },
954 973 {
955 974 "rev": 1,
956 975 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
957 976 "branch": "default",
958 977 "phase": "draft",
959 978 "user": "A. N. Other <other@place>",
960 979 "date": [1100000, 0],
961 980 "desc": "other 1\nother 2\n\nother 3",
962 981 "bookmarks": [],
963 982 "tags": [],
964 983 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"]
965 984 },
966 985 {
967 986 "rev": 0,
968 987 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
969 988 "branch": "default",
970 989 "phase": "draft",
971 990 "user": "User Name <user@hostname>",
972 991 "date": [1000000, 0],
973 992 "desc": "line 1\nline 2",
974 993 "bookmarks": [],
975 994 "tags": [],
976 995 "parents": ["0000000000000000000000000000000000000000"]
977 996 }
978 997 ]
979 998
980 999 $ hg heads -v -Tjson
981 1000 [
982 1001 {
983 1002 "rev": 8,
984 1003 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
985 1004 "branch": "default",
986 1005 "phase": "draft",
987 1006 "user": "test",
988 1007 "date": [1577872860, 0],
989 1008 "desc": "third",
990 1009 "bookmarks": [],
991 1010 "tags": ["tip"],
992 1011 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
993 1012 "files": ["fourth", "second", "third"]
994 1013 },
995 1014 {
996 1015 "rev": 6,
997 1016 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
998 1017 "branch": "default",
999 1018 "phase": "draft",
1000 1019 "user": "person",
1001 1020 "date": [1500001, 0],
1002 1021 "desc": "merge",
1003 1022 "bookmarks": [],
1004 1023 "tags": [],
1005 1024 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
1006 1025 "files": []
1007 1026 },
1008 1027 {
1009 1028 "rev": 4,
1010 1029 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
1011 1030 "branch": "foo",
1012 1031 "phase": "draft",
1013 1032 "user": "person",
1014 1033 "date": [1400000, 0],
1015 1034 "desc": "new branch",
1016 1035 "bookmarks": [],
1017 1036 "tags": [],
1018 1037 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1019 1038 "files": []
1020 1039 }
1021 1040 ]
1022 1041
1023 1042 $ hg log --debug -Tjson
1024 1043 [
1025 1044 {
1026 1045 "rev": 8,
1027 1046 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
1028 1047 "branch": "default",
1029 1048 "phase": "draft",
1030 1049 "user": "test",
1031 1050 "date": [1577872860, 0],
1032 1051 "desc": "third",
1033 1052 "bookmarks": [],
1034 1053 "tags": ["tip"],
1035 1054 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
1036 1055 "manifest": "94961b75a2da554b4df6fb599e5bfc7d48de0c64",
1037 1056 "extra": {"branch": "default"},
1038 1057 "modified": [],
1039 1058 "added": ["fourth", "third"],
1040 1059 "removed": ["second"]
1041 1060 },
1042 1061 {
1043 1062 "rev": 7,
1044 1063 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
1045 1064 "branch": "default",
1046 1065 "phase": "draft",
1047 1066 "user": "User Name <user@hostname>",
1048 1067 "date": [1000000, 0],
1049 1068 "desc": "second",
1050 1069 "bookmarks": [],
1051 1070 "tags": [],
1052 1071 "parents": ["0000000000000000000000000000000000000000"],
1053 1072 "manifest": "f2dbc354b94e5ec0b4f10680ee0cee816101d0bf",
1054 1073 "extra": {"branch": "default"},
1055 1074 "modified": [],
1056 1075 "added": ["second"],
1057 1076 "removed": []
1058 1077 },
1059 1078 {
1060 1079 "rev": 6,
1061 1080 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
1062 1081 "branch": "default",
1063 1082 "phase": "draft",
1064 1083 "user": "person",
1065 1084 "date": [1500001, 0],
1066 1085 "desc": "merge",
1067 1086 "bookmarks": [],
1068 1087 "tags": [],
1069 1088 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
1070 1089 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
1071 1090 "extra": {"branch": "default"},
1072 1091 "modified": [],
1073 1092 "added": [],
1074 1093 "removed": []
1075 1094 },
1076 1095 {
1077 1096 "rev": 5,
1078 1097 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
1079 1098 "branch": "default",
1080 1099 "phase": "draft",
1081 1100 "user": "person",
1082 1101 "date": [1500000, 0],
1083 1102 "desc": "new head",
1084 1103 "bookmarks": [],
1085 1104 "tags": [],
1086 1105 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1087 1106 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
1088 1107 "extra": {"branch": "default"},
1089 1108 "modified": [],
1090 1109 "added": ["d"],
1091 1110 "removed": []
1092 1111 },
1093 1112 {
1094 1113 "rev": 4,
1095 1114 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
1096 1115 "branch": "foo",
1097 1116 "phase": "draft",
1098 1117 "user": "person",
1099 1118 "date": [1400000, 0],
1100 1119 "desc": "new branch",
1101 1120 "bookmarks": [],
1102 1121 "tags": [],
1103 1122 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1104 1123 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
1105 1124 "extra": {"branch": "foo"},
1106 1125 "modified": [],
1107 1126 "added": [],
1108 1127 "removed": []
1109 1128 },
1110 1129 {
1111 1130 "rev": 3,
1112 1131 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
1113 1132 "branch": "default",
1114 1133 "phase": "draft",
1115 1134 "user": "person",
1116 1135 "date": [1300000, 0],
1117 1136 "desc": "no user, no domain",
1118 1137 "bookmarks": [],
1119 1138 "tags": [],
1120 1139 "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
1121 1140 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
1122 1141 "extra": {"branch": "default"},
1123 1142 "modified": ["c"],
1124 1143 "added": [],
1125 1144 "removed": []
1126 1145 },
1127 1146 {
1128 1147 "rev": 2,
1129 1148 "node": "97054abb4ab824450e9164180baf491ae0078465",
1130 1149 "branch": "default",
1131 1150 "phase": "draft",
1132 1151 "user": "other@place",
1133 1152 "date": [1200000, 0],
1134 1153 "desc": "no person",
1135 1154 "bookmarks": [],
1136 1155 "tags": [],
1137 1156 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
1138 1157 "manifest": "6e0e82995c35d0d57a52aca8da4e56139e06b4b1",
1139 1158 "extra": {"branch": "default"},
1140 1159 "modified": [],
1141 1160 "added": ["c"],
1142 1161 "removed": []
1143 1162 },
1144 1163 {
1145 1164 "rev": 1,
1146 1165 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
1147 1166 "branch": "default",
1148 1167 "phase": "draft",
1149 1168 "user": "A. N. Other <other@place>",
1150 1169 "date": [1100000, 0],
1151 1170 "desc": "other 1\nother 2\n\nother 3",
1152 1171 "bookmarks": [],
1153 1172 "tags": [],
1154 1173 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
1155 1174 "manifest": "4e8d705b1e53e3f9375e0e60dc7b525d8211fe55",
1156 1175 "extra": {"branch": "default"},
1157 1176 "modified": [],
1158 1177 "added": ["b"],
1159 1178 "removed": []
1160 1179 },
1161 1180 {
1162 1181 "rev": 0,
1163 1182 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
1164 1183 "branch": "default",
1165 1184 "phase": "draft",
1166 1185 "user": "User Name <user@hostname>",
1167 1186 "date": [1000000, 0],
1168 1187 "desc": "line 1\nline 2",
1169 1188 "bookmarks": [],
1170 1189 "tags": [],
1171 1190 "parents": ["0000000000000000000000000000000000000000"],
1172 1191 "manifest": "a0c8bcbbb45c63b90b70ad007bf38961f64f2af0",
1173 1192 "extra": {"branch": "default"},
1174 1193 "modified": [],
1175 1194 "added": ["a"],
1176 1195 "removed": []
1177 1196 }
1178 1197 ]
1179 1198
1180 1199 Error if style not readable:
1181 1200
1182 1201 #if unix-permissions no-root
1183 1202 $ touch q
1184 1203 $ chmod 0 q
1185 1204 $ hg log --style ./q
1186 1205 abort: Permission denied: ./q
1187 1206 [255]
1188 1207 #endif
1189 1208
1190 1209 Error if no style:
1191 1210
1192 1211 $ hg log --style notexist
1193 1212 abort: style 'notexist' not found
1194 1213 (available styles: bisect, changelog, compact, default, phases, show, status, xml)
1195 1214 [255]
1196 1215
1197 1216 $ hg log -T list
1198 1217 available styles: bisect, changelog, compact, default, phases, show, status, xml
1199 1218 abort: specify a template
1200 1219 [255]
1201 1220
1202 1221 Error if style missing key:
1203 1222
1204 1223 $ echo 'q = q' > t
1205 1224 $ hg log --style ./t
1206 1225 abort: "changeset" not in template map
1207 1226 [255]
1208 1227
1209 1228 Error if style missing value:
1210 1229
1211 1230 $ echo 'changeset =' > t
1212 1231 $ hg log --style t
1213 1232 hg: parse error at t:1: missing value
1214 1233 [255]
1215 1234
1216 1235 Error if include fails:
1217 1236
1218 1237 $ echo 'changeset = q' >> t
1219 1238 #if unix-permissions no-root
1220 1239 $ hg log --style ./t
1221 1240 abort: template file ./q: Permission denied
1222 1241 [255]
1223 1242 $ rm -f q
1224 1243 #endif
1225 1244
1226 1245 Include works:
1227 1246
1228 1247 $ echo '{rev}' > q
1229 1248 $ hg log --style ./t
1230 1249 8
1231 1250 7
1232 1251 6
1233 1252 5
1234 1253 4
1235 1254 3
1236 1255 2
1237 1256 1
1238 1257 0
1239 1258
1240 1259 Check that recursive reference does not fall into RuntimeError (issue4758):
1241 1260
1242 1261 common mistake:
1243 1262
1244 1263 $ cat << EOF > issue4758
1245 1264 > changeset = '{changeset}\n'
1246 1265 > EOF
1247 1266 $ hg log --style ./issue4758
1248 1267 abort: recursive reference 'changeset' in template
1249 1268 [255]
1250 1269
1251 1270 circular reference:
1252 1271
1253 1272 $ cat << EOF > issue4758
1254 1273 > changeset = '{foo}'
1255 1274 > foo = '{changeset}'
1256 1275 > EOF
1257 1276 $ hg log --style ./issue4758
1258 1277 abort: recursive reference 'foo' in template
1259 1278 [255]
1260 1279
1261 1280 buildmap() -> gettemplate(), where no thunk was made:
1262 1281
1263 1282 $ cat << EOF > issue4758
1264 1283 > changeset = '{files % changeset}\n'
1265 1284 > EOF
1266 1285 $ hg log --style ./issue4758
1267 1286 abort: recursive reference 'changeset' in template
1268 1287 [255]
1269 1288
1270 1289 not a recursion if a keyword of the same name exists:
1271 1290
1272 1291 $ cat << EOF > issue4758
1273 1292 > changeset = '{tags % rev}'
1274 1293 > rev = '{rev} {tag}\n'
1275 1294 > EOF
1276 1295 $ hg log --style ./issue4758 -r tip
1277 1296 8 tip
1278 1297
1279 1298 Check that {phase} works correctly on parents:
1280 1299
1281 1300 $ cat << EOF > parentphase
1282 1301 > changeset_debug = '{rev} ({phase}):{parents}\n'
1283 1302 > parent = ' {rev} ({phase})'
1284 1303 > EOF
1285 1304 $ hg phase -r 5 --public
1286 1305 $ hg phase -r 7 --secret --force
1287 1306 $ hg log --debug -G --style ./parentphase
1288 1307 @ 8 (secret): 7 (secret) -1 (public)
1289 1308 |
1290 1309 o 7 (secret): -1 (public) -1 (public)
1291 1310
1292 1311 o 6 (draft): 5 (public) 4 (draft)
1293 1312 |\
1294 1313 | o 5 (public): 3 (public) -1 (public)
1295 1314 | |
1296 1315 o | 4 (draft): 3 (public) -1 (public)
1297 1316 |/
1298 1317 o 3 (public): 2 (public) -1 (public)
1299 1318 |
1300 1319 o 2 (public): 1 (public) -1 (public)
1301 1320 |
1302 1321 o 1 (public): 0 (public) -1 (public)
1303 1322 |
1304 1323 o 0 (public): -1 (public) -1 (public)
1305 1324
1306 1325
1307 1326 Missing non-standard names give no error (backward compatibility):
1308 1327
1309 1328 $ echo "changeset = '{c}'" > t
1310 1329 $ hg log --style ./t
1311 1330
1312 1331 Defining non-standard name works:
1313 1332
1314 1333 $ cat <<EOF > t
1315 1334 > changeset = '{c}'
1316 1335 > c = q
1317 1336 > EOF
1318 1337 $ hg log --style ./t
1319 1338 8
1320 1339 7
1321 1340 6
1322 1341 5
1323 1342 4
1324 1343 3
1325 1344 2
1326 1345 1
1327 1346 0
1328 1347
1329 1348 ui.style works:
1330 1349
1331 1350 $ echo '[ui]' > .hg/hgrc
1332 1351 $ echo 'style = t' >> .hg/hgrc
1333 1352 $ hg log
1334 1353 8
1335 1354 7
1336 1355 6
1337 1356 5
1338 1357 4
1339 1358 3
1340 1359 2
1341 1360 1
1342 1361 0
1343 1362
1344 1363
1345 1364 Issue338:
1346 1365
1347 1366 $ hg log --style=changelog > changelog
1348 1367
1349 1368 $ cat changelog
1350 1369 2020-01-01 test <test>
1351 1370
1352 1371 * fourth, second, third:
1353 1372 third
1354 1373 [95c24699272e] [tip]
1355 1374
1356 1375 1970-01-12 User Name <user@hostname>
1357 1376
1358 1377 * second:
1359 1378 second
1360 1379 [29114dbae42b]
1361 1380
1362 1381 1970-01-18 person <person>
1363 1382
1364 1383 * merge
1365 1384 [d41e714fe50d]
1366 1385
1367 1386 * d:
1368 1387 new head
1369 1388 [13207e5a10d9]
1370 1389
1371 1390 1970-01-17 person <person>
1372 1391
1373 1392 * new branch
1374 1393 [bbe44766e73d] <foo>
1375 1394
1376 1395 1970-01-16 person <person>
1377 1396
1378 1397 * c:
1379 1398 no user, no domain
1380 1399 [10e46f2dcbf4]
1381 1400
1382 1401 1970-01-14 other <other@place>
1383 1402
1384 1403 * c:
1385 1404 no person
1386 1405 [97054abb4ab8]
1387 1406
1388 1407 1970-01-13 A. N. Other <other@place>
1389 1408
1390 1409 * b:
1391 1410 other 1 other 2
1392 1411
1393 1412 other 3
1394 1413 [b608e9d1a3f0]
1395 1414
1396 1415 1970-01-12 User Name <user@hostname>
1397 1416
1398 1417 * a:
1399 1418 line 1 line 2
1400 1419 [1e4e1b8f71e0]
1401 1420
1402 1421
1403 1422 Issue2130: xml output for 'hg heads' is malformed
1404 1423
1405 1424 $ hg heads --style changelog
1406 1425 2020-01-01 test <test>
1407 1426
1408 1427 * fourth, second, third:
1409 1428 third
1410 1429 [95c24699272e] [tip]
1411 1430
1412 1431 1970-01-18 person <person>
1413 1432
1414 1433 * merge
1415 1434 [d41e714fe50d]
1416 1435
1417 1436 1970-01-17 person <person>
1418 1437
1419 1438 * new branch
1420 1439 [bbe44766e73d] <foo>
1421 1440
1422 1441
1423 1442 Keys work:
1424 1443
1425 1444 $ for key in author branch branches date desc file_adds file_dels file_mods \
1426 1445 > file_copies file_copies_switch files \
1427 1446 > manifest node parents rev tags diffstat extras \
1428 1447 > p1rev p2rev p1node p2node; do
1429 1448 > for mode in '' --verbose --debug; do
1430 1449 > hg log $mode --template "$key$mode: {$key}\n"
1431 1450 > done
1432 1451 > done
1433 1452 author: test
1434 1453 author: User Name <user@hostname>
1435 1454 author: person
1436 1455 author: person
1437 1456 author: person
1438 1457 author: person
1439 1458 author: other@place
1440 1459 author: A. N. Other <other@place>
1441 1460 author: User Name <user@hostname>
1442 1461 author--verbose: test
1443 1462 author--verbose: User Name <user@hostname>
1444 1463 author--verbose: person
1445 1464 author--verbose: person
1446 1465 author--verbose: person
1447 1466 author--verbose: person
1448 1467 author--verbose: other@place
1449 1468 author--verbose: A. N. Other <other@place>
1450 1469 author--verbose: User Name <user@hostname>
1451 1470 author--debug: test
1452 1471 author--debug: User Name <user@hostname>
1453 1472 author--debug: person
1454 1473 author--debug: person
1455 1474 author--debug: person
1456 1475 author--debug: person
1457 1476 author--debug: other@place
1458 1477 author--debug: A. N. Other <other@place>
1459 1478 author--debug: User Name <user@hostname>
1460 1479 branch: default
1461 1480 branch: default
1462 1481 branch: default
1463 1482 branch: default
1464 1483 branch: foo
1465 1484 branch: default
1466 1485 branch: default
1467 1486 branch: default
1468 1487 branch: default
1469 1488 branch--verbose: default
1470 1489 branch--verbose: default
1471 1490 branch--verbose: default
1472 1491 branch--verbose: default
1473 1492 branch--verbose: foo
1474 1493 branch--verbose: default
1475 1494 branch--verbose: default
1476 1495 branch--verbose: default
1477 1496 branch--verbose: default
1478 1497 branch--debug: default
1479 1498 branch--debug: default
1480 1499 branch--debug: default
1481 1500 branch--debug: default
1482 1501 branch--debug: foo
1483 1502 branch--debug: default
1484 1503 branch--debug: default
1485 1504 branch--debug: default
1486 1505 branch--debug: default
1487 1506 branches:
1488 1507 branches:
1489 1508 branches:
1490 1509 branches:
1491 1510 branches: foo
1492 1511 branches:
1493 1512 branches:
1494 1513 branches:
1495 1514 branches:
1496 1515 branches--verbose:
1497 1516 branches--verbose:
1498 1517 branches--verbose:
1499 1518 branches--verbose:
1500 1519 branches--verbose: foo
1501 1520 branches--verbose:
1502 1521 branches--verbose:
1503 1522 branches--verbose:
1504 1523 branches--verbose:
1505 1524 branches--debug:
1506 1525 branches--debug:
1507 1526 branches--debug:
1508 1527 branches--debug:
1509 1528 branches--debug: foo
1510 1529 branches--debug:
1511 1530 branches--debug:
1512 1531 branches--debug:
1513 1532 branches--debug:
1514 1533 date: 1577872860.00
1515 1534 date: 1000000.00
1516 1535 date: 1500001.00
1517 1536 date: 1500000.00
1518 1537 date: 1400000.00
1519 1538 date: 1300000.00
1520 1539 date: 1200000.00
1521 1540 date: 1100000.00
1522 1541 date: 1000000.00
1523 1542 date--verbose: 1577872860.00
1524 1543 date--verbose: 1000000.00
1525 1544 date--verbose: 1500001.00
1526 1545 date--verbose: 1500000.00
1527 1546 date--verbose: 1400000.00
1528 1547 date--verbose: 1300000.00
1529 1548 date--verbose: 1200000.00
1530 1549 date--verbose: 1100000.00
1531 1550 date--verbose: 1000000.00
1532 1551 date--debug: 1577872860.00
1533 1552 date--debug: 1000000.00
1534 1553 date--debug: 1500001.00
1535 1554 date--debug: 1500000.00
1536 1555 date--debug: 1400000.00
1537 1556 date--debug: 1300000.00
1538 1557 date--debug: 1200000.00
1539 1558 date--debug: 1100000.00
1540 1559 date--debug: 1000000.00
1541 1560 desc: third
1542 1561 desc: second
1543 1562 desc: merge
1544 1563 desc: new head
1545 1564 desc: new branch
1546 1565 desc: no user, no domain
1547 1566 desc: no person
1548 1567 desc: other 1
1549 1568 other 2
1550 1569
1551 1570 other 3
1552 1571 desc: line 1
1553 1572 line 2
1554 1573 desc--verbose: third
1555 1574 desc--verbose: second
1556 1575 desc--verbose: merge
1557 1576 desc--verbose: new head
1558 1577 desc--verbose: new branch
1559 1578 desc--verbose: no user, no domain
1560 1579 desc--verbose: no person
1561 1580 desc--verbose: other 1
1562 1581 other 2
1563 1582
1564 1583 other 3
1565 1584 desc--verbose: line 1
1566 1585 line 2
1567 1586 desc--debug: third
1568 1587 desc--debug: second
1569 1588 desc--debug: merge
1570 1589 desc--debug: new head
1571 1590 desc--debug: new branch
1572 1591 desc--debug: no user, no domain
1573 1592 desc--debug: no person
1574 1593 desc--debug: other 1
1575 1594 other 2
1576 1595
1577 1596 other 3
1578 1597 desc--debug: line 1
1579 1598 line 2
1580 1599 file_adds: fourth third
1581 1600 file_adds: second
1582 1601 file_adds:
1583 1602 file_adds: d
1584 1603 file_adds:
1585 1604 file_adds:
1586 1605 file_adds: c
1587 1606 file_adds: b
1588 1607 file_adds: a
1589 1608 file_adds--verbose: fourth third
1590 1609 file_adds--verbose: second
1591 1610 file_adds--verbose:
1592 1611 file_adds--verbose: d
1593 1612 file_adds--verbose:
1594 1613 file_adds--verbose:
1595 1614 file_adds--verbose: c
1596 1615 file_adds--verbose: b
1597 1616 file_adds--verbose: a
1598 1617 file_adds--debug: fourth third
1599 1618 file_adds--debug: second
1600 1619 file_adds--debug:
1601 1620 file_adds--debug: d
1602 1621 file_adds--debug:
1603 1622 file_adds--debug:
1604 1623 file_adds--debug: c
1605 1624 file_adds--debug: b
1606 1625 file_adds--debug: a
1607 1626 file_dels: second
1608 1627 file_dels:
1609 1628 file_dels:
1610 1629 file_dels:
1611 1630 file_dels:
1612 1631 file_dels:
1613 1632 file_dels:
1614 1633 file_dels:
1615 1634 file_dels:
1616 1635 file_dels--verbose: second
1617 1636 file_dels--verbose:
1618 1637 file_dels--verbose:
1619 1638 file_dels--verbose:
1620 1639 file_dels--verbose:
1621 1640 file_dels--verbose:
1622 1641 file_dels--verbose:
1623 1642 file_dels--verbose:
1624 1643 file_dels--verbose:
1625 1644 file_dels--debug: second
1626 1645 file_dels--debug:
1627 1646 file_dels--debug:
1628 1647 file_dels--debug:
1629 1648 file_dels--debug:
1630 1649 file_dels--debug:
1631 1650 file_dels--debug:
1632 1651 file_dels--debug:
1633 1652 file_dels--debug:
1634 1653 file_mods:
1635 1654 file_mods:
1636 1655 file_mods:
1637 1656 file_mods:
1638 1657 file_mods:
1639 1658 file_mods: c
1640 1659 file_mods:
1641 1660 file_mods:
1642 1661 file_mods:
1643 1662 file_mods--verbose:
1644 1663 file_mods--verbose:
1645 1664 file_mods--verbose:
1646 1665 file_mods--verbose:
1647 1666 file_mods--verbose:
1648 1667 file_mods--verbose: c
1649 1668 file_mods--verbose:
1650 1669 file_mods--verbose:
1651 1670 file_mods--verbose:
1652 1671 file_mods--debug:
1653 1672 file_mods--debug:
1654 1673 file_mods--debug:
1655 1674 file_mods--debug:
1656 1675 file_mods--debug:
1657 1676 file_mods--debug: c
1658 1677 file_mods--debug:
1659 1678 file_mods--debug:
1660 1679 file_mods--debug:
1661 1680 file_copies: fourth (second)
1662 1681 file_copies:
1663 1682 file_copies:
1664 1683 file_copies:
1665 1684 file_copies:
1666 1685 file_copies:
1667 1686 file_copies:
1668 1687 file_copies:
1669 1688 file_copies:
1670 1689 file_copies--verbose: fourth (second)
1671 1690 file_copies--verbose:
1672 1691 file_copies--verbose:
1673 1692 file_copies--verbose:
1674 1693 file_copies--verbose:
1675 1694 file_copies--verbose:
1676 1695 file_copies--verbose:
1677 1696 file_copies--verbose:
1678 1697 file_copies--verbose:
1679 1698 file_copies--debug: fourth (second)
1680 1699 file_copies--debug:
1681 1700 file_copies--debug:
1682 1701 file_copies--debug:
1683 1702 file_copies--debug:
1684 1703 file_copies--debug:
1685 1704 file_copies--debug:
1686 1705 file_copies--debug:
1687 1706 file_copies--debug:
1688 1707 file_copies_switch:
1689 1708 file_copies_switch:
1690 1709 file_copies_switch:
1691 1710 file_copies_switch:
1692 1711 file_copies_switch:
1693 1712 file_copies_switch:
1694 1713 file_copies_switch:
1695 1714 file_copies_switch:
1696 1715 file_copies_switch:
1697 1716 file_copies_switch--verbose:
1698 1717 file_copies_switch--verbose:
1699 1718 file_copies_switch--verbose:
1700 1719 file_copies_switch--verbose:
1701 1720 file_copies_switch--verbose:
1702 1721 file_copies_switch--verbose:
1703 1722 file_copies_switch--verbose:
1704 1723 file_copies_switch--verbose:
1705 1724 file_copies_switch--verbose:
1706 1725 file_copies_switch--debug:
1707 1726 file_copies_switch--debug:
1708 1727 file_copies_switch--debug:
1709 1728 file_copies_switch--debug:
1710 1729 file_copies_switch--debug:
1711 1730 file_copies_switch--debug:
1712 1731 file_copies_switch--debug:
1713 1732 file_copies_switch--debug:
1714 1733 file_copies_switch--debug:
1715 1734 files: fourth second third
1716 1735 files: second
1717 1736 files:
1718 1737 files: d
1719 1738 files:
1720 1739 files: c
1721 1740 files: c
1722 1741 files: b
1723 1742 files: a
1724 1743 files--verbose: fourth second third
1725 1744 files--verbose: second
1726 1745 files--verbose:
1727 1746 files--verbose: d
1728 1747 files--verbose:
1729 1748 files--verbose: c
1730 1749 files--verbose: c
1731 1750 files--verbose: b
1732 1751 files--verbose: a
1733 1752 files--debug: fourth second third
1734 1753 files--debug: second
1735 1754 files--debug:
1736 1755 files--debug: d
1737 1756 files--debug:
1738 1757 files--debug: c
1739 1758 files--debug: c
1740 1759 files--debug: b
1741 1760 files--debug: a
1742 1761 manifest: 6:94961b75a2da
1743 1762 manifest: 5:f2dbc354b94e
1744 1763 manifest: 4:4dc3def4f9b4
1745 1764 manifest: 4:4dc3def4f9b4
1746 1765 manifest: 3:cb5a1327723b
1747 1766 manifest: 3:cb5a1327723b
1748 1767 manifest: 2:6e0e82995c35
1749 1768 manifest: 1:4e8d705b1e53
1750 1769 manifest: 0:a0c8bcbbb45c
1751 1770 manifest--verbose: 6:94961b75a2da
1752 1771 manifest--verbose: 5:f2dbc354b94e
1753 1772 manifest--verbose: 4:4dc3def4f9b4
1754 1773 manifest--verbose: 4:4dc3def4f9b4
1755 1774 manifest--verbose: 3:cb5a1327723b
1756 1775 manifest--verbose: 3:cb5a1327723b
1757 1776 manifest--verbose: 2:6e0e82995c35
1758 1777 manifest--verbose: 1:4e8d705b1e53
1759 1778 manifest--verbose: 0:a0c8bcbbb45c
1760 1779 manifest--debug: 6:94961b75a2da554b4df6fb599e5bfc7d48de0c64
1761 1780 manifest--debug: 5:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
1762 1781 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
1763 1782 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
1764 1783 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1765 1784 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1766 1785 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
1767 1786 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
1768 1787 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
1769 1788 node: 95c24699272ef57d062b8bccc32c878bf841784a
1770 1789 node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1771 1790 node: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1772 1791 node: 13207e5a10d9fd28ec424934298e176197f2c67f
1773 1792 node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1774 1793 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1775 1794 node: 97054abb4ab824450e9164180baf491ae0078465
1776 1795 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1777 1796 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
1778 1797 node--verbose: 95c24699272ef57d062b8bccc32c878bf841784a
1779 1798 node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1780 1799 node--verbose: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1781 1800 node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1782 1801 node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1783 1802 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1784 1803 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
1785 1804 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1786 1805 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
1787 1806 node--debug: 95c24699272ef57d062b8bccc32c878bf841784a
1788 1807 node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1789 1808 node--debug: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1790 1809 node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
1791 1810 node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1792 1811 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1793 1812 node--debug: 97054abb4ab824450e9164180baf491ae0078465
1794 1813 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1795 1814 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
1796 1815 parents:
1797 1816 parents: -1:000000000000
1798 1817 parents: 5:13207e5a10d9 4:bbe44766e73d
1799 1818 parents: 3:10e46f2dcbf4
1800 1819 parents:
1801 1820 parents:
1802 1821 parents:
1803 1822 parents:
1804 1823 parents:
1805 1824 parents--verbose:
1806 1825 parents--verbose: -1:000000000000
1807 1826 parents--verbose: 5:13207e5a10d9 4:bbe44766e73d
1808 1827 parents--verbose: 3:10e46f2dcbf4
1809 1828 parents--verbose:
1810 1829 parents--verbose:
1811 1830 parents--verbose:
1812 1831 parents--verbose:
1813 1832 parents--verbose:
1814 1833 parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000
1815 1834 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1816 1835 parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1817 1836 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1818 1837 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1819 1838 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
1820 1839 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
1821 1840 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
1822 1841 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1823 1842 rev: 8
1824 1843 rev: 7
1825 1844 rev: 6
1826 1845 rev: 5
1827 1846 rev: 4
1828 1847 rev: 3
1829 1848 rev: 2
1830 1849 rev: 1
1831 1850 rev: 0
1832 1851 rev--verbose: 8
1833 1852 rev--verbose: 7
1834 1853 rev--verbose: 6
1835 1854 rev--verbose: 5
1836 1855 rev--verbose: 4
1837 1856 rev--verbose: 3
1838 1857 rev--verbose: 2
1839 1858 rev--verbose: 1
1840 1859 rev--verbose: 0
1841 1860 rev--debug: 8
1842 1861 rev--debug: 7
1843 1862 rev--debug: 6
1844 1863 rev--debug: 5
1845 1864 rev--debug: 4
1846 1865 rev--debug: 3
1847 1866 rev--debug: 2
1848 1867 rev--debug: 1
1849 1868 rev--debug: 0
1850 1869 tags: tip
1851 1870 tags:
1852 1871 tags:
1853 1872 tags:
1854 1873 tags:
1855 1874 tags:
1856 1875 tags:
1857 1876 tags:
1858 1877 tags:
1859 1878 tags--verbose: tip
1860 1879 tags--verbose:
1861 1880 tags--verbose:
1862 1881 tags--verbose:
1863 1882 tags--verbose:
1864 1883 tags--verbose:
1865 1884 tags--verbose:
1866 1885 tags--verbose:
1867 1886 tags--verbose:
1868 1887 tags--debug: tip
1869 1888 tags--debug:
1870 1889 tags--debug:
1871 1890 tags--debug:
1872 1891 tags--debug:
1873 1892 tags--debug:
1874 1893 tags--debug:
1875 1894 tags--debug:
1876 1895 tags--debug:
1877 1896 diffstat: 3: +2/-1
1878 1897 diffstat: 1: +1/-0
1879 1898 diffstat: 0: +0/-0
1880 1899 diffstat: 1: +1/-0
1881 1900 diffstat: 0: +0/-0
1882 1901 diffstat: 1: +1/-0
1883 1902 diffstat: 1: +4/-0
1884 1903 diffstat: 1: +2/-0
1885 1904 diffstat: 1: +1/-0
1886 1905 diffstat--verbose: 3: +2/-1
1887 1906 diffstat--verbose: 1: +1/-0
1888 1907 diffstat--verbose: 0: +0/-0
1889 1908 diffstat--verbose: 1: +1/-0
1890 1909 diffstat--verbose: 0: +0/-0
1891 1910 diffstat--verbose: 1: +1/-0
1892 1911 diffstat--verbose: 1: +4/-0
1893 1912 diffstat--verbose: 1: +2/-0
1894 1913 diffstat--verbose: 1: +1/-0
1895 1914 diffstat--debug: 3: +2/-1
1896 1915 diffstat--debug: 1: +1/-0
1897 1916 diffstat--debug: 0: +0/-0
1898 1917 diffstat--debug: 1: +1/-0
1899 1918 diffstat--debug: 0: +0/-0
1900 1919 diffstat--debug: 1: +1/-0
1901 1920 diffstat--debug: 1: +4/-0
1902 1921 diffstat--debug: 1: +2/-0
1903 1922 diffstat--debug: 1: +1/-0
1904 1923 extras: branch=default
1905 1924 extras: branch=default
1906 1925 extras: branch=default
1907 1926 extras: branch=default
1908 1927 extras: branch=foo
1909 1928 extras: branch=default
1910 1929 extras: branch=default
1911 1930 extras: branch=default
1912 1931 extras: branch=default
1913 1932 extras--verbose: branch=default
1914 1933 extras--verbose: branch=default
1915 1934 extras--verbose: branch=default
1916 1935 extras--verbose: branch=default
1917 1936 extras--verbose: branch=foo
1918 1937 extras--verbose: branch=default
1919 1938 extras--verbose: branch=default
1920 1939 extras--verbose: branch=default
1921 1940 extras--verbose: branch=default
1922 1941 extras--debug: branch=default
1923 1942 extras--debug: branch=default
1924 1943 extras--debug: branch=default
1925 1944 extras--debug: branch=default
1926 1945 extras--debug: branch=foo
1927 1946 extras--debug: branch=default
1928 1947 extras--debug: branch=default
1929 1948 extras--debug: branch=default
1930 1949 extras--debug: branch=default
1931 1950 p1rev: 7
1932 1951 p1rev: -1
1933 1952 p1rev: 5
1934 1953 p1rev: 3
1935 1954 p1rev: 3
1936 1955 p1rev: 2
1937 1956 p1rev: 1
1938 1957 p1rev: 0
1939 1958 p1rev: -1
1940 1959 p1rev--verbose: 7
1941 1960 p1rev--verbose: -1
1942 1961 p1rev--verbose: 5
1943 1962 p1rev--verbose: 3
1944 1963 p1rev--verbose: 3
1945 1964 p1rev--verbose: 2
1946 1965 p1rev--verbose: 1
1947 1966 p1rev--verbose: 0
1948 1967 p1rev--verbose: -1
1949 1968 p1rev--debug: 7
1950 1969 p1rev--debug: -1
1951 1970 p1rev--debug: 5
1952 1971 p1rev--debug: 3
1953 1972 p1rev--debug: 3
1954 1973 p1rev--debug: 2
1955 1974 p1rev--debug: 1
1956 1975 p1rev--debug: 0
1957 1976 p1rev--debug: -1
1958 1977 p2rev: -1
1959 1978 p2rev: -1
1960 1979 p2rev: 4
1961 1980 p2rev: -1
1962 1981 p2rev: -1
1963 1982 p2rev: -1
1964 1983 p2rev: -1
1965 1984 p2rev: -1
1966 1985 p2rev: -1
1967 1986 p2rev--verbose: -1
1968 1987 p2rev--verbose: -1
1969 1988 p2rev--verbose: 4
1970 1989 p2rev--verbose: -1
1971 1990 p2rev--verbose: -1
1972 1991 p2rev--verbose: -1
1973 1992 p2rev--verbose: -1
1974 1993 p2rev--verbose: -1
1975 1994 p2rev--verbose: -1
1976 1995 p2rev--debug: -1
1977 1996 p2rev--debug: -1
1978 1997 p2rev--debug: 4
1979 1998 p2rev--debug: -1
1980 1999 p2rev--debug: -1
1981 2000 p2rev--debug: -1
1982 2001 p2rev--debug: -1
1983 2002 p2rev--debug: -1
1984 2003 p2rev--debug: -1
1985 2004 p1node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1986 2005 p1node: 0000000000000000000000000000000000000000
1987 2006 p1node: 13207e5a10d9fd28ec424934298e176197f2c67f
1988 2007 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1989 2008 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1990 2009 p1node: 97054abb4ab824450e9164180baf491ae0078465
1991 2010 p1node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1992 2011 p1node: 1e4e1b8f71e05681d422154f5421e385fec3454f
1993 2012 p1node: 0000000000000000000000000000000000000000
1994 2013 p1node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1995 2014 p1node--verbose: 0000000000000000000000000000000000000000
1996 2015 p1node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1997 2016 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1998 2017 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1999 2018 p1node--verbose: 97054abb4ab824450e9164180baf491ae0078465
2000 2019 p1node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
2001 2020 p1node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
2002 2021 p1node--verbose: 0000000000000000000000000000000000000000
2003 2022 p1node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
2004 2023 p1node--debug: 0000000000000000000000000000000000000000
2005 2024 p1node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
2006 2025 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
2007 2026 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
2008 2027 p1node--debug: 97054abb4ab824450e9164180baf491ae0078465
2009 2028 p1node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
2010 2029 p1node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
2011 2030 p1node--debug: 0000000000000000000000000000000000000000
2012 2031 p2node: 0000000000000000000000000000000000000000
2013 2032 p2node: 0000000000000000000000000000000000000000
2014 2033 p2node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
2015 2034 p2node: 0000000000000000000000000000000000000000
2016 2035 p2node: 0000000000000000000000000000000000000000
2017 2036 p2node: 0000000000000000000000000000000000000000
2018 2037 p2node: 0000000000000000000000000000000000000000
2019 2038 p2node: 0000000000000000000000000000000000000000
2020 2039 p2node: 0000000000000000000000000000000000000000
2021 2040 p2node--verbose: 0000000000000000000000000000000000000000
2022 2041 p2node--verbose: 0000000000000000000000000000000000000000
2023 2042 p2node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
2024 2043 p2node--verbose: 0000000000000000000000000000000000000000
2025 2044 p2node--verbose: 0000000000000000000000000000000000000000
2026 2045 p2node--verbose: 0000000000000000000000000000000000000000
2027 2046 p2node--verbose: 0000000000000000000000000000000000000000
2028 2047 p2node--verbose: 0000000000000000000000000000000000000000
2029 2048 p2node--verbose: 0000000000000000000000000000000000000000
2030 2049 p2node--debug: 0000000000000000000000000000000000000000
2031 2050 p2node--debug: 0000000000000000000000000000000000000000
2032 2051 p2node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
2033 2052 p2node--debug: 0000000000000000000000000000000000000000
2034 2053 p2node--debug: 0000000000000000000000000000000000000000
2035 2054 p2node--debug: 0000000000000000000000000000000000000000
2036 2055 p2node--debug: 0000000000000000000000000000000000000000
2037 2056 p2node--debug: 0000000000000000000000000000000000000000
2038 2057 p2node--debug: 0000000000000000000000000000000000000000
2039 2058
2040 2059 Filters work:
2041 2060
2042 2061 $ hg log --template '{author|domain}\n'
2043 2062
2044 2063 hostname
2045 2064
2046 2065
2047 2066
2048 2067
2049 2068 place
2050 2069 place
2051 2070 hostname
2052 2071
2053 2072 $ hg log --template '{author|person}\n'
2054 2073 test
2055 2074 User Name
2056 2075 person
2057 2076 person
2058 2077 person
2059 2078 person
2060 2079 other
2061 2080 A. N. Other
2062 2081 User Name
2063 2082
2064 2083 $ hg log --template '{author|user}\n'
2065 2084 test
2066 2085 user
2067 2086 person
2068 2087 person
2069 2088 person
2070 2089 person
2071 2090 other
2072 2091 other
2073 2092 user
2074 2093
2075 2094 $ hg log --template '{date|date}\n'
2076 2095 Wed Jan 01 10:01:00 2020 +0000
2077 2096 Mon Jan 12 13:46:40 1970 +0000
2078 2097 Sun Jan 18 08:40:01 1970 +0000
2079 2098 Sun Jan 18 08:40:00 1970 +0000
2080 2099 Sat Jan 17 04:53:20 1970 +0000
2081 2100 Fri Jan 16 01:06:40 1970 +0000
2082 2101 Wed Jan 14 21:20:00 1970 +0000
2083 2102 Tue Jan 13 17:33:20 1970 +0000
2084 2103 Mon Jan 12 13:46:40 1970 +0000
2085 2104
2086 2105 $ hg log --template '{date|isodate}\n'
2087 2106 2020-01-01 10:01 +0000
2088 2107 1970-01-12 13:46 +0000
2089 2108 1970-01-18 08:40 +0000
2090 2109 1970-01-18 08:40 +0000
2091 2110 1970-01-17 04:53 +0000
2092 2111 1970-01-16 01:06 +0000
2093 2112 1970-01-14 21:20 +0000
2094 2113 1970-01-13 17:33 +0000
2095 2114 1970-01-12 13:46 +0000
2096 2115
2097 2116 $ hg log --template '{date|isodatesec}\n'
2098 2117 2020-01-01 10:01:00 +0000
2099 2118 1970-01-12 13:46:40 +0000
2100 2119 1970-01-18 08:40:01 +0000
2101 2120 1970-01-18 08:40:00 +0000
2102 2121 1970-01-17 04:53:20 +0000
2103 2122 1970-01-16 01:06:40 +0000
2104 2123 1970-01-14 21:20:00 +0000
2105 2124 1970-01-13 17:33:20 +0000
2106 2125 1970-01-12 13:46:40 +0000
2107 2126
2108 2127 $ hg log --template '{date|rfc822date}\n'
2109 2128 Wed, 01 Jan 2020 10:01:00 +0000
2110 2129 Mon, 12 Jan 1970 13:46:40 +0000
2111 2130 Sun, 18 Jan 1970 08:40:01 +0000
2112 2131 Sun, 18 Jan 1970 08:40:00 +0000
2113 2132 Sat, 17 Jan 1970 04:53:20 +0000
2114 2133 Fri, 16 Jan 1970 01:06:40 +0000
2115 2134 Wed, 14 Jan 1970 21:20:00 +0000
2116 2135 Tue, 13 Jan 1970 17:33:20 +0000
2117 2136 Mon, 12 Jan 1970 13:46:40 +0000
2118 2137
2119 2138 $ hg log --template '{desc|firstline}\n'
2120 2139 third
2121 2140 second
2122 2141 merge
2123 2142 new head
2124 2143 new branch
2125 2144 no user, no domain
2126 2145 no person
2127 2146 other 1
2128 2147 line 1
2129 2148
2130 2149 $ hg log --template '{node|short}\n'
2131 2150 95c24699272e
2132 2151 29114dbae42b
2133 2152 d41e714fe50d
2134 2153 13207e5a10d9
2135 2154 bbe44766e73d
2136 2155 10e46f2dcbf4
2137 2156 97054abb4ab8
2138 2157 b608e9d1a3f0
2139 2158 1e4e1b8f71e0
2140 2159
2141 2160 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
2142 2161 <changeset author="test"/>
2143 2162 <changeset author="User Name &lt;user@hostname&gt;"/>
2144 2163 <changeset author="person"/>
2145 2164 <changeset author="person"/>
2146 2165 <changeset author="person"/>
2147 2166 <changeset author="person"/>
2148 2167 <changeset author="other@place"/>
2149 2168 <changeset author="A. N. Other &lt;other@place&gt;"/>
2150 2169 <changeset author="User Name &lt;user@hostname&gt;"/>
2151 2170
2152 2171 $ hg log --template '{rev}: {children}\n'
2153 2172 8:
2154 2173 7: 8:95c24699272e
2155 2174 6:
2156 2175 5: 6:d41e714fe50d
2157 2176 4: 6:d41e714fe50d
2158 2177 3: 4:bbe44766e73d 5:13207e5a10d9
2159 2178 2: 3:10e46f2dcbf4
2160 2179 1: 2:97054abb4ab8
2161 2180 0: 1:b608e9d1a3f0
2162 2181
2163 2182 Formatnode filter works:
2164 2183
2165 2184 $ hg -q log -r 0 --template '{node|formatnode}\n'
2166 2185 1e4e1b8f71e0
2167 2186
2168 2187 $ hg log -r 0 --template '{node|formatnode}\n'
2169 2188 1e4e1b8f71e0
2170 2189
2171 2190 $ hg -v log -r 0 --template '{node|formatnode}\n'
2172 2191 1e4e1b8f71e0
2173 2192
2174 2193 $ hg --debug log -r 0 --template '{node|formatnode}\n'
2175 2194 1e4e1b8f71e05681d422154f5421e385fec3454f
2176 2195
2177 2196 Age filter:
2178 2197
2179 2198 $ hg init unstable-hash
2180 2199 $ cd unstable-hash
2181 2200 $ hg log --template '{date|age}\n' > /dev/null || exit 1
2182 2201
2183 2202 >>> from __future__ import absolute_import
2184 2203 >>> import datetime
2185 2204 >>> fp = open('a', 'w')
2186 2205 >>> n = datetime.datetime.now() + datetime.timedelta(366 * 7)
2187 2206 >>> fp.write('%d-%d-%d 00:00' % (n.year, n.month, n.day))
2188 2207 >>> fp.close()
2189 2208 $ hg add a
2190 2209 $ hg commit -m future -d "`cat a`"
2191 2210
2192 2211 $ hg log -l1 --template '{date|age}\n'
2193 2212 7 years from now
2194 2213
2195 2214 $ cd ..
2196 2215 $ rm -rf unstable-hash
2197 2216
2198 2217 Add a dummy commit to make up for the instability of the above:
2199 2218
2200 2219 $ echo a > a
2201 2220 $ hg add a
2202 2221 $ hg ci -m future
2203 2222
2204 2223 Count filter:
2205 2224
2206 2225 $ hg log -l1 --template '{node|count} {node|short|count}\n'
2207 2226 40 12
2208 2227
2209 2228 $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
2210 2229 0 1 4
2211 2230
2212 2231 $ hg log -G --template '{rev}: children: {children|count}, \
2213 2232 > tags: {tags|count}, file_adds: {file_adds|count}, \
2214 2233 > ancestors: {revset("ancestors(%s)", rev)|count}'
2215 2234 @ 9: children: 0, tags: 1, file_adds: 1, ancestors: 3
2216 2235 |
2217 2236 o 8: children: 1, tags: 0, file_adds: 2, ancestors: 2
2218 2237 |
2219 2238 o 7: children: 1, tags: 0, file_adds: 1, ancestors: 1
2220 2239
2221 2240 o 6: children: 0, tags: 0, file_adds: 0, ancestors: 7
2222 2241 |\
2223 2242 | o 5: children: 1, tags: 0, file_adds: 1, ancestors: 5
2224 2243 | |
2225 2244 o | 4: children: 1, tags: 0, file_adds: 0, ancestors: 5
2226 2245 |/
2227 2246 o 3: children: 2, tags: 0, file_adds: 0, ancestors: 4
2228 2247 |
2229 2248 o 2: children: 1, tags: 0, file_adds: 1, ancestors: 3
2230 2249 |
2231 2250 o 1: children: 1, tags: 0, file_adds: 1, ancestors: 2
2232 2251 |
2233 2252 o 0: children: 1, tags: 0, file_adds: 1, ancestors: 1
2234 2253
2235 2254
2236 2255 Upper/lower filters:
2237 2256
2238 2257 $ hg log -r0 --template '{branch|upper}\n'
2239 2258 DEFAULT
2240 2259 $ hg log -r0 --template '{author|lower}\n'
2241 2260 user name <user@hostname>
2242 2261 $ hg log -r0 --template '{date|upper}\n'
2243 2262 abort: template filter 'upper' is not compatible with keyword 'date'
2244 2263 [255]
2245 2264
2246 2265 Add a commit that does all possible modifications at once
2247 2266
2248 2267 $ echo modify >> third
2249 2268 $ touch b
2250 2269 $ hg add b
2251 2270 $ hg mv fourth fifth
2252 2271 $ hg rm a
2253 2272 $ hg ci -m "Modify, add, remove, rename"
2254 2273
2255 2274 Check the status template
2256 2275
2257 2276 $ cat <<EOF >> $HGRCPATH
2258 2277 > [extensions]
2259 2278 > color=
2260 2279 > EOF
2261 2280
2262 2281 $ hg log -T status -r 10
2263 2282 changeset: 10:0f9759ec227a
2264 2283 tag: tip
2265 2284 user: test
2266 2285 date: Thu Jan 01 00:00:00 1970 +0000
2267 2286 summary: Modify, add, remove, rename
2268 2287 files:
2269 2288 M third
2270 2289 A b
2271 2290 A fifth
2272 2291 R a
2273 2292 R fourth
2274 2293
2275 2294 $ hg log -T status -C -r 10
2276 2295 changeset: 10:0f9759ec227a
2277 2296 tag: tip
2278 2297 user: test
2279 2298 date: Thu Jan 01 00:00:00 1970 +0000
2280 2299 summary: Modify, add, remove, rename
2281 2300 files:
2282 2301 M third
2283 2302 A b
2284 2303 A fifth
2285 2304 fourth
2286 2305 R a
2287 2306 R fourth
2288 2307
2289 2308 $ hg log -T status -C -r 10 -v
2290 2309 changeset: 10:0f9759ec227a
2291 2310 tag: tip
2292 2311 user: test
2293 2312 date: Thu Jan 01 00:00:00 1970 +0000
2294 2313 description:
2295 2314 Modify, add, remove, rename
2296 2315
2297 2316 files:
2298 2317 M third
2299 2318 A b
2300 2319 A fifth
2301 2320 fourth
2302 2321 R a
2303 2322 R fourth
2304 2323
2305 2324 $ hg log -T status -C -r 10 --debug
2306 2325 changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c
2307 2326 tag: tip
2308 2327 phase: secret
2309 2328 parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066
2310 2329 parent: -1:0000000000000000000000000000000000000000
2311 2330 manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567
2312 2331 user: test
2313 2332 date: Thu Jan 01 00:00:00 1970 +0000
2314 2333 extra: branch=default
2315 2334 description:
2316 2335 Modify, add, remove, rename
2317 2336
2318 2337 files:
2319 2338 M third
2320 2339 A b
2321 2340 A fifth
2322 2341 fourth
2323 2342 R a
2324 2343 R fourth
2325 2344
2326 2345 $ hg log -T status -C -r 10 --quiet
2327 2346 10:0f9759ec227a
2328 2347 $ hg --color=debug log -T status -r 10
2329 2348 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2330 2349 [log.tag|tag: tip]
2331 2350 [log.user|user: test]
2332 2351 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2333 2352 [log.summary|summary: Modify, add, remove, rename]
2334 2353 [ui.note log.files|files:]
2335 2354 [status.modified|M third]
2336 2355 [status.added|A b]
2337 2356 [status.added|A fifth]
2338 2357 [status.removed|R a]
2339 2358 [status.removed|R fourth]
2340 2359
2341 2360 $ hg --color=debug log -T status -C -r 10
2342 2361 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2343 2362 [log.tag|tag: tip]
2344 2363 [log.user|user: test]
2345 2364 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2346 2365 [log.summary|summary: Modify, add, remove, rename]
2347 2366 [ui.note log.files|files:]
2348 2367 [status.modified|M third]
2349 2368 [status.added|A b]
2350 2369 [status.added|A fifth]
2351 2370 [status.copied| fourth]
2352 2371 [status.removed|R a]
2353 2372 [status.removed|R fourth]
2354 2373
2355 2374 $ hg --color=debug log -T status -C -r 10 -v
2356 2375 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2357 2376 [log.tag|tag: tip]
2358 2377 [log.user|user: test]
2359 2378 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2360 2379 [ui.note log.description|description:]
2361 2380 [ui.note log.description|Modify, add, remove, rename]
2362 2381
2363 2382 [ui.note log.files|files:]
2364 2383 [status.modified|M third]
2365 2384 [status.added|A b]
2366 2385 [status.added|A fifth]
2367 2386 [status.copied| fourth]
2368 2387 [status.removed|R a]
2369 2388 [status.removed|R fourth]
2370 2389
2371 2390 $ hg --color=debug log -T status -C -r 10 --debug
2372 2391 [log.changeset changeset.secret|changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c]
2373 2392 [log.tag|tag: tip]
2374 2393 [log.phase|phase: secret]
2375 2394 [log.parent changeset.secret|parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066]
2376 2395 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2377 2396 [ui.debug log.manifest|manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567]
2378 2397 [log.user|user: test]
2379 2398 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2380 2399 [ui.debug log.extra|extra: branch=default]
2381 2400 [ui.note log.description|description:]
2382 2401 [ui.note log.description|Modify, add, remove, rename]
2383 2402
2384 2403 [ui.note log.files|files:]
2385 2404 [status.modified|M third]
2386 2405 [status.added|A b]
2387 2406 [status.added|A fifth]
2388 2407 [status.copied| fourth]
2389 2408 [status.removed|R a]
2390 2409 [status.removed|R fourth]
2391 2410
2392 2411 $ hg --color=debug log -T status -C -r 10 --quiet
2393 2412 [log.node|10:0f9759ec227a]
2394 2413
2395 2414 Check the bisect template
2396 2415
2397 2416 $ hg bisect -g 1
2398 2417 $ hg bisect -b 3 --noupdate
2399 2418 Testing changeset 2:97054abb4ab8 (2 changesets remaining, ~1 tests)
2400 2419 $ hg log -T bisect -r 0:4
2401 2420 changeset: 0:1e4e1b8f71e0
2402 2421 bisect: good (implicit)
2403 2422 user: User Name <user@hostname>
2404 2423 date: Mon Jan 12 13:46:40 1970 +0000
2405 2424 summary: line 1
2406 2425
2407 2426 changeset: 1:b608e9d1a3f0
2408 2427 bisect: good
2409 2428 user: A. N. Other <other@place>
2410 2429 date: Tue Jan 13 17:33:20 1970 +0000
2411 2430 summary: other 1
2412 2431
2413 2432 changeset: 2:97054abb4ab8
2414 2433 bisect: untested
2415 2434 user: other@place
2416 2435 date: Wed Jan 14 21:20:00 1970 +0000
2417 2436 summary: no person
2418 2437
2419 2438 changeset: 3:10e46f2dcbf4
2420 2439 bisect: bad
2421 2440 user: person
2422 2441 date: Fri Jan 16 01:06:40 1970 +0000
2423 2442 summary: no user, no domain
2424 2443
2425 2444 changeset: 4:bbe44766e73d
2426 2445 bisect: bad (implicit)
2427 2446 branch: foo
2428 2447 user: person
2429 2448 date: Sat Jan 17 04:53:20 1970 +0000
2430 2449 summary: new branch
2431 2450
2432 2451 $ hg log --debug -T bisect -r 0:4
2433 2452 changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
2434 2453 bisect: good (implicit)
2435 2454 phase: public
2436 2455 parent: -1:0000000000000000000000000000000000000000
2437 2456 parent: -1:0000000000000000000000000000000000000000
2438 2457 manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
2439 2458 user: User Name <user@hostname>
2440 2459 date: Mon Jan 12 13:46:40 1970 +0000
2441 2460 files+: a
2442 2461 extra: branch=default
2443 2462 description:
2444 2463 line 1
2445 2464 line 2
2446 2465
2447 2466
2448 2467 changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
2449 2468 bisect: good
2450 2469 phase: public
2451 2470 parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
2452 2471 parent: -1:0000000000000000000000000000000000000000
2453 2472 manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
2454 2473 user: A. N. Other <other@place>
2455 2474 date: Tue Jan 13 17:33:20 1970 +0000
2456 2475 files+: b
2457 2476 extra: branch=default
2458 2477 description:
2459 2478 other 1
2460 2479 other 2
2461 2480
2462 2481 other 3
2463 2482
2464 2483
2465 2484 changeset: 2:97054abb4ab824450e9164180baf491ae0078465
2466 2485 bisect: untested
2467 2486 phase: public
2468 2487 parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
2469 2488 parent: -1:0000000000000000000000000000000000000000
2470 2489 manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
2471 2490 user: other@place
2472 2491 date: Wed Jan 14 21:20:00 1970 +0000
2473 2492 files+: c
2474 2493 extra: branch=default
2475 2494 description:
2476 2495 no person
2477 2496
2478 2497
2479 2498 changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
2480 2499 bisect: bad
2481 2500 phase: public
2482 2501 parent: 2:97054abb4ab824450e9164180baf491ae0078465
2483 2502 parent: -1:0000000000000000000000000000000000000000
2484 2503 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
2485 2504 user: person
2486 2505 date: Fri Jan 16 01:06:40 1970 +0000
2487 2506 files: c
2488 2507 extra: branch=default
2489 2508 description:
2490 2509 no user, no domain
2491 2510
2492 2511
2493 2512 changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
2494 2513 bisect: bad (implicit)
2495 2514 branch: foo
2496 2515 phase: draft
2497 2516 parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
2498 2517 parent: -1:0000000000000000000000000000000000000000
2499 2518 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
2500 2519 user: person
2501 2520 date: Sat Jan 17 04:53:20 1970 +0000
2502 2521 extra: branch=foo
2503 2522 description:
2504 2523 new branch
2505 2524
2506 2525
2507 2526 $ hg log -v -T bisect -r 0:4
2508 2527 changeset: 0:1e4e1b8f71e0
2509 2528 bisect: good (implicit)
2510 2529 user: User Name <user@hostname>
2511 2530 date: Mon Jan 12 13:46:40 1970 +0000
2512 2531 files: a
2513 2532 description:
2514 2533 line 1
2515 2534 line 2
2516 2535
2517 2536
2518 2537 changeset: 1:b608e9d1a3f0
2519 2538 bisect: good
2520 2539 user: A. N. Other <other@place>
2521 2540 date: Tue Jan 13 17:33:20 1970 +0000
2522 2541 files: b
2523 2542 description:
2524 2543 other 1
2525 2544 other 2
2526 2545
2527 2546 other 3
2528 2547
2529 2548
2530 2549 changeset: 2:97054abb4ab8
2531 2550 bisect: untested
2532 2551 user: other@place
2533 2552 date: Wed Jan 14 21:20:00 1970 +0000
2534 2553 files: c
2535 2554 description:
2536 2555 no person
2537 2556
2538 2557
2539 2558 changeset: 3:10e46f2dcbf4
2540 2559 bisect: bad
2541 2560 user: person
2542 2561 date: Fri Jan 16 01:06:40 1970 +0000
2543 2562 files: c
2544 2563 description:
2545 2564 no user, no domain
2546 2565
2547 2566
2548 2567 changeset: 4:bbe44766e73d
2549 2568 bisect: bad (implicit)
2550 2569 branch: foo
2551 2570 user: person
2552 2571 date: Sat Jan 17 04:53:20 1970 +0000
2553 2572 description:
2554 2573 new branch
2555 2574
2556 2575
2557 2576 $ hg --color=debug log -T bisect -r 0:4
2558 2577 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
2559 2578 [log.bisect bisect.good|bisect: good (implicit)]
2560 2579 [log.user|user: User Name <user@hostname>]
2561 2580 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2562 2581 [log.summary|summary: line 1]
2563 2582
2564 2583 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
2565 2584 [log.bisect bisect.good|bisect: good]
2566 2585 [log.user|user: A. N. Other <other@place>]
2567 2586 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2568 2587 [log.summary|summary: other 1]
2569 2588
2570 2589 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
2571 2590 [log.bisect bisect.untested|bisect: untested]
2572 2591 [log.user|user: other@place]
2573 2592 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2574 2593 [log.summary|summary: no person]
2575 2594
2576 2595 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
2577 2596 [log.bisect bisect.bad|bisect: bad]
2578 2597 [log.user|user: person]
2579 2598 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2580 2599 [log.summary|summary: no user, no domain]
2581 2600
2582 2601 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
2583 2602 [log.bisect bisect.bad|bisect: bad (implicit)]
2584 2603 [log.branch|branch: foo]
2585 2604 [log.user|user: person]
2586 2605 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2587 2606 [log.summary|summary: new branch]
2588 2607
2589 2608 $ hg --color=debug log --debug -T bisect -r 0:4
2590 2609 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
2591 2610 [log.bisect bisect.good|bisect: good (implicit)]
2592 2611 [log.phase|phase: public]
2593 2612 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2594 2613 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2595 2614 [ui.debug log.manifest|manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0]
2596 2615 [log.user|user: User Name <user@hostname>]
2597 2616 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2598 2617 [ui.debug log.files|files+: a]
2599 2618 [ui.debug log.extra|extra: branch=default]
2600 2619 [ui.note log.description|description:]
2601 2620 [ui.note log.description|line 1
2602 2621 line 2]
2603 2622
2604 2623
2605 2624 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
2606 2625 [log.bisect bisect.good|bisect: good]
2607 2626 [log.phase|phase: public]
2608 2627 [log.parent changeset.public|parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
2609 2628 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2610 2629 [ui.debug log.manifest|manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55]
2611 2630 [log.user|user: A. N. Other <other@place>]
2612 2631 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2613 2632 [ui.debug log.files|files+: b]
2614 2633 [ui.debug log.extra|extra: branch=default]
2615 2634 [ui.note log.description|description:]
2616 2635 [ui.note log.description|other 1
2617 2636 other 2
2618 2637
2619 2638 other 3]
2620 2639
2621 2640
2622 2641 [log.changeset changeset.public|changeset: 2:97054abb4ab824450e9164180baf491ae0078465]
2623 2642 [log.bisect bisect.untested|bisect: untested]
2624 2643 [log.phase|phase: public]
2625 2644 [log.parent changeset.public|parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
2626 2645 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2627 2646 [ui.debug log.manifest|manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1]
2628 2647 [log.user|user: other@place]
2629 2648 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2630 2649 [ui.debug log.files|files+: c]
2631 2650 [ui.debug log.extra|extra: branch=default]
2632 2651 [ui.note log.description|description:]
2633 2652 [ui.note log.description|no person]
2634 2653
2635 2654
2636 2655 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
2637 2656 [log.bisect bisect.bad|bisect: bad]
2638 2657 [log.phase|phase: public]
2639 2658 [log.parent changeset.public|parent: 2:97054abb4ab824450e9164180baf491ae0078465]
2640 2659 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2641 2660 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
2642 2661 [log.user|user: person]
2643 2662 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2644 2663 [ui.debug log.files|files: c]
2645 2664 [ui.debug log.extra|extra: branch=default]
2646 2665 [ui.note log.description|description:]
2647 2666 [ui.note log.description|no user, no domain]
2648 2667
2649 2668
2650 2669 [log.changeset changeset.draft|changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74]
2651 2670 [log.bisect bisect.bad|bisect: bad (implicit)]
2652 2671 [log.branch|branch: foo]
2653 2672 [log.phase|phase: draft]
2654 2673 [log.parent changeset.public|parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
2655 2674 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2656 2675 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
2657 2676 [log.user|user: person]
2658 2677 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2659 2678 [ui.debug log.extra|extra: branch=foo]
2660 2679 [ui.note log.description|description:]
2661 2680 [ui.note log.description|new branch]
2662 2681
2663 2682
2664 2683 $ hg --color=debug log -v -T bisect -r 0:4
2665 2684 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
2666 2685 [log.bisect bisect.good|bisect: good (implicit)]
2667 2686 [log.user|user: User Name <user@hostname>]
2668 2687 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2669 2688 [ui.note log.files|files: a]
2670 2689 [ui.note log.description|description:]
2671 2690 [ui.note log.description|line 1
2672 2691 line 2]
2673 2692
2674 2693
2675 2694 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
2676 2695 [log.bisect bisect.good|bisect: good]
2677 2696 [log.user|user: A. N. Other <other@place>]
2678 2697 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2679 2698 [ui.note log.files|files: b]
2680 2699 [ui.note log.description|description:]
2681 2700 [ui.note log.description|other 1
2682 2701 other 2
2683 2702
2684 2703 other 3]
2685 2704
2686 2705
2687 2706 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
2688 2707 [log.bisect bisect.untested|bisect: untested]
2689 2708 [log.user|user: other@place]
2690 2709 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2691 2710 [ui.note log.files|files: c]
2692 2711 [ui.note log.description|description:]
2693 2712 [ui.note log.description|no person]
2694 2713
2695 2714
2696 2715 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
2697 2716 [log.bisect bisect.bad|bisect: bad]
2698 2717 [log.user|user: person]
2699 2718 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2700 2719 [ui.note log.files|files: c]
2701 2720 [ui.note log.description|description:]
2702 2721 [ui.note log.description|no user, no domain]
2703 2722
2704 2723
2705 2724 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
2706 2725 [log.bisect bisect.bad|bisect: bad (implicit)]
2707 2726 [log.branch|branch: foo]
2708 2727 [log.user|user: person]
2709 2728 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2710 2729 [ui.note log.description|description:]
2711 2730 [ui.note log.description|new branch]
2712 2731
2713 2732
2714 2733 $ hg bisect --reset
2715 2734
2716 2735 Error on syntax:
2717 2736
2718 2737 $ echo 'x = "f' >> t
2719 2738 $ hg log
2720 2739 hg: parse error at t:3: unmatched quotes
2721 2740 [255]
2722 2741
2723 2742 $ hg log -T '{date'
2724 2743 hg: parse error at 1: unterminated template expansion
2725 2744 [255]
2726 2745
2727 2746 Behind the scenes, this will throw TypeError
2728 2747
2729 2748 $ hg log -l 3 --template '{date|obfuscate}\n'
2730 2749 abort: template filter 'obfuscate' is not compatible with keyword 'date'
2731 2750 [255]
2732 2751
2733 2752 Behind the scenes, this will throw a ValueError
2734 2753
2735 2754 $ hg log -l 3 --template 'line: {desc|shortdate}\n'
2736 2755 abort: template filter 'shortdate' is not compatible with keyword 'desc'
2737 2756 [255]
2738 2757
2739 2758 Behind the scenes, this will throw AttributeError
2740 2759
2741 2760 $ hg log -l 3 --template 'line: {date|escape}\n'
2742 2761 abort: template filter 'escape' is not compatible with keyword 'date'
2743 2762 [255]
2744 2763
2745 2764 $ hg log -l 3 --template 'line: {extras|localdate}\n'
2746 2765 hg: parse error: localdate expects a date information
2747 2766 [255]
2748 2767
2749 2768 Behind the scenes, this will throw ValueError
2750 2769
2751 2770 $ hg tip --template '{author|email|date}\n'
2752 2771 hg: parse error: date expects a date information
2753 2772 [255]
2754 2773
2755 2774 $ hg tip -T '{author|email|shortdate}\n'
2756 2775 abort: template filter 'shortdate' is not compatible with keyword 'author'
2757 2776 [255]
2758 2777
2759 2778 $ hg tip -T '{get(extras, "branch")|shortdate}\n'
2760 2779 abort: incompatible use of template filter 'shortdate'
2761 2780 [255]
2762 2781
2763 2782 Error in nested template:
2764 2783
2765 2784 $ hg log -T '{"date'
2766 2785 hg: parse error at 2: unterminated string
2767 2786 [255]
2768 2787
2769 2788 $ hg log -T '{"foo{date|?}"}'
2770 2789 hg: parse error at 11: syntax error
2771 2790 [255]
2772 2791
2773 2792 Thrown an error if a template function doesn't exist
2774 2793
2775 2794 $ hg tip --template '{foo()}\n'
2776 2795 hg: parse error: unknown function 'foo'
2777 2796 [255]
2778 2797
2779 2798 Pass generator object created by template function to filter
2780 2799
2781 2800 $ hg log -l 1 --template '{if(author, author)|user}\n'
2782 2801 test
2783 2802
2784 2803 Test index keyword:
2785 2804
2786 2805 $ hg log -l 2 -T '{index + 10}{files % " {index}:{file}"}\n'
2787 2806 10 0:a 1:b 2:fifth 3:fourth 4:third
2788 2807 11 0:a
2789 2808
2790 2809 $ hg branches -T '{index} {branch}\n'
2791 2810 0 default
2792 2811 1 foo
2793 2812
2794 2813 Test diff function:
2795 2814
2796 2815 $ hg diff -c 8
2797 2816 diff -r 29114dbae42b -r 95c24699272e fourth
2798 2817 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2799 2818 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2800 2819 @@ -0,0 +1,1 @@
2801 2820 +second
2802 2821 diff -r 29114dbae42b -r 95c24699272e second
2803 2822 --- a/second Mon Jan 12 13:46:40 1970 +0000
2804 2823 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2805 2824 @@ -1,1 +0,0 @@
2806 2825 -second
2807 2826 diff -r 29114dbae42b -r 95c24699272e third
2808 2827 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2809 2828 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2810 2829 @@ -0,0 +1,1 @@
2811 2830 +third
2812 2831
2813 2832 $ hg log -r 8 -T "{diff()}"
2814 2833 diff -r 29114dbae42b -r 95c24699272e fourth
2815 2834 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2816 2835 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2817 2836 @@ -0,0 +1,1 @@
2818 2837 +second
2819 2838 diff -r 29114dbae42b -r 95c24699272e second
2820 2839 --- a/second Mon Jan 12 13:46:40 1970 +0000
2821 2840 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2822 2841 @@ -1,1 +0,0 @@
2823 2842 -second
2824 2843 diff -r 29114dbae42b -r 95c24699272e third
2825 2844 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2826 2845 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2827 2846 @@ -0,0 +1,1 @@
2828 2847 +third
2829 2848
2830 2849 $ hg log -r 8 -T "{diff('glob:f*')}"
2831 2850 diff -r 29114dbae42b -r 95c24699272e fourth
2832 2851 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2833 2852 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2834 2853 @@ -0,0 +1,1 @@
2835 2854 +second
2836 2855
2837 2856 $ hg log -r 8 -T "{diff('', 'glob:f*')}"
2838 2857 diff -r 29114dbae42b -r 95c24699272e second
2839 2858 --- a/second Mon Jan 12 13:46:40 1970 +0000
2840 2859 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2841 2860 @@ -1,1 +0,0 @@
2842 2861 -second
2843 2862 diff -r 29114dbae42b -r 95c24699272e third
2844 2863 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2845 2864 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2846 2865 @@ -0,0 +1,1 @@
2847 2866 +third
2848 2867
2849 2868 $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
2850 2869 diff -r 29114dbae42b -r 95c24699272e fourth
2851 2870 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2852 2871 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2853 2872 @@ -0,0 +1,1 @@
2854 2873 +second
2855 2874
2856 2875 $ cd ..
2857 2876
2858 2877
2859 2878 latesttag:
2860 2879
2861 2880 $ hg init latesttag
2862 2881 $ cd latesttag
2863 2882
2864 2883 $ echo a > file
2865 2884 $ hg ci -Am a -d '0 0'
2866 2885 adding file
2867 2886
2868 2887 $ echo b >> file
2869 2888 $ hg ci -m b -d '1 0'
2870 2889
2871 2890 $ echo c >> head1
2872 2891 $ hg ci -Am h1c -d '2 0'
2873 2892 adding head1
2874 2893
2875 2894 $ hg update -q 1
2876 2895 $ echo d >> head2
2877 2896 $ hg ci -Am h2d -d '3 0'
2878 2897 adding head2
2879 2898 created new head
2880 2899
2881 2900 $ echo e >> head2
2882 2901 $ hg ci -m h2e -d '4 0'
2883 2902
2884 2903 $ hg merge -q
2885 2904 $ hg ci -m merge -d '5 -3600'
2886 2905
2887 2906 No tag set:
2888 2907
2889 2908 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
2890 2909 @ 5: null+5
2891 2910 |\
2892 2911 | o 4: null+4
2893 2912 | |
2894 2913 | o 3: null+3
2895 2914 | |
2896 2915 o | 2: null+3
2897 2916 |/
2898 2917 o 1: null+2
2899 2918 |
2900 2919 o 0: null+1
2901 2920
2902 2921
2903 2922 One common tag: longest path wins for {latesttagdistance}:
2904 2923
2905 2924 $ hg tag -r 1 -m t1 -d '6 0' t1
2906 2925 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
2907 2926 @ 6: t1+4
2908 2927 |
2909 2928 o 5: t1+3
2910 2929 |\
2911 2930 | o 4: t1+2
2912 2931 | |
2913 2932 | o 3: t1+1
2914 2933 | |
2915 2934 o | 2: t1+1
2916 2935 |/
2917 2936 o 1: t1+0
2918 2937 |
2919 2938 o 0: null+1
2920 2939
2921 2940
2922 2941 One ancestor tag: closest wins:
2923 2942
2924 2943 $ hg tag -r 2 -m t2 -d '7 0' t2
2925 2944 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
2926 2945 @ 7: t2+3
2927 2946 |
2928 2947 o 6: t2+2
2929 2948 |
2930 2949 o 5: t2+1
2931 2950 |\
2932 2951 | o 4: t1+2
2933 2952 | |
2934 2953 | o 3: t1+1
2935 2954 | |
2936 2955 o | 2: t2+0
2937 2956 |/
2938 2957 o 1: t1+0
2939 2958 |
2940 2959 o 0: null+1
2941 2960
2942 2961
2943 2962 Two branch tags: more recent wins if same number of changes:
2944 2963
2945 2964 $ hg tag -r 3 -m t3 -d '8 0' t3
2946 2965 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
2947 2966 @ 8: t3+5
2948 2967 |
2949 2968 o 7: t3+4
2950 2969 |
2951 2970 o 6: t3+3
2952 2971 |
2953 2972 o 5: t3+2
2954 2973 |\
2955 2974 | o 4: t3+1
2956 2975 | |
2957 2976 | o 3: t3+0
2958 2977 | |
2959 2978 o | 2: t2+0
2960 2979 |/
2961 2980 o 1: t1+0
2962 2981 |
2963 2982 o 0: null+1
2964 2983
2965 2984
2966 2985 Two branch tags: fewest changes wins:
2967 2986
2968 2987 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
2969 2988 $ hg log -G --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
2970 2989 @ 9: t4+5,6
2971 2990 |
2972 2991 o 8: t4+4,5
2973 2992 |
2974 2993 o 7: t4+3,4
2975 2994 |
2976 2995 o 6: t4+2,3
2977 2996 |
2978 2997 o 5: t4+1,2
2979 2998 |\
2980 2999 | o 4: t4+0,0
2981 3000 | |
2982 3001 | o 3: t3+0,0
2983 3002 | |
2984 3003 o | 2: t2+0,0
2985 3004 |/
2986 3005 o 1: t1+0,0
2987 3006 |
2988 3007 o 0: null+1,1
2989 3008
2990 3009
2991 3010 Merged tag overrides:
2992 3011
2993 3012 $ hg tag -r 5 -m t5 -d '9 0' t5
2994 3013 $ hg tag -r 3 -m at3 -d '10 0' at3
2995 3014 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
2996 3015 @ 11: t5+6
2997 3016 |
2998 3017 o 10: t5+5
2999 3018 |
3000 3019 o 9: t5+4
3001 3020 |
3002 3021 o 8: t5+3
3003 3022 |
3004 3023 o 7: t5+2
3005 3024 |
3006 3025 o 6: t5+1
3007 3026 |
3008 3027 o 5: t5+0
3009 3028 |\
3010 3029 | o 4: t4+0
3011 3030 | |
3012 3031 | o 3: at3:t3+0
3013 3032 | |
3014 3033 o | 2: t2+0
3015 3034 |/
3016 3035 o 1: t1+0
3017 3036 |
3018 3037 o 0: null+1
3019 3038
3020 3039
3021 3040 $ hg log -G --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
3022 3041 @ 11: t5+6,6
3023 3042 |
3024 3043 o 10: t5+5,5
3025 3044 |
3026 3045 o 9: t5+4,4
3027 3046 |
3028 3047 o 8: t5+3,3
3029 3048 |
3030 3049 o 7: t5+2,2
3031 3050 |
3032 3051 o 6: t5+1,1
3033 3052 |
3034 3053 o 5: t5+0,0
3035 3054 |\
3036 3055 | o 4: t4+0,0
3037 3056 | |
3038 3057 | o 3: at3+0,0 t3+0,0
3039 3058 | |
3040 3059 o | 2: t2+0,0
3041 3060 |/
3042 3061 o 1: t1+0,0
3043 3062 |
3044 3063 o 0: null+1,1
3045 3064
3046 3065
3047 3066 $ hg log -G --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
3048 3067 @ 11: t3, C: 9, D: 8
3049 3068 |
3050 3069 o 10: t3, C: 8, D: 7
3051 3070 |
3052 3071 o 9: t3, C: 7, D: 6
3053 3072 |
3054 3073 o 8: t3, C: 6, D: 5
3055 3074 |
3056 3075 o 7: t3, C: 5, D: 4
3057 3076 |
3058 3077 o 6: t3, C: 4, D: 3
3059 3078 |
3060 3079 o 5: t3, C: 3, D: 2
3061 3080 |\
3062 3081 | o 4: t3, C: 1, D: 1
3063 3082 | |
3064 3083 | o 3: t3, C: 0, D: 0
3065 3084 | |
3066 3085 o | 2: t1, C: 1, D: 1
3067 3086 |/
3068 3087 o 1: t1, C: 0, D: 0
3069 3088 |
3070 3089 o 0: null, C: 1, D: 1
3071 3090
3072 3091
3073 3092 $ cd ..
3074 3093
3075 3094
3076 3095 Style path expansion: issue1948 - ui.style option doesn't work on OSX
3077 3096 if it is a relative path
3078 3097
3079 3098 $ mkdir -p home/styles
3080 3099
3081 3100 $ cat > home/styles/teststyle <<EOF
3082 3101 > changeset = 'test {rev}:{node|short}\n'
3083 3102 > EOF
3084 3103
3085 3104 $ HOME=`pwd`/home; export HOME
3086 3105
3087 3106 $ cat > latesttag/.hg/hgrc <<EOF
3088 3107 > [ui]
3089 3108 > style = ~/styles/teststyle
3090 3109 > EOF
3091 3110
3092 3111 $ hg -R latesttag tip
3093 3112 test 11:97e5943b523a
3094 3113
3095 3114 Test recursive showlist template (issue1989):
3096 3115
3097 3116 $ cat > style1989 <<EOF
3098 3117 > changeset = '{file_mods}{manifest}{extras}'
3099 3118 > file_mod = 'M|{author|person}\n'
3100 3119 > manifest = '{rev},{author}\n'
3101 3120 > extra = '{key}: {author}\n'
3102 3121 > EOF
3103 3122
3104 3123 $ hg -R latesttag log -r tip --style=style1989
3105 3124 M|test
3106 3125 11,test
3107 3126 branch: test
3108 3127
3109 3128 Test new-style inline templating:
3110 3129
3111 3130 $ hg log -R latesttag -r tip --template 'modified files: {file_mods % " {file}\n"}\n'
3112 3131 modified files: .hgtags
3113 3132
3114 3133
3115 3134 $ hg log -R latesttag -r tip -T '{rev % "a"}\n'
3116 3135 hg: parse error: keyword 'rev' is not iterable
3117 3136 [255]
3118 3137 $ hg log -R latesttag -r tip -T '{get(extras, "unknown") % "a"}\n'
3119 3138 hg: parse error: None is not iterable
3120 3139 [255]
3121 3140
3122 3141 Test new-style inline templating of non-list/dict type:
3123 3142
3124 3143 $ hg log -R latesttag -r tip -T '{manifest}\n'
3125 3144 11:2bc6e9006ce2
3126 3145 $ hg log -R latesttag -r tip -T 'string length: {manifest|count}\n'
3127 3146 string length: 15
3128 3147 $ hg log -R latesttag -r tip -T '{manifest % "{rev}:{node}"}\n'
3129 3148 11:2bc6e9006ce29882383a22d39fd1f4e66dd3e2fc
3130 3149
3131 3150 $ hg log -R latesttag -r tip -T '{get(extras, "branch") % "{key}: {value}\n"}'
3132 3151 branch: default
3133 3152 $ hg log -R latesttag -r tip -T '{get(extras, "unknown") % "{key}\n"}'
3134 3153 hg: parse error: None is not iterable
3135 3154 [255]
3136 3155 $ hg log -R latesttag -r tip -T '{min(extras) % "{key}: {value}\n"}'
3137 3156 branch: default
3138 3157 $ hg log -R latesttag -l1 -T '{min(revset("0:9")) % "{rev}:{node|short}\n"}'
3139 3158 0:ce3cec86e6c2
3140 3159 $ hg log -R latesttag -l1 -T '{max(revset("0:9")) % "{rev}:{node|short}\n"}'
3141 3160 9:fbc7cd862e9c
3142 3161
3143 3162 Test manifest/get() can be join()-ed as before, though it's silly:
3144 3163
3145 3164 $ hg log -R latesttag -r tip -T '{join(manifest, "")}\n'
3146 3165 11:2bc6e9006ce2
3147 3166 $ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), "")}\n'
3148 3167 default
3149 3168
3150 3169 Test min/max of integers
3151 3170
3152 3171 $ hg log -R latesttag -l1 -T '{min(revset("9:10"))}\n'
3153 3172 9
3154 3173 $ hg log -R latesttag -l1 -T '{max(revset("9:10"))}\n'
3155 3174 10
3156 3175
3157 3176 Test dot operator precedence:
3158 3177
3159 3178 $ hg debugtemplate -R latesttag -r0 -v '{manifest.node|short}\n'
3160 3179 (template
3161 3180 (|
3162 3181 (.
3163 3182 (symbol 'manifest')
3164 3183 (symbol 'node'))
3165 3184 (symbol 'short'))
3166 3185 (string '\n'))
3167 3186 89f4071fec70
3168 3187
3169 3188 (the following examples are invalid, but seem natural in parsing POV)
3170 3189
3171 3190 $ hg debugtemplate -R latesttag -r0 -v '{foo|bar.baz}\n' 2> /dev/null
3172 3191 (template
3173 3192 (|
3174 3193 (symbol 'foo')
3175 3194 (.
3176 3195 (symbol 'bar')
3177 3196 (symbol 'baz')))
3178 3197 (string '\n'))
3179 3198 [255]
3180 3199 $ hg debugtemplate -R latesttag -r0 -v '{foo.bar()}\n' 2> /dev/null
3181 3200 (template
3182 3201 (.
3183 3202 (symbol 'foo')
3184 3203 (func
3185 3204 (symbol 'bar')
3186 3205 None))
3187 3206 (string '\n'))
3188 3207 [255]
3189 3208
3190 3209 Test evaluation of dot operator:
3191 3210
3192 3211 $ hg log -R latesttag -l1 -T '{min(revset("0:9")).node}\n'
3193 3212 ce3cec86e6c26bd9bdfc590a6b92abc9680f1796
3194 3213 $ hg log -R latesttag -r0 -T '{extras.branch}\n'
3195 3214 default
3196 3215
3197 3216 $ hg log -R latesttag -l1 -T '{author.invalid}\n'
3198 3217 hg: parse error: keyword 'author' has no member
3199 3218 [255]
3200 3219 $ hg log -R latesttag -l1 -T '{min("abc").invalid}\n'
3201 3220 hg: parse error: 'a' has no member
3202 3221 [255]
3203 3222
3204 3223 Test the sub function of templating for expansion:
3205 3224
3206 3225 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
3207 3226 xx
3208 3227
3209 3228 $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
3210 3229 hg: parse error: sub got an invalid pattern: [
3211 3230 [255]
3212 3231 $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
3213 3232 hg: parse error: sub got an invalid replacement: \1
3214 3233 [255]
3215 3234
3216 3235 Test the strip function with chars specified:
3217 3236
3218 3237 $ hg log -R latesttag --template '{desc}\n'
3219 3238 at3
3220 3239 t5
3221 3240 t4
3222 3241 t3
3223 3242 t2
3224 3243 t1
3225 3244 merge
3226 3245 h2e
3227 3246 h2d
3228 3247 h1c
3229 3248 b
3230 3249 a
3231 3250
3232 3251 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
3233 3252 at3
3234 3253 5
3235 3254 4
3236 3255 3
3237 3256 2
3238 3257 1
3239 3258 merg
3240 3259 h2
3241 3260 h2d
3242 3261 h1c
3243 3262 b
3244 3263 a
3245 3264
3246 3265 Test date format:
3247 3266
3248 3267 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
3249 3268 date: 70 01 01 10 +0000
3250 3269 date: 70 01 01 09 +0000
3251 3270 date: 70 01 01 04 +0000
3252 3271 date: 70 01 01 08 +0000
3253 3272 date: 70 01 01 07 +0000
3254 3273 date: 70 01 01 06 +0000
3255 3274 date: 70 01 01 05 +0100
3256 3275 date: 70 01 01 04 +0000
3257 3276 date: 70 01 01 03 +0000
3258 3277 date: 70 01 01 02 +0000
3259 3278 date: 70 01 01 01 +0000
3260 3279 date: 70 01 01 00 +0000
3261 3280
3262 3281 Test invalid date:
3263 3282
3264 3283 $ hg log -R latesttag -T '{date(rev)}\n'
3265 3284 hg: parse error: date expects a date information
3266 3285 [255]
3267 3286
3268 3287 Test integer literal:
3269 3288
3270 3289 $ hg debugtemplate -v '{(0)}\n'
3271 3290 (template
3272 3291 (group
3273 3292 (integer '0'))
3274 3293 (string '\n'))
3275 3294 0
3276 3295 $ hg debugtemplate -v '{(123)}\n'
3277 3296 (template
3278 3297 (group
3279 3298 (integer '123'))
3280 3299 (string '\n'))
3281 3300 123
3282 3301 $ hg debugtemplate -v '{(-4)}\n'
3283 3302 (template
3284 3303 (group
3285 3304 (negate
3286 3305 (integer '4')))
3287 3306 (string '\n'))
3288 3307 -4
3289 3308 $ hg debugtemplate '{(-)}\n'
3290 3309 hg: parse error at 3: not a prefix: )
3291 3310 [255]
3292 3311 $ hg debugtemplate '{(-a)}\n'
3293 3312 hg: parse error: negation needs an integer argument
3294 3313 [255]
3295 3314
3296 3315 top-level integer literal is interpreted as symbol (i.e. variable name):
3297 3316
3298 3317 $ hg debugtemplate -D 1=one -v '{1}\n'
3299 3318 (template
3300 3319 (integer '1')
3301 3320 (string '\n'))
3302 3321 one
3303 3322 $ hg debugtemplate -D 1=one -v '{if("t", "{1}")}\n'
3304 3323 (template
3305 3324 (func
3306 3325 (symbol 'if')
3307 3326 (list
3308 3327 (string 't')
3309 3328 (template
3310 3329 (integer '1'))))
3311 3330 (string '\n'))
3312 3331 one
3313 3332 $ hg debugtemplate -D 1=one -v '{1|stringify}\n'
3314 3333 (template
3315 3334 (|
3316 3335 (integer '1')
3317 3336 (symbol 'stringify'))
3318 3337 (string '\n'))
3319 3338 one
3320 3339
3321 3340 unless explicit symbol is expected:
3322 3341
3323 3342 $ hg log -Ra -r0 -T '{desc|1}\n'
3324 3343 hg: parse error: expected a symbol, got 'integer'
3325 3344 [255]
3326 3345 $ hg log -Ra -r0 -T '{1()}\n'
3327 3346 hg: parse error: expected a symbol, got 'integer'
3328 3347 [255]
3329 3348
3330 3349 Test string literal:
3331 3350
3332 3351 $ hg debugtemplate -Ra -r0 -v '{"string with no template fragment"}\n'
3333 3352 (template
3334 3353 (string 'string with no template fragment')
3335 3354 (string '\n'))
3336 3355 string with no template fragment
3337 3356 $ hg debugtemplate -Ra -r0 -v '{"template: {rev}"}\n'
3338 3357 (template
3339 3358 (template
3340 3359 (string 'template: ')
3341 3360 (symbol 'rev'))
3342 3361 (string '\n'))
3343 3362 template: 0
3344 3363 $ hg debugtemplate -Ra -r0 -v '{r"rawstring: {rev}"}\n'
3345 3364 (template
3346 3365 (string 'rawstring: {rev}')
3347 3366 (string '\n'))
3348 3367 rawstring: {rev}
3349 3368 $ hg debugtemplate -Ra -r0 -v '{files % r"rawstring: {file}"}\n'
3350 3369 (template
3351 3370 (%
3352 3371 (symbol 'files')
3353 3372 (string 'rawstring: {file}'))
3354 3373 (string '\n'))
3355 3374 rawstring: {file}
3356 3375
3357 3376 Test string escaping:
3358 3377
3359 3378 $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
3360 3379 >
3361 3380 <>\n<[>
3362 3381 <>\n<]>
3363 3382 <>\n<
3364 3383
3365 3384 $ hg log -R latesttag -r 0 \
3366 3385 > --config ui.logtemplate='>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
3367 3386 >
3368 3387 <>\n<[>
3369 3388 <>\n<]>
3370 3389 <>\n<
3371 3390
3372 3391 $ hg log -R latesttag -r 0 -T esc \
3373 3392 > --config templates.esc='>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
3374 3393 >
3375 3394 <>\n<[>
3376 3395 <>\n<]>
3377 3396 <>\n<
3378 3397
3379 3398 $ cat <<'EOF' > esctmpl
3380 3399 > changeset = '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
3381 3400 > EOF
3382 3401 $ hg log -R latesttag -r 0 --style ./esctmpl
3383 3402 >
3384 3403 <>\n<[>
3385 3404 <>\n<]>
3386 3405 <>\n<
3387 3406
3388 3407 Test string escaping of quotes:
3389 3408
3390 3409 $ hg log -Ra -r0 -T '{"\""}\n'
3391 3410 "
3392 3411 $ hg log -Ra -r0 -T '{"\\\""}\n'
3393 3412 \"
3394 3413 $ hg log -Ra -r0 -T '{r"\""}\n'
3395 3414 \"
3396 3415 $ hg log -Ra -r0 -T '{r"\\\""}\n'
3397 3416 \\\"
3398 3417
3399 3418
3400 3419 $ hg log -Ra -r0 -T '{"\""}\n'
3401 3420 "
3402 3421 $ hg log -Ra -r0 -T '{"\\\""}\n'
3403 3422 \"
3404 3423 $ hg log -Ra -r0 -T '{r"\""}\n'
3405 3424 \"
3406 3425 $ hg log -Ra -r0 -T '{r"\\\""}\n'
3407 3426 \\\"
3408 3427
3409 3428 Test exception in quoted template. single backslash before quotation mark is
3410 3429 stripped before parsing:
3411 3430
3412 3431 $ cat <<'EOF' > escquotetmpl
3413 3432 > changeset = "\" \\" \\\" \\\\" {files % \"{file}\"}\n"
3414 3433 > EOF
3415 3434 $ cd latesttag
3416 3435 $ hg log -r 2 --style ../escquotetmpl
3417 3436 " \" \" \\" head1
3418 3437
3419 3438 $ hg log -r 2 -T esc --config templates.esc='"{\"valid\"}\n"'
3420 3439 valid
3421 3440 $ hg log -r 2 -T esc --config templates.esc="'"'{\'"'"'valid\'"'"'}\n'"'"
3422 3441 valid
3423 3442
3424 3443 Test compatibility with 2.9.2-3.4 of escaped quoted strings in nested
3425 3444 _evalifliteral() templates (issue4733):
3426 3445
3427 3446 $ hg log -r 2 -T '{if(rev, "\"{rev}")}\n'
3428 3447 "2
3429 3448 $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\"{rev}\")}")}\n'
3430 3449 "2
3431 3450 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, \\\"\\\\\\\"{rev}\\\")}\")}")}\n'
3432 3451 "2
3433 3452
3434 3453 $ hg log -r 2 -T '{if(rev, "\\\"")}\n'
3435 3454 \"
3436 3455 $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\\\\\"\")}")}\n'
3437 3456 \"
3438 3457 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, \\\"\\\\\\\\\\\\\\\"\\\")}\")}")}\n'
3439 3458 \"
3440 3459
3441 3460 $ hg log -r 2 -T '{if(rev, r"\\\"")}\n'
3442 3461 \\\"
3443 3462 $ hg log -r 2 -T '{if(rev, "{if(rev, r\"\\\\\\\"\")}")}\n'
3444 3463 \\\"
3445 3464 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, r\\\"\\\\\\\\\\\\\\\"\\\")}\")}")}\n'
3446 3465 \\\"
3447 3466
3448 3467 escaped single quotes and errors:
3449 3468
3450 3469 $ hg log -r 2 -T "{if(rev, '{if(rev, \'foo\')}')}"'\n'
3451 3470 foo
3452 3471 $ hg log -r 2 -T "{if(rev, '{if(rev, r\'foo\')}')}"'\n'
3453 3472 foo
3454 3473 $ hg log -r 2 -T '{if(rev, "{if(rev, \")}")}\n'
3455 3474 hg: parse error at 21: unterminated string
3456 3475 [255]
3457 3476 $ hg log -r 2 -T '{if(rev, \"\\"")}\n'
3458 3477 hg: parse error: trailing \ in string
3459 3478 [255]
3460 3479 $ hg log -r 2 -T '{if(rev, r\"\\"")}\n'
3461 3480 hg: parse error: trailing \ in string
3462 3481 [255]
3463 3482
3464 3483 $ cd ..
3465 3484
3466 3485 Test leading backslashes:
3467 3486
3468 3487 $ cd latesttag
3469 3488 $ hg log -r 2 -T '\{rev} {files % "\{file}"}\n'
3470 3489 {rev} {file}
3471 3490 $ hg log -r 2 -T '\\{rev} {files % "\\{file}"}\n'
3472 3491 \2 \head1
3473 3492 $ hg log -r 2 -T '\\\{rev} {files % "\\\{file}"}\n'
3474 3493 \{rev} \{file}
3475 3494 $ cd ..
3476 3495
3477 3496 Test leading backslashes in "if" expression (issue4714):
3478 3497
3479 3498 $ cd latesttag
3480 3499 $ hg log -r 2 -T '{if("1", "\{rev}")} {if("1", r"\{rev}")}\n'
3481 3500 {rev} \{rev}
3482 3501 $ hg log -r 2 -T '{if("1", "\\{rev}")} {if("1", r"\\{rev}")}\n'
3483 3502 \2 \\{rev}
3484 3503 $ hg log -r 2 -T '{if("1", "\\\{rev}")} {if("1", r"\\\{rev}")}\n'
3485 3504 \{rev} \\\{rev}
3486 3505 $ cd ..
3487 3506
3488 3507 "string-escape"-ed "\x5c\x786e" becomes r"\x6e" (once) or r"n" (twice)
3489 3508
3490 3509 $ hg log -R a -r 0 --template '{if("1", "\x5c\x786e", "NG")}\n'
3491 3510 \x6e
3492 3511 $ hg log -R a -r 0 --template '{if("1", r"\x5c\x786e", "NG")}\n'
3493 3512 \x5c\x786e
3494 3513 $ hg log -R a -r 0 --template '{if("", "NG", "\x5c\x786e")}\n'
3495 3514 \x6e
3496 3515 $ hg log -R a -r 0 --template '{if("", "NG", r"\x5c\x786e")}\n'
3497 3516 \x5c\x786e
3498 3517
3499 3518 $ hg log -R a -r 2 --template '{ifeq("no perso\x6e", desc, "\x5c\x786e", "NG")}\n'
3500 3519 \x6e
3501 3520 $ hg log -R a -r 2 --template '{ifeq(r"no perso\x6e", desc, "NG", r"\x5c\x786e")}\n'
3502 3521 \x5c\x786e
3503 3522 $ hg log -R a -r 2 --template '{ifeq(desc, "no perso\x6e", "\x5c\x786e", "NG")}\n'
3504 3523 \x6e
3505 3524 $ hg log -R a -r 2 --template '{ifeq(desc, r"no perso\x6e", "NG", r"\x5c\x786e")}\n'
3506 3525 \x5c\x786e
3507 3526
3508 3527 $ hg log -R a -r 8 --template '{join(files, "\n")}\n'
3509 3528 fourth
3510 3529 second
3511 3530 third
3512 3531 $ hg log -R a -r 8 --template '{join(files, r"\n")}\n'
3513 3532 fourth\nsecond\nthird
3514 3533
3515 3534 $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", "htm\x6c")}'
3516 3535 <p>
3517 3536 1st
3518 3537 </p>
3519 3538 <p>
3520 3539 2nd
3521 3540 </p>
3522 3541 $ hg log -R a -r 2 --template '{rstdoc(r"1st\n\n2nd", "html")}'
3523 3542 <p>
3524 3543 1st\n\n2nd
3525 3544 </p>
3526 3545 $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", r"htm\x6c")}'
3527 3546 1st
3528 3547
3529 3548 2nd
3530 3549
3531 3550 $ hg log -R a -r 2 --template '{strip(desc, "\x6e")}\n'
3532 3551 o perso
3533 3552 $ hg log -R a -r 2 --template '{strip(desc, r"\x6e")}\n'
3534 3553 no person
3535 3554 $ hg log -R a -r 2 --template '{strip("no perso\x6e", "\x6e")}\n'
3536 3555 o perso
3537 3556 $ hg log -R a -r 2 --template '{strip(r"no perso\x6e", r"\x6e")}\n'
3538 3557 no perso
3539 3558
3540 3559 $ hg log -R a -r 2 --template '{sub("\\x6e", "\x2d", desc)}\n'
3541 3560 -o perso-
3542 3561 $ hg log -R a -r 2 --template '{sub(r"\\x6e", "-", desc)}\n'
3543 3562 no person
3544 3563 $ hg log -R a -r 2 --template '{sub("n", r"\x2d", desc)}\n'
3545 3564 \x2do perso\x2d
3546 3565 $ hg log -R a -r 2 --template '{sub("n", "\x2d", "no perso\x6e")}\n'
3547 3566 -o perso-
3548 3567 $ hg log -R a -r 2 --template '{sub("n", r"\x2d", r"no perso\x6e")}\n'
3549 3568 \x2do perso\x6e
3550 3569
3551 3570 $ hg log -R a -r 8 --template '{files % "{file}\n"}'
3552 3571 fourth
3553 3572 second
3554 3573 third
3555 3574
3556 3575 Test string escaping in nested expression:
3557 3576
3558 3577 $ hg log -R a -r 8 --template '{ifeq(r"\x6e", if("1", "\x5c\x786e"), join(files, "\x5c\x786e"))}\n'
3559 3578 fourth\x6esecond\x6ethird
3560 3579 $ hg log -R a -r 8 --template '{ifeq(if("1", r"\x6e"), "\x5c\x786e", join(files, "\x5c\x786e"))}\n'
3561 3580 fourth\x6esecond\x6ethird
3562 3581
3563 3582 $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", "\x5c\x786e"))}\n'
3564 3583 fourth\x6esecond\x6ethird
3565 3584 $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", r"\x5c\x786e"))}\n'
3566 3585 fourth\x5c\x786esecond\x5c\x786ethird
3567 3586
3568 3587 $ hg log -R a -r 3:4 --template '{rev}:{sub(if("1", "\x6e"), ifeq(branch, "foo", r"\x5c\x786e", "\x5c\x786e"), desc)}\n'
3569 3588 3:\x6eo user, \x6eo domai\x6e
3570 3589 4:\x5c\x786eew bra\x5c\x786ech
3571 3590
3572 3591 Test quotes in nested expression are evaluated just like a $(command)
3573 3592 substitution in POSIX shells:
3574 3593
3575 3594 $ hg log -R a -r 8 -T '{"{"{rev}:{node|short}"}"}\n'
3576 3595 8:95c24699272e
3577 3596 $ hg log -R a -r 8 -T '{"{"\{{rev}} \"{node|short}\""}"}\n'
3578 3597 {8} "95c24699272e"
3579 3598
3580 3599 Test recursive evaluation:
3581 3600
3582 3601 $ hg init r
3583 3602 $ cd r
3584 3603 $ echo a > a
3585 3604 $ hg ci -Am '{rev}'
3586 3605 adding a
3587 3606 $ hg log -r 0 --template '{if(rev, desc)}\n'
3588 3607 {rev}
3589 3608 $ hg log -r 0 --template '{if(rev, "{author} {rev}")}\n'
3590 3609 test 0
3591 3610
3592 3611 $ hg branch -q 'text.{rev}'
3593 3612 $ echo aa >> aa
3594 3613 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
3595 3614
3596 3615 $ hg log -l1 --template '{fill(desc, "20", author, branch)}'
3597 3616 {node|short}desc to
3598 3617 text.{rev}be wrapped
3599 3618 text.{rev}desc to be
3600 3619 text.{rev}wrapped (no-eol)
3601 3620 $ hg log -l1 --template '{fill(desc, "20", "{node|short}:", "text.{rev}:")}'
3602 3621 bcc7ff960b8e:desc to
3603 3622 text.1:be wrapped
3604 3623 text.1:desc to be
3605 3624 text.1:wrapped (no-eol)
3606 3625 $ hg log -l1 -T '{fill(desc, date, "", "")}\n'
3607 3626 hg: parse error: fill expects an integer width
3608 3627 [255]
3609 3628
3610 3629 $ COLUMNS=25 hg log -l1 --template '{fill(desc, termwidth, "{node|short}:", "termwidth.{rev}:")}'
3611 3630 bcc7ff960b8e:desc to be
3612 3631 termwidth.1:wrapped desc
3613 3632 termwidth.1:to be wrapped (no-eol)
3614 3633
3615 3634 $ hg log -l 1 --template '{sub(r"[0-9]", "-", author)}'
3616 3635 {node|short} (no-eol)
3617 3636 $ hg log -l 1 --template '{sub(r"[0-9]", "-", "{node|short}")}'
3618 3637 bcc-ff---b-e (no-eol)
3619 3638
3620 3639 $ cat >> .hg/hgrc <<EOF
3621 3640 > [extensions]
3622 3641 > color=
3623 3642 > [color]
3624 3643 > mode=ansi
3625 3644 > text.{rev} = red
3626 3645 > text.1 = green
3627 3646 > EOF
3628 3647 $ hg log --color=always -l 1 --template '{label(branch, "text\n")}'
3629 3648 \x1b[0;31mtext\x1b[0m (esc)
3630 3649 $ hg log --color=always -l 1 --template '{label("text.{rev}", "text\n")}'
3631 3650 \x1b[0;32mtext\x1b[0m (esc)
3632 3651
3633 3652 color effect can be specified without quoting:
3634 3653
3635 3654 $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
3636 3655 \x1b[0;31mtext\x1b[0m (esc)
3637 3656
3638 3657 color effects can be nested (issue5413)
3639 3658
3640 3659 $ hg debugtemplate --color=always \
3641 3660 > '{label(red, "red{label(magenta, "ma{label(cyan, "cyan")}{label(yellow, "yellow")}genta")}")}\n'
3642 3661 \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)
3643 3662
3644 3663 pad() should interact well with color codes (issue5416)
3645 3664
3646 3665 $ hg debugtemplate --color=always \
3647 3666 > '{pad(label(red, "red"), 5, label(cyan, "-"))}\n'
3648 3667 \x1b[0;31mred\x1b[0m\x1b[0;36m-\x1b[0m\x1b[0;36m-\x1b[0m (esc)
3649 3668
3650 3669 label should be no-op if color is disabled:
3651 3670
3652 3671 $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
3653 3672 text
3654 3673 $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
3655 3674 text
3656 3675
3657 3676 Test branches inside if statement:
3658 3677
3659 3678 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
3660 3679 no
3661 3680
3662 3681 Test dict constructor:
3663 3682
3664 3683 $ hg log -r 0 -T '{dict(y=node|short, x=rev)}\n'
3665 3684 y=f7769ec2ab97 x=0
3666 3685 $ hg log -r 0 -T '{dict(x=rev, y=node|short) % "{key}={value}\n"}'
3667 3686 x=0
3668 3687 y=f7769ec2ab97
3669 3688 $ hg log -r 0 -T '{dict(x=rev, y=node|short)|json}\n'
3670 3689 {"x": 0, "y": "f7769ec2ab97"}
3671 3690 $ hg log -r 0 -T '{dict()|json}\n'
3672 3691 {}
3673 3692
3674 3693 $ hg log -r 0 -T '{dict(rev, node=node|short)}\n'
3675 3694 rev=0 node=f7769ec2ab97
3676 3695 $ hg log -r 0 -T '{dict(rev, node|short)}\n'
3677 3696 rev=0 node=f7769ec2ab97
3678 3697
3679 3698 $ hg log -r 0 -T '{dict(rev, rev=rev)}\n'
3680 3699 hg: parse error: duplicated dict key 'rev' inferred
3681 3700 [255]
3682 3701 $ hg log -r 0 -T '{dict(node, node|short)}\n'
3683 3702 hg: parse error: duplicated dict key 'node' inferred
3684 3703 [255]
3685 3704 $ hg log -r 0 -T '{dict(1 + 2)}'
3686 3705 hg: parse error: dict key cannot be inferred
3687 3706 [255]
3688 3707
3689 3708 $ hg log -r 0 -T '{dict(x=rev, x=node)}'
3690 3709 hg: parse error: dict got multiple values for keyword argument 'x'
3691 3710 [255]
3692 3711
3693 3712 Test get function:
3694 3713
3695 3714 $ hg log -r 0 --template '{get(extras, "branch")}\n'
3696 3715 default
3697 3716 $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
3698 3717 default
3699 3718 $ hg log -r 0 --template '{get(files, "should_fail")}\n'
3700 3719 hg: parse error: get() expects a dict as first argument
3701 3720 [255]
3702 3721
3703 3722 Test json filter applied to hybrid object:
3704 3723
3705 3724 $ hg log -r0 -T '{files|json}\n'
3706 3725 ["a"]
3707 3726 $ hg log -r0 -T '{extras|json}\n'
3708 3727 {"branch": "default"}
3709 3728
3710 3729 Test localdate(date, tz) function:
3711 3730
3712 3731 $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
3713 3732 1970-01-01 09:00 +0900
3714 3733 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
3715 3734 1970-01-01 00:00 +0000
3716 3735 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
3717 3736 hg: parse error: localdate expects a timezone
3718 3737 [255]
3719 3738 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
3720 3739 1970-01-01 02:00 +0200
3721 3740 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
3722 3741 1970-01-01 00:00 +0000
3723 3742 $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
3724 3743 1970-01-01 00:00 +0000
3725 3744 $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
3726 3745 hg: parse error: localdate expects a timezone
3727 3746 [255]
3728 3747 $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
3729 3748 hg: parse error: localdate expects a timezone
3730 3749 [255]
3731 3750
3732 3751 Test shortest(node) function:
3733 3752
3734 3753 $ echo b > b
3735 3754 $ hg ci -qAm b
3736 3755 $ hg log --template '{shortest(node)}\n'
3737 3756 e777
3738 3757 bcc7
3739 3758 f776
3740 3759 $ hg log --template '{shortest(node, 10)}\n'
3741 3760 e777603221
3742 3761 bcc7ff960b
3743 3762 f7769ec2ab
3744 3763 $ hg log --template '{node|shortest}\n' -l1
3745 3764 e777
3746 3765
3747 3766 $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
3748 3767 f7769ec2ab
3749 3768 $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
3750 3769 hg: parse error: shortest() expects an integer minlength
3751 3770 [255]
3752 3771
3753 3772 $ hg log -r 'wdir()' -T '{node|shortest}\n'
3754 3773 ffff
3755 3774
3756 3775 $ cd ..
3757 3776
3758 3777 Test shortest(node) with the repo having short hash collision:
3759 3778
3760 3779 $ hg init hashcollision
3761 3780 $ cd hashcollision
3762 3781 $ cat <<EOF >> .hg/hgrc
3763 3782 > [experimental]
3764 3783 > stabilization = createmarkers
3765 3784 > EOF
3766 3785 $ echo 0 > a
3767 3786 $ hg ci -qAm 0
3768 3787 $ for i in 17 129 248 242 480 580 617 1057 2857 4025; do
3769 3788 > hg up -q 0
3770 3789 > echo $i > a
3771 3790 > hg ci -qm $i
3772 3791 > done
3773 3792 $ hg up -q null
3774 3793 $ hg log -r0: -T '{rev}:{node}\n'
3775 3794 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
3776 3795 1:11424df6dc1dd4ea255eae2b58eaca7831973bbc
3777 3796 2:11407b3f1b9c3e76a79c1ec5373924df096f0499
3778 3797 3:11dd92fe0f39dfdaacdaa5f3997edc533875cfc4
3779 3798 4:10776689e627b465361ad5c296a20a487e153ca4
3780 3799 5:a00be79088084cb3aff086ab799f8790e01a976b
3781 3800 6:a0b0acd79b4498d0052993d35a6a748dd51d13e6
3782 3801 7:a0457b3450b8e1b778f1163b31a435802987fe5d
3783 3802 8:c56256a09cd28e5764f32e8e2810d0f01e2e357a
3784 3803 9:c5623987d205cd6d9d8389bfc40fff9dbb670b48
3785 3804 10:c562ddd9c94164376c20b86b0b4991636a3bf84f
3786 3805 $ hg debugobsolete a00be79088084cb3aff086ab799f8790e01a976b
3787 3806 obsoleted 1 changesets
3788 3807 $ hg debugobsolete c5623987d205cd6d9d8389bfc40fff9dbb670b48
3789 3808 obsoleted 1 changesets
3790 3809 $ hg debugobsolete c562ddd9c94164376c20b86b0b4991636a3bf84f
3791 3810 obsoleted 1 changesets
3792 3811
3793 3812 nodes starting with '11' (we don't have the revision number '11' though)
3794 3813
3795 3814 $ hg log -r 1:3 -T '{rev}:{shortest(node, 0)}\n'
3796 3815 1:1142
3797 3816 2:1140
3798 3817 3:11d
3799 3818
3800 3819 '5:a00' is hidden, but still we have two nodes starting with 'a0'
3801 3820
3802 3821 $ hg log -r 6:7 -T '{rev}:{shortest(node, 0)}\n'
3803 3822 6:a0b
3804 3823 7:a04
3805 3824
3806 3825 node '10' conflicts with the revision number '10' even if it is hidden
3807 3826 (we could exclude hidden revision numbers, but currently we don't)
3808 3827
3809 3828 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n'
3810 3829 4:107
3811 3830 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
3812 3831 4:107
3813 3832
3814 3833 node 'c562' should be unique if the other 'c562' nodes are hidden
3815 3834 (but we don't try the slow path to filter out hidden nodes for now)
3816 3835
3817 3836 $ hg log -r 8 -T '{rev}:{node|shortest}\n'
3818 3837 8:c5625
3819 3838 $ hg log -r 8:10 -T '{rev}:{node|shortest}\n' --hidden
3820 3839 8:c5625
3821 3840 9:c5623
3822 3841 10:c562d
3823 3842
3824 3843 $ cd ..
3825 3844
3826 3845 Test pad function
3827 3846
3828 3847 $ cd r
3829 3848
3830 3849 $ hg log --template '{pad(rev, 20)} {author|user}\n'
3831 3850 2 test
3832 3851 1 {node|short}
3833 3852 0 test
3834 3853
3835 3854 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
3836 3855 2 test
3837 3856 1 {node|short}
3838 3857 0 test
3839 3858
3840 3859 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
3841 3860 2------------------- test
3842 3861 1------------------- {node|short}
3843 3862 0------------------- test
3844 3863
3845 3864 Test template string in pad function
3846 3865
3847 3866 $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
3848 3867 {0} test
3849 3868
3850 3869 $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
3851 3870 \{rev} test
3852 3871
3853 3872 Test width argument passed to pad function
3854 3873
3855 3874 $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
3856 3875 0 test
3857 3876 $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
3858 3877 hg: parse error: pad() expects an integer width
3859 3878 [255]
3860 3879
3861 3880 Test invalid fillchar passed to pad function
3862 3881
3863 3882 $ hg log -r 0 -T '{pad(rev, 10, "")}\n'
3864 3883 hg: parse error: pad() expects a single fill character
3865 3884 [255]
3866 3885 $ hg log -r 0 -T '{pad(rev, 10, "--")}\n'
3867 3886 hg: parse error: pad() expects a single fill character
3868 3887 [255]
3869 3888
3870 3889 Test boolean argument passed to pad function
3871 3890
3872 3891 no crash
3873 3892
3874 3893 $ hg log -r 0 -T '{pad(rev, 10, "-", "f{"oo"}")}\n'
3875 3894 ---------0
3876 3895
3877 3896 string/literal
3878 3897
3879 3898 $ hg log -r 0 -T '{pad(rev, 10, "-", "false")}\n'
3880 3899 ---------0
3881 3900 $ hg log -r 0 -T '{pad(rev, 10, "-", false)}\n'
3882 3901 0---------
3883 3902 $ hg log -r 0 -T '{pad(rev, 10, "-", "")}\n'
3884 3903 0---------
3885 3904
3886 3905 unknown keyword is evaluated to ''
3887 3906
3888 3907 $ hg log -r 0 -T '{pad(rev, 10, "-", unknownkeyword)}\n'
3889 3908 0---------
3890 3909
3891 3910 Test separate function
3892 3911
3893 3912 $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
3894 3913 a-b-c
3895 3914 $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
3896 3915 0:f7769ec2ab97 test default
3897 3916 $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
3898 3917 a \x1b[0;31mb\x1b[0m c d (esc)
3899 3918
3900 3919 Test boolean expression/literal passed to if function
3901 3920
3902 3921 $ hg log -r 0 -T '{if(rev, "rev 0 is True")}\n'
3903 3922 rev 0 is True
3904 3923 $ hg log -r 0 -T '{if(0, "literal 0 is True as well")}\n'
3905 3924 literal 0 is True as well
3906 3925 $ hg log -r 0 -T '{if("", "", "empty string is False")}\n'
3907 3926 empty string is False
3908 3927 $ hg log -r 0 -T '{if(revset(r"0 - 0"), "", "empty list is False")}\n'
3909 3928 empty list is False
3910 3929 $ hg log -r 0 -T '{if(true, "true is True")}\n'
3911 3930 true is True
3912 3931 $ hg log -r 0 -T '{if(false, "", "false is False")}\n'
3913 3932 false is False
3914 3933 $ hg log -r 0 -T '{if("false", "non-empty string is True")}\n'
3915 3934 non-empty string is True
3916 3935
3917 3936 Test ifcontains function
3918 3937
3919 3938 $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
3920 3939 2 is in the string
3921 3940 1 is not
3922 3941 0 is in the string
3923 3942
3924 3943 $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
3925 3944 2 is in the string
3926 3945 1 is not
3927 3946 0 is in the string
3928 3947
3929 3948 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
3930 3949 2 did not add a
3931 3950 1 did not add a
3932 3951 0 added a
3933 3952
3934 3953 $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
3935 3954 2 is parent of 1
3936 3955 1
3937 3956 0
3938 3957
3939 3958 Test revset function
3940 3959
3941 3960 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
3942 3961 2 current rev
3943 3962 1 not current rev
3944 3963 0 not current rev
3945 3964
3946 3965 $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
3947 3966 2 match rev
3948 3967 1 match rev
3949 3968 0 not match rev
3950 3969
3951 3970 $ hg log -T '{ifcontains(desc, revset(":"), "", "type not match")}\n' -l1
3952 3971 type not match
3953 3972
3954 3973 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
3955 3974 2 Parents: 1
3956 3975 1 Parents: 0
3957 3976 0 Parents:
3958 3977
3959 3978 $ cat >> .hg/hgrc <<EOF
3960 3979 > [revsetalias]
3961 3980 > myparents(\$1) = parents(\$1)
3962 3981 > EOF
3963 3982 $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
3964 3983 2 Parents: 1
3965 3984 1 Parents: 0
3966 3985 0 Parents:
3967 3986
3968 3987 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
3969 3988 Rev: 2
3970 3989 Ancestor: 0
3971 3990 Ancestor: 1
3972 3991 Ancestor: 2
3973 3992
3974 3993 Rev: 1
3975 3994 Ancestor: 0
3976 3995 Ancestor: 1
3977 3996
3978 3997 Rev: 0
3979 3998 Ancestor: 0
3980 3999
3981 4000 $ hg log --template '{revset("TIP"|lower)}\n' -l1
3982 4001 2
3983 4002
3984 4003 $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
3985 4004 2
3986 4005
3987 4006 a list template is evaluated for each item of revset/parents
3988 4007
3989 4008 $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
3990 4009 2 p: 1:bcc7ff960b8e
3991 4010 1 p: 0:f7769ec2ab97
3992 4011 0 p:
3993 4012
3994 4013 $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
3995 4014 2 p: 1:bcc7ff960b8e -1:000000000000
3996 4015 1 p: 0:f7769ec2ab97 -1:000000000000
3997 4016 0 p: -1:000000000000 -1:000000000000
3998 4017
3999 4018 therefore, 'revcache' should be recreated for each rev
4000 4019
4001 4020 $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
4002 4021 2 aa b
4003 4022 p
4004 4023 1
4005 4024 p a
4006 4025 0 a
4007 4026 p
4008 4027
4009 4028 $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
4010 4029 2 aa b
4011 4030 p
4012 4031 1
4013 4032 p a
4014 4033 0 a
4015 4034 p
4016 4035
4017 4036 a revset item must be evaluated as an integer revision, not an offset from tip
4018 4037
4019 4038 $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
4020 4039 -1:000000000000
4021 4040 $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
4022 4041 -1:000000000000
4023 4042
4024 4043 join() should pick '{rev}' from revset items:
4025 4044
4026 4045 $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
4027 4046 4, 5
4028 4047
4029 4048 on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
4030 4049 default. join() should agree with the default formatting:
4031 4050
4032 4051 $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
4033 4052 5:13207e5a10d9, 4:bbe44766e73d
4034 4053
4035 4054 $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
4036 4055 5:13207e5a10d9fd28ec424934298e176197f2c67f,
4037 4056 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
4038 4057
4039 4058 Test files function
4040 4059
4041 4060 $ hg log -T "{rev}\n{join(files('*'), '\n')}\n"
4042 4061 2
4043 4062 a
4044 4063 aa
4045 4064 b
4046 4065 1
4047 4066 a
4048 4067 0
4049 4068 a
4050 4069
4051 4070 $ hg log -T "{rev}\n{join(files('aa'), '\n')}\n"
4052 4071 2
4053 4072 aa
4054 4073 1
4055 4074
4056 4075 0
4057 4076
4058 4077
4059 4078 Test relpath function
4060 4079
4061 4080 $ hg log -r0 -T '{files % "{file|relpath}\n"}'
4062 4081 a
4063 4082 $ cd ..
4064 4083 $ hg log -R r -r0 -T '{files % "{file|relpath}\n"}'
4065 4084 r/a
4066 4085 $ cd r
4067 4086
4068 4087 Test active bookmark templating
4069 4088
4070 4089 $ hg book foo
4071 4090 $ hg book bar
4072 4091 $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, active, \"*\")} '}\n"
4073 4092 2 bar* foo
4074 4093 1
4075 4094 0
4076 4095 $ hg log --template "{rev} {activebookmark}\n"
4077 4096 2 bar
4078 4097 1
4079 4098 0
4080 4099 $ hg bookmarks --inactive bar
4081 4100 $ hg log --template "{rev} {activebookmark}\n"
4082 4101 2
4083 4102 1
4084 4103 0
4085 4104 $ hg book -r1 baz
4086 4105 $ hg log --template "{rev} {join(bookmarks, ' ')}\n"
4087 4106 2 bar foo
4088 4107 1 baz
4089 4108 0
4090 4109 $ hg log --template "{rev} {ifcontains('foo', bookmarks, 't', 'f')}\n"
4091 4110 2 t
4092 4111 1 f
4093 4112 0 f
4094 4113
4095 4114 Test namespaces dict
4096 4115
4097 4116 $ hg --config extensions.revnamesext=$TESTDIR/revnamesext.py log -T '{rev}\n{namespaces % " {namespace} color={colorname} builtin={builtin}\n {join(names, ",")}\n"}\n'
4098 4117 2
4099 4118 bookmarks color=bookmark builtin=True
4100 4119 bar,foo
4101 4120 tags color=tag builtin=True
4102 4121 tip
4103 4122 branches color=branch builtin=True
4104 4123 text.{rev}
4105 4124 revnames color=revname builtin=False
4106 4125 r2
4107 4126
4108 4127 1
4109 4128 bookmarks color=bookmark builtin=True
4110 4129 baz
4111 4130 tags color=tag builtin=True
4112 4131
4113 4132 branches color=branch builtin=True
4114 4133 text.{rev}
4115 4134 revnames color=revname builtin=False
4116 4135 r1
4117 4136
4118 4137 0
4119 4138 bookmarks color=bookmark builtin=True
4120 4139
4121 4140 tags color=tag builtin=True
4122 4141
4123 4142 branches color=branch builtin=True
4124 4143 default
4125 4144 revnames color=revname builtin=False
4126 4145 r0
4127 4146
4128 4147 $ hg log -r2 -T '{namespaces % "{namespace}: {names}\n"}'
4129 4148 bookmarks: bar foo
4130 4149 tags: tip
4131 4150 branches: text.{rev}
4132 4151 $ hg log -r2 -T '{namespaces % "{namespace}:\n{names % " {name}\n"}"}'
4133 4152 bookmarks:
4134 4153 bar
4135 4154 foo
4136 4155 tags:
4137 4156 tip
4138 4157 branches:
4139 4158 text.{rev}
4140 4159 $ hg log -r2 -T '{get(namespaces, "bookmarks") % "{name}\n"}'
4141 4160 bar
4142 4161 foo
4143 4162 $ hg log -r2 -T '{namespaces.bookmarks % "{bookmark}\n"}'
4144 4163 bar
4145 4164 foo
4146 4165
4147 4166 Test stringify on sub expressions
4148 4167
4149 4168 $ cd ..
4150 4169 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
4151 4170 fourth, second, third
4152 4171 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
4153 4172 abc
4154 4173
4155 4174 Test splitlines
4156 4175
4157 4176 $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
4158 4177 @ foo Modify, add, remove, rename
4159 4178 |
4160 4179 o foo future
4161 4180 |
4162 4181 o foo third
4163 4182 |
4164 4183 o foo second
4165 4184
4166 4185 o foo merge
4167 4186 |\
4168 4187 | o foo new head
4169 4188 | |
4170 4189 o | foo new branch
4171 4190 |/
4172 4191 o foo no user, no domain
4173 4192 |
4174 4193 o foo no person
4175 4194 |
4176 4195 o foo other 1
4177 4196 | foo other 2
4178 4197 | foo
4179 4198 | foo other 3
4180 4199 o foo line 1
4181 4200 foo line 2
4182 4201
4183 4202 $ hg log -R a -r0 -T '{desc|splitlines}\n'
4184 4203 line 1 line 2
4185 4204 $ hg log -R a -r0 -T '{join(desc|splitlines, "|")}\n'
4186 4205 line 1|line 2
4187 4206
4188 4207 Test startswith
4189 4208 $ hg log -Gv -R a --template "{startswith(desc)}"
4190 4209 hg: parse error: startswith expects two arguments
4191 4210 [255]
4192 4211
4193 4212 $ hg log -Gv -R a --template "{startswith('line', desc)}"
4194 4213 @
4195 4214 |
4196 4215 o
4197 4216 |
4198 4217 o
4199 4218 |
4200 4219 o
4201 4220
4202 4221 o
4203 4222 |\
4204 4223 | o
4205 4224 | |
4206 4225 o |
4207 4226 |/
4208 4227 o
4209 4228 |
4210 4229 o
4211 4230 |
4212 4231 o
4213 4232 |
4214 4233 o line 1
4215 4234 line 2
4216 4235
4217 4236 Test bad template with better error message
4218 4237
4219 4238 $ hg log -Gv -R a --template '{desc|user()}'
4220 4239 hg: parse error: expected a symbol, got 'func'
4221 4240 [255]
4222 4241
4223 4242 Test word function (including index out of bounds graceful failure)
4224 4243
4225 4244 $ hg log -Gv -R a --template "{word('1', desc)}"
4226 4245 @ add,
4227 4246 |
4228 4247 o
4229 4248 |
4230 4249 o
4231 4250 |
4232 4251 o
4233 4252
4234 4253 o
4235 4254 |\
4236 4255 | o head
4237 4256 | |
4238 4257 o | branch
4239 4258 |/
4240 4259 o user,
4241 4260 |
4242 4261 o person
4243 4262 |
4244 4263 o 1
4245 4264 |
4246 4265 o 1
4247 4266
4248 4267
4249 4268 Test word third parameter used as splitter
4250 4269
4251 4270 $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
4252 4271 @ M
4253 4272 |
4254 4273 o future
4255 4274 |
4256 4275 o third
4257 4276 |
4258 4277 o sec
4259 4278
4260 4279 o merge
4261 4280 |\
4262 4281 | o new head
4263 4282 | |
4264 4283 o | new branch
4265 4284 |/
4266 4285 o n
4267 4286 |
4268 4287 o n
4269 4288 |
4270 4289 o
4271 4290 |
4272 4291 o line 1
4273 4292 line 2
4274 4293
4275 4294 Test word error messages for not enough and too many arguments
4276 4295
4277 4296 $ hg log -Gv -R a --template "{word('0')}"
4278 4297 hg: parse error: word expects two or three arguments, got 1
4279 4298 [255]
4280 4299
4281 4300 $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
4282 4301 hg: parse error: word expects two or three arguments, got 7
4283 4302 [255]
4284 4303
4285 4304 Test word for integer literal
4286 4305
4287 4306 $ hg log -R a --template "{word(2, desc)}\n" -r0
4288 4307 line
4289 4308
4290 4309 Test word for invalid numbers
4291 4310
4292 4311 $ hg log -Gv -R a --template "{word('a', desc)}"
4293 4312 hg: parse error: word expects an integer index
4294 4313 [255]
4295 4314
4296 4315 Test word for out of range
4297 4316
4298 4317 $ hg log -R a --template "{word(10000, desc)}"
4299 4318 $ hg log -R a --template "{word(-10000, desc)}"
4300 4319
4301 4320 Test indent and not adding to empty lines
4302 4321
4303 4322 $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
4304 4323 -----
4305 4324 > line 1
4306 4325 >> line 2
4307 4326 -----
4308 4327 > other 1
4309 4328 >> other 2
4310 4329
4311 4330 >> other 3
4312 4331
4313 4332 Test with non-strings like dates
4314 4333
4315 4334 $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a
4316 4335 1200000.00
4317 4336 1300000.00
4318 4337
4319 4338 Test broken string escapes:
4320 4339
4321 4340 $ hg log -T "bogus\\" -R a
4322 4341 hg: parse error: trailing \ in string
4323 4342 [255]
4324 4343 $ hg log -T "\\xy" -R a
4325 4344 hg: parse error: invalid \x escape
4326 4345 [255]
4327 4346
4328 4347 json filter should escape HTML tags so that the output can be embedded in hgweb:
4329 4348
4330 4349 $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
4331 4350 "\u003cfoo@example.org\u003e"
4332 4351
4333 4352 Templater supports aliases of symbol and func() styles:
4334 4353
4335 4354 $ hg clone -q a aliases
4336 4355 $ cd aliases
4337 4356 $ cat <<EOF >> .hg/hgrc
4338 4357 > [templatealias]
4339 4358 > r = rev
4340 4359 > rn = "{r}:{node|short}"
4341 4360 > status(c, files) = files % "{c} {file}\n"
4342 4361 > utcdate(d) = localdate(d, "UTC")
4343 4362 > EOF
4344 4363
4345 4364 $ hg debugtemplate -vr0 '{rn} {utcdate(date)|isodate}\n'
4346 4365 (template
4347 4366 (symbol 'rn')
4348 4367 (string ' ')
4349 4368 (|
4350 4369 (func
4351 4370 (symbol 'utcdate')
4352 4371 (symbol 'date'))
4353 4372 (symbol 'isodate'))
4354 4373 (string '\n'))
4355 4374 * expanded:
4356 4375 (template
4357 4376 (template
4358 4377 (symbol 'rev')
4359 4378 (string ':')
4360 4379 (|
4361 4380 (symbol 'node')
4362 4381 (symbol 'short')))
4363 4382 (string ' ')
4364 4383 (|
4365 4384 (func
4366 4385 (symbol 'localdate')
4367 4386 (list
4368 4387 (symbol 'date')
4369 4388 (string 'UTC')))
4370 4389 (symbol 'isodate'))
4371 4390 (string '\n'))
4372 4391 0:1e4e1b8f71e0 1970-01-12 13:46 +0000
4373 4392
4374 4393 $ hg debugtemplate -vr0 '{status("A", file_adds)}'
4375 4394 (template
4376 4395 (func
4377 4396 (symbol 'status')
4378 4397 (list
4379 4398 (string 'A')
4380 4399 (symbol 'file_adds'))))
4381 4400 * expanded:
4382 4401 (template
4383 4402 (%
4384 4403 (symbol 'file_adds')
4385 4404 (template
4386 4405 (string 'A')
4387 4406 (string ' ')
4388 4407 (symbol 'file')
4389 4408 (string '\n'))))
4390 4409 A a
4391 4410
4392 4411 A unary function alias can be called as a filter:
4393 4412
4394 4413 $ hg debugtemplate -vr0 '{date|utcdate|isodate}\n'
4395 4414 (template
4396 4415 (|
4397 4416 (|
4398 4417 (symbol 'date')
4399 4418 (symbol 'utcdate'))
4400 4419 (symbol 'isodate'))
4401 4420 (string '\n'))
4402 4421 * expanded:
4403 4422 (template
4404 4423 (|
4405 4424 (func
4406 4425 (symbol 'localdate')
4407 4426 (list
4408 4427 (symbol 'date')
4409 4428 (string 'UTC')))
4410 4429 (symbol 'isodate'))
4411 4430 (string '\n'))
4412 4431 1970-01-12 13:46 +0000
4413 4432
4414 4433 Aliases should be applied only to command arguments and templates in hgrc.
4415 4434 Otherwise, our stock styles and web templates could be corrupted:
4416 4435
4417 4436 $ hg log -r0 -T '{rn} {utcdate(date)|isodate}\n'
4418 4437 0:1e4e1b8f71e0 1970-01-12 13:46 +0000
4419 4438
4420 4439 $ hg log -r0 --config ui.logtemplate='"{rn} {utcdate(date)|isodate}\n"'
4421 4440 0:1e4e1b8f71e0 1970-01-12 13:46 +0000
4422 4441
4423 4442 $ cat <<EOF > tmpl
4424 4443 > changeset = 'nothing expanded:{rn}\n'
4425 4444 > EOF
4426 4445 $ hg log -r0 --style ./tmpl
4427 4446 nothing expanded:
4428 4447
4429 4448 Aliases in formatter:
4430 4449
4431 4450 $ hg branches -T '{pad(branch, 7)} {rn}\n'
4432 4451 default 6:d41e714fe50d
4433 4452 foo 4:bbe44766e73d
4434 4453
4435 4454 Aliases should honor HGPLAIN:
4436 4455
4437 4456 $ HGPLAIN= hg log -r0 -T 'nothing expanded:{rn}\n'
4438 4457 nothing expanded:
4439 4458 $ HGPLAINEXCEPT=templatealias hg log -r0 -T '{rn}\n'
4440 4459 0:1e4e1b8f71e0
4441 4460
4442 4461 Unparsable alias:
4443 4462
4444 4463 $ hg debugtemplate --config templatealias.bad='x(' -v '{bad}'
4445 4464 (template
4446 4465 (symbol 'bad'))
4447 4466 abort: bad definition of template alias "bad": at 2: not a prefix: end
4448 4467 [255]
4449 4468 $ hg log --config templatealias.bad='x(' -T '{bad}'
4450 4469 abort: bad definition of template alias "bad": at 2: not a prefix: end
4451 4470 [255]
4452 4471
4453 4472 $ cd ..
4454 4473
4455 4474 Set up repository for non-ascii encoding tests:
4456 4475
4457 4476 $ hg init nonascii
4458 4477 $ cd nonascii
4459 4478 $ $PYTHON <<EOF
4460 4479 > open('latin1', 'w').write('\xe9')
4461 4480 > open('utf-8', 'w').write('\xc3\xa9')
4462 4481 > EOF
4463 4482 $ HGENCODING=utf-8 hg branch -q `cat utf-8`
4464 4483 $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
4465 4484
4466 4485 json filter should try round-trip conversion to utf-8:
4467 4486
4468 4487 $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
4469 4488 "\u00e9"
4470 4489 $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
4471 4490 "non-ascii branch: \u00e9"
4472 4491
4473 4492 json filter takes input as utf-8b:
4474 4493
4475 4494 $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
4476 4495 "\u00e9"
4477 4496 $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
4478 4497 "\udce9"
4479 4498
4480 4499 utf8 filter:
4481 4500
4482 4501 $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
4483 4502 round-trip: c3a9
4484 4503 $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
4485 4504 decoded: c3a9
4486 4505 $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
4487 4506 abort: decoding near * (glob)
4488 4507 [255]
4489 4508 $ hg log -T "invalid type: {rev|utf8}\n" -r0
4490 4509 abort: template filter 'utf8' is not compatible with keyword 'rev'
4491 4510 [255]
4492 4511
4493 4512 pad width:
4494 4513
4495 4514 $ HGENCODING=utf-8 hg debugtemplate "{pad('`cat utf-8`', 2, '-')}\n"
4496 4515 \xc3\xa9- (esc)
4497 4516
4498 4517 $ cd ..
4499 4518
4500 4519 Test that template function in extension is registered as expected
4501 4520
4502 4521 $ cd a
4503 4522
4504 4523 $ cat <<EOF > $TESTTMP/customfunc.py
4505 4524 > from mercurial import registrar
4506 4525 >
4507 4526 > templatefunc = registrar.templatefunc()
4508 4527 >
4509 4528 > @templatefunc('custom()')
4510 4529 > def custom(context, mapping, args):
4511 4530 > return 'custom'
4512 4531 > EOF
4513 4532 $ cat <<EOF > .hg/hgrc
4514 4533 > [extensions]
4515 4534 > customfunc = $TESTTMP/customfunc.py
4516 4535 > EOF
4517 4536
4518 4537 $ hg log -r . -T "{custom()}\n" --config customfunc.enabled=true
4519 4538 custom
4520 4539
4521 4540 $ cd ..
4522 4541
4523 4542 Test 'graphwidth' in 'hg log' on various topologies. The key here is that the
4524 4543 printed graphwidths 3, 5, 7, etc. should all line up in their respective
4525 4544 columns. We don't care about other aspects of the graph rendering here.
4526 4545
4527 4546 $ hg init graphwidth
4528 4547 $ cd graphwidth
4529 4548
4530 4549 $ wrappabletext="a a a a a a a a a a a a"
4531 4550
4532 4551 $ printf "first\n" > file
4533 4552 $ hg add file
4534 4553 $ hg commit -m "$wrappabletext"
4535 4554
4536 4555 $ printf "first\nsecond\n" > file
4537 4556 $ hg commit -m "$wrappabletext"
4538 4557
4539 4558 $ hg checkout 0
4540 4559 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
4541 4560 $ printf "third\nfirst\n" > file
4542 4561 $ hg commit -m "$wrappabletext"
4543 4562 created new head
4544 4563
4545 4564 $ hg merge
4546 4565 merging file
4547 4566 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
4548 4567 (branch merge, don't forget to commit)
4549 4568
4550 4569 $ hg log --graph -T "{graphwidth}"
4551 4570 @ 3
4552 4571 |
4553 4572 | @ 5
4554 4573 |/
4555 4574 o 3
4556 4575
4557 4576 $ hg commit -m "$wrappabletext"
4558 4577
4559 4578 $ hg log --graph -T "{graphwidth}"
4560 4579 @ 5
4561 4580 |\
4562 4581 | o 5
4563 4582 | |
4564 4583 o | 5
4565 4584 |/
4566 4585 o 3
4567 4586
4568 4587
4569 4588 $ hg checkout 0
4570 4589 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
4571 4590 $ printf "third\nfirst\nsecond\n" > file
4572 4591 $ hg commit -m "$wrappabletext"
4573 4592 created new head
4574 4593
4575 4594 $ hg log --graph -T "{graphwidth}"
4576 4595 @ 3
4577 4596 |
4578 4597 | o 7
4579 4598 | |\
4580 4599 +---o 7
4581 4600 | |
4582 4601 | o 5
4583 4602 |/
4584 4603 o 3
4585 4604
4586 4605
4587 4606 $ hg log --graph -T "{graphwidth}" -r 3
4588 4607 o 5
4589 4608 |\
4590 4609 ~ ~
4591 4610
4592 4611 $ hg log --graph -T "{graphwidth}" -r 1
4593 4612 o 3
4594 4613 |
4595 4614 ~
4596 4615
4597 4616 $ hg merge
4598 4617 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
4599 4618 (branch merge, don't forget to commit)
4600 4619 $ hg commit -m "$wrappabletext"
4601 4620
4602 4621 $ printf "seventh\n" >> file
4603 4622 $ hg commit -m "$wrappabletext"
4604 4623
4605 4624 $ hg log --graph -T "{graphwidth}"
4606 4625 @ 3
4607 4626 |
4608 4627 o 5
4609 4628 |\
4610 4629 | o 5
4611 4630 | |
4612 4631 o | 7
4613 4632 |\ \
4614 4633 | o | 7
4615 4634 | |/
4616 4635 o / 5
4617 4636 |/
4618 4637 o 3
4619 4638
4620 4639
4621 4640 The point of graphwidth is to allow wrapping that accounts for the space taken
4622 4641 by the graph.
4623 4642
4624 4643 $ COLUMNS=10 hg log --graph -T "{fill(desc, termwidth - graphwidth)}"
4625 4644 @ a a a a
4626 4645 | a a a a
4627 4646 | a a a a
4628 4647 o a a a
4629 4648 |\ a a a
4630 4649 | | a a a
4631 4650 | | a a a
4632 4651 | o a a a
4633 4652 | | a a a
4634 4653 | | a a a
4635 4654 | | a a a
4636 4655 o | a a
4637 4656 |\ \ a a
4638 4657 | | | a a
4639 4658 | | | a a
4640 4659 | | | a a
4641 4660 | | | a a
4642 4661 | o | a a
4643 4662 | |/ a a
4644 4663 | | a a
4645 4664 | | a a
4646 4665 | | a a
4647 4666 | | a a
4648 4667 o | a a a
4649 4668 |/ a a a
4650 4669 | a a a
4651 4670 | a a a
4652 4671 o a a a a
4653 4672 a a a a
4654 4673 a a a a
4655 4674
4656 4675 Something tricky happens when there are elided nodes; the next drawn row of
4657 4676 edges can be more than one column wider, but the graph width only increases by
4658 4677 one column. The remaining columns are added in between the nodes.
4659 4678
4660 4679 $ hg log --graph -T "{graphwidth}" -r "0|2|4|5"
4661 4680 o 5
4662 4681 |\
4663 4682 | \
4664 4683 | :\
4665 4684 o : : 7
4666 4685 :/ /
4667 4686 : o 5
4668 4687 :/
4669 4688 o 3
4670 4689
4671 4690
4672 4691 $ cd ..
4673 4692
General Comments 0
You need to be logged in to leave comments. Login now