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