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