##// END OF EJS Templates
formatter: parse name of built-in formatter templates in standard way...
Yuya Nishihara -
r43370:90b9a7e0 default
parent child Browse files
Show More
@@ -1,762 +1,769 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 97 ... fm.write(b'reponame', b'[%s]\\n', b'baz')
98 98 ... files(ui, fm.nested(b'files', tmpl=b'{reponame}'))
99 99 ... fm.end()
100 100 >>> show(subrepos)
101 101 [baz]
102 102 foo
103 103 bar
104 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 contextlib
111 111 import itertools
112 112 import os
113 113
114 114 from .i18n import _
115 115 from .node import (
116 116 hex,
117 117 short,
118 118 )
119 119 from .thirdparty import attr
120 120
121 121 from . import (
122 122 error,
123 123 pycompat,
124 124 templatefilters,
125 125 templatekw,
126 126 templater,
127 127 templateutil,
128 128 util,
129 129 )
130 130 from .utils import (
131 131 cborutil,
132 132 dateutil,
133 133 stringutil,
134 134 )
135 135
136 136 pickle = util.pickle
137 137
138 138
139 139 class _nullconverter(object):
140 140 '''convert non-primitive data types to be processed by formatter'''
141 141
142 142 # set to True if context object should be stored as item
143 143 storecontext = False
144 144
145 145 @staticmethod
146 146 def wrapnested(data, tmpl, sep):
147 147 '''wrap nested data by appropriate type'''
148 148 return data
149 149
150 150 @staticmethod
151 151 def formatdate(date, fmt):
152 152 '''convert date tuple to appropriate format'''
153 153 # timestamp can be float, but the canonical form should be int
154 154 ts, tz = date
155 155 return (int(ts), tz)
156 156
157 157 @staticmethod
158 158 def formatdict(data, key, value, fmt, sep):
159 159 '''convert dict or key-value pairs to appropriate dict format'''
160 160 # use plain dict instead of util.sortdict so that data can be
161 161 # serialized as a builtin dict in pickle output
162 162 return dict(data)
163 163
164 164 @staticmethod
165 165 def formatlist(data, name, fmt, sep):
166 166 '''convert iterable to appropriate list format'''
167 167 return list(data)
168 168
169 169
170 170 class baseformatter(object):
171 171 def __init__(self, ui, topic, opts, converter):
172 172 self._ui = ui
173 173 self._topic = topic
174 174 self._opts = opts
175 175 self._converter = converter
176 176 self._item = None
177 177 # function to convert node to string suitable for this output
178 178 self.hexfunc = hex
179 179
180 180 def __enter__(self):
181 181 return self
182 182
183 183 def __exit__(self, exctype, excvalue, traceback):
184 184 if exctype is None:
185 185 self.end()
186 186
187 187 def _showitem(self):
188 188 '''show a formatted item once all data is collected'''
189 189
190 190 def startitem(self):
191 191 '''begin an item in the format list'''
192 192 if self._item is not None:
193 193 self._showitem()
194 194 self._item = {}
195 195
196 196 def formatdate(self, date, fmt=b'%a %b %d %H:%M:%S %Y %1%2'):
197 197 '''convert date tuple to appropriate format'''
198 198 return self._converter.formatdate(date, fmt)
199 199
200 200 def formatdict(self, data, key=b'key', value=b'value', fmt=None, sep=b' '):
201 201 '''convert dict or key-value pairs to appropriate dict format'''
202 202 return self._converter.formatdict(data, key, value, fmt, sep)
203 203
204 204 def formatlist(self, data, name, fmt=None, sep=b' '):
205 205 '''convert iterable to appropriate list format'''
206 206 # name is mandatory argument for now, but it could be optional if
207 207 # we have default template keyword, e.g. {item}
208 208 return self._converter.formatlist(data, name, fmt, sep)
209 209
210 210 def context(self, **ctxs):
211 211 '''insert context objects to be used to render template keywords'''
212 212 ctxs = pycompat.byteskwargs(ctxs)
213 213 assert all(k in {b'repo', b'ctx', b'fctx'} for k in ctxs)
214 214 if self._converter.storecontext:
215 215 # populate missing resources in fctx -> ctx -> repo order
216 216 if b'fctx' in ctxs and b'ctx' not in ctxs:
217 217 ctxs[b'ctx'] = ctxs[b'fctx'].changectx()
218 218 if b'ctx' in ctxs and b'repo' not in ctxs:
219 219 ctxs[b'repo'] = ctxs[b'ctx'].repo()
220 220 self._item.update(ctxs)
221 221
222 222 def datahint(self):
223 223 '''set of field names to be referenced'''
224 224 return set()
225 225
226 226 def data(self, **data):
227 227 '''insert data into item that's not shown in default output'''
228 228 data = pycompat.byteskwargs(data)
229 229 self._item.update(data)
230 230
231 231 def write(self, fields, deftext, *fielddata, **opts):
232 232 '''do default text output while assigning data to item'''
233 233 fieldkeys = fields.split()
234 234 assert len(fieldkeys) == len(fielddata), (fieldkeys, fielddata)
235 235 self._item.update(zip(fieldkeys, fielddata))
236 236
237 237 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
238 238 '''do conditional write (primarily for plain formatter)'''
239 239 fieldkeys = fields.split()
240 240 assert len(fieldkeys) == len(fielddata)
241 241 self._item.update(zip(fieldkeys, fielddata))
242 242
243 243 def plain(self, text, **opts):
244 244 '''show raw text for non-templated mode'''
245 245
246 246 def isplain(self):
247 247 '''check for plain formatter usage'''
248 248 return False
249 249
250 250 def nested(self, field, tmpl=None, sep=b''):
251 251 '''sub formatter to store nested data in the specified field'''
252 252 data = []
253 253 self._item[field] = self._converter.wrapnested(data, tmpl, sep)
254 254 return _nestedformatter(self._ui, self._converter, data)
255 255
256 256 def end(self):
257 257 '''end output for the formatter'''
258 258 if self._item is not None:
259 259 self._showitem()
260 260
261 261
262 262 def nullformatter(ui, topic, opts):
263 263 '''formatter that prints nothing'''
264 264 return baseformatter(ui, topic, opts, converter=_nullconverter)
265 265
266 266
267 267 class _nestedformatter(baseformatter):
268 268 '''build sub items and store them in the parent formatter'''
269 269
270 270 def __init__(self, ui, converter, data):
271 271 baseformatter.__init__(
272 272 self, ui, topic=b'', opts={}, converter=converter
273 273 )
274 274 self._data = data
275 275
276 276 def _showitem(self):
277 277 self._data.append(self._item)
278 278
279 279
280 280 def _iteritems(data):
281 281 '''iterate key-value pairs in stable order'''
282 282 if isinstance(data, dict):
283 283 return sorted(data.iteritems())
284 284 return data
285 285
286 286
287 287 class _plainconverter(object):
288 288 '''convert non-primitive data types to text'''
289 289
290 290 storecontext = False
291 291
292 292 @staticmethod
293 293 def wrapnested(data, tmpl, sep):
294 294 raise error.ProgrammingError(b'plainformatter should never be nested')
295 295
296 296 @staticmethod
297 297 def formatdate(date, fmt):
298 298 '''stringify date tuple in the given format'''
299 299 return dateutil.datestr(date, fmt)
300 300
301 301 @staticmethod
302 302 def formatdict(data, key, value, fmt, sep):
303 303 '''stringify key-value pairs separated by sep'''
304 304 prefmt = pycompat.identity
305 305 if fmt is None:
306 306 fmt = b'%s=%s'
307 307 prefmt = pycompat.bytestr
308 308 return sep.join(
309 309 fmt % (prefmt(k), prefmt(v)) for k, v in _iteritems(data)
310 310 )
311 311
312 312 @staticmethod
313 313 def formatlist(data, name, fmt, sep):
314 314 '''stringify iterable separated by sep'''
315 315 prefmt = pycompat.identity
316 316 if fmt is None:
317 317 fmt = b'%s'
318 318 prefmt = pycompat.bytestr
319 319 return sep.join(fmt % prefmt(e) for e in data)
320 320
321 321
322 322 class plainformatter(baseformatter):
323 323 '''the default text output scheme'''
324 324
325 325 def __init__(self, ui, out, topic, opts):
326 326 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
327 327 if ui.debugflag:
328 328 self.hexfunc = hex
329 329 else:
330 330 self.hexfunc = short
331 331 if ui is out:
332 332 self._write = ui.write
333 333 else:
334 334 self._write = lambda s, **opts: out.write(s)
335 335
336 336 def startitem(self):
337 337 pass
338 338
339 339 def data(self, **data):
340 340 pass
341 341
342 342 def write(self, fields, deftext, *fielddata, **opts):
343 343 self._write(deftext % fielddata, **opts)
344 344
345 345 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
346 346 '''do conditional write'''
347 347 if cond:
348 348 self._write(deftext % fielddata, **opts)
349 349
350 350 def plain(self, text, **opts):
351 351 self._write(text, **opts)
352 352
353 353 def isplain(self):
354 354 return True
355 355
356 356 def nested(self, field, tmpl=None, sep=b''):
357 357 # nested data will be directly written to ui
358 358 return self
359 359
360 360 def end(self):
361 361 pass
362 362
363 363
364 364 class debugformatter(baseformatter):
365 365 def __init__(self, ui, out, topic, opts):
366 366 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
367 367 self._out = out
368 368 self._out.write(b"%s = [\n" % self._topic)
369 369
370 370 def _showitem(self):
371 371 self._out.write(
372 372 b' %s,\n' % stringutil.pprint(self._item, indent=4, level=1)
373 373 )
374 374
375 375 def end(self):
376 376 baseformatter.end(self)
377 377 self._out.write(b"]\n")
378 378
379 379
380 380 class pickleformatter(baseformatter):
381 381 def __init__(self, ui, out, topic, opts):
382 382 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
383 383 self._out = out
384 384 self._data = []
385 385
386 386 def _showitem(self):
387 387 self._data.append(self._item)
388 388
389 389 def end(self):
390 390 baseformatter.end(self)
391 391 self._out.write(pickle.dumps(self._data))
392 392
393 393
394 394 class cborformatter(baseformatter):
395 395 '''serialize items as an indefinite-length CBOR array'''
396 396
397 397 def __init__(self, ui, out, topic, opts):
398 398 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
399 399 self._out = out
400 400 self._out.write(cborutil.BEGIN_INDEFINITE_ARRAY)
401 401
402 402 def _showitem(self):
403 403 self._out.write(b''.join(cborutil.streamencode(self._item)))
404 404
405 405 def end(self):
406 406 baseformatter.end(self)
407 407 self._out.write(cborutil.BREAK)
408 408
409 409
410 410 class jsonformatter(baseformatter):
411 411 def __init__(self, ui, out, topic, opts):
412 412 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
413 413 self._out = out
414 414 self._out.write(b"[")
415 415 self._first = True
416 416
417 417 def _showitem(self):
418 418 if self._first:
419 419 self._first = False
420 420 else:
421 421 self._out.write(b",")
422 422
423 423 self._out.write(b"\n {\n")
424 424 first = True
425 425 for k, v in sorted(self._item.items()):
426 426 if first:
427 427 first = False
428 428 else:
429 429 self._out.write(b",\n")
430 430 u = templatefilters.json(v, paranoid=False)
431 431 self._out.write(b' "%s": %s' % (k, u))
432 432 self._out.write(b"\n }")
433 433
434 434 def end(self):
435 435 baseformatter.end(self)
436 436 self._out.write(b"\n]\n")
437 437
438 438
439 439 class _templateconverter(object):
440 440 '''convert non-primitive data types to be processed by templater'''
441 441
442 442 storecontext = True
443 443
444 444 @staticmethod
445 445 def wrapnested(data, tmpl, sep):
446 446 '''wrap nested data by templatable type'''
447 447 return templateutil.mappinglist(data, tmpl=tmpl, sep=sep)
448 448
449 449 @staticmethod
450 450 def formatdate(date, fmt):
451 451 '''return date tuple'''
452 452 return templateutil.date(date)
453 453
454 454 @staticmethod
455 455 def formatdict(data, key, value, fmt, sep):
456 456 '''build object that can be evaluated as either plain string or dict'''
457 457 data = util.sortdict(_iteritems(data))
458 458
459 459 def f():
460 460 yield _plainconverter.formatdict(data, key, value, fmt, sep)
461 461
462 462 return templateutil.hybriddict(
463 463 data, key=key, value=value, fmt=fmt, gen=f
464 464 )
465 465
466 466 @staticmethod
467 467 def formatlist(data, name, fmt, sep):
468 468 '''build object that can be evaluated as either plain string or list'''
469 469 data = list(data)
470 470
471 471 def f():
472 472 yield _plainconverter.formatlist(data, name, fmt, sep)
473 473
474 474 return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f)
475 475
476 476
477 477 class templateformatter(baseformatter):
478 478 def __init__(self, ui, out, topic, opts, spec):
479 479 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
480 480 self._out = out
481 481 self._tref = spec.ref
482 482 self._t = loadtemplater(
483 483 ui,
484 484 spec,
485 485 defaults=templatekw.keywords,
486 486 resources=templateresources(ui),
487 487 cache=templatekw.defaulttempl,
488 488 )
489 489 self._parts = templatepartsmap(
490 490 spec, self._t, [b'docheader', b'docfooter', b'separator']
491 491 )
492 492 self._counter = itertools.count()
493 493 self._renderitem(b'docheader', {})
494 494
495 495 def _showitem(self):
496 496 item = self._item.copy()
497 497 item[b'index'] = index = next(self._counter)
498 498 if index > 0:
499 499 self._renderitem(b'separator', {})
500 500 self._renderitem(self._tref, item)
501 501
502 502 def _renderitem(self, part, item):
503 503 if part not in self._parts:
504 504 return
505 505 ref = self._parts[part]
506 506 self._out.write(self._t.render(ref, item))
507 507
508 508 @util.propertycache
509 509 def _symbolsused(self):
510 510 return self._t.symbolsused(self._tref)
511 511
512 512 def datahint(self):
513 513 '''set of field names to be referenced from the template'''
514 514 return self._symbolsused[0]
515 515
516 516 def end(self):
517 517 baseformatter.end(self)
518 518 self._renderitem(b'docfooter', {})
519 519
520 520
521 521 @attr.s(frozen=True)
522 522 class templatespec(object):
523 523 ref = attr.ib()
524 524 tmpl = attr.ib()
525 525 mapfile = attr.ib()
526 526
527 527
528 528 def lookuptemplate(ui, topic, tmpl):
529 529 """Find the template matching the given -T/--template spec 'tmpl'
530 530
531 531 'tmpl' can be any of the following:
532 532
533 533 - a literal template (e.g. '{rev}')
534 - a reference to built-in template (i.e. formatter)
534 535 - a map-file name or path (e.g. 'changelog')
535 536 - a reference to [templates] in config file
536 537 - a path to raw template file
537 538
538 539 A map file defines a stand-alone template environment. If a map file
539 540 selected, all templates defined in the file will be loaded, and the
540 541 template matching the given topic will be rendered. Aliases won't be
541 542 loaded from user config, but from the map file.
542 543
543 544 If no map file selected, all templates in [templates] section will be
544 545 available as well as aliases in [templatealias].
545 546 """
546 547
548 if not tmpl:
549 return templatespec(None, None, None)
550
547 551 # looks like a literal template?
548 552 if b'{' in tmpl:
549 553 return templatespec(b'', tmpl, None)
550 554
555 # a reference to built-in (formatter) template
556 if tmpl in {b'cbor', b'json', b'pickle', b'debug'}:
557 return templatespec(tmpl, None, None)
558
551 559 # perhaps a stock style?
552 560 if not os.path.split(tmpl)[0]:
553 561 mapname = templater.templatepath(
554 562 b'map-cmdline.' + tmpl
555 563 ) or templater.templatepath(tmpl)
556 564 if mapname and os.path.isfile(mapname):
557 565 return templatespec(topic, None, mapname)
558 566
559 567 # perhaps it's a reference to [templates]
560 568 if ui.config(b'templates', tmpl):
561 569 return templatespec(tmpl, None, None)
562 570
563 571 if tmpl == b'list':
564 572 ui.write(_(b"available styles: %s\n") % templater.stylelist())
565 573 raise error.Abort(_(b"specify a template"))
566 574
567 575 # perhaps it's a path to a map or a template
568 576 if (b'/' in tmpl or b'\\' in tmpl) and os.path.isfile(tmpl):
569 577 # is it a mapfile for a style?
570 578 if os.path.basename(tmpl).startswith(b"map-"):
571 579 return templatespec(topic, None, os.path.realpath(tmpl))
572 580 with util.posixfile(tmpl, b'rb') as f:
573 581 tmpl = f.read()
574 582 return templatespec(b'', tmpl, None)
575 583
576 584 # constant string?
577 585 return templatespec(b'', tmpl, None)
578 586
579 587
580 588 def templatepartsmap(spec, t, partnames):
581 589 """Create a mapping of {part: ref}"""
582 590 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
583 591 if spec.mapfile:
584 592 partsmap.update((p, p) for p in partnames if p in t)
585 593 elif spec.ref:
586 594 for part in partnames:
587 595 ref = b'%s:%s' % (spec.ref, part) # select config sub-section
588 596 if ref in t:
589 597 partsmap[part] = ref
590 598 return partsmap
591 599
592 600
593 601 def loadtemplater(ui, spec, defaults=None, resources=None, cache=None):
594 602 """Create a templater from either a literal template or loading from
595 603 a map file"""
596 604 assert not (spec.tmpl and spec.mapfile)
597 605 if spec.mapfile:
598 606 frommapfile = templater.templater.frommapfile
599 607 return frommapfile(
600 608 spec.mapfile, defaults=defaults, resources=resources, cache=cache
601 609 )
602 610 return maketemplater(
603 611 ui, spec.tmpl, defaults=defaults, resources=resources, cache=cache
604 612 )
605 613
606 614
607 615 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None):
608 616 """Create a templater from a string template 'tmpl'"""
609 617 aliases = ui.configitems(b'templatealias')
610 618 t = templater.templater(
611 619 defaults=defaults, resources=resources, cache=cache, aliases=aliases
612 620 )
613 621 t.cache.update(
614 622 (k, templater.unquotestring(v)) for k, v in ui.configitems(b'templates')
615 623 )
616 624 if tmpl:
617 625 t.cache[b''] = tmpl
618 626 return t
619 627
620 628
621 629 # marker to denote a resource to be loaded on demand based on mapping values
622 630 # (e.g. (ctx, path) -> fctx)
623 631 _placeholder = object()
624 632
625 633
626 634 class templateresources(templater.resourcemapper):
627 635 """Resource mapper designed for the default templatekw and function"""
628 636
629 637 def __init__(self, ui, repo=None):
630 638 self._resmap = {
631 639 b'cache': {}, # for templatekw/funcs to store reusable data
632 640 b'repo': repo,
633 641 b'ui': ui,
634 642 }
635 643
636 644 def availablekeys(self, mapping):
637 645 return {
638 646 k for k in self.knownkeys() if self._getsome(mapping, k) is not None
639 647 }
640 648
641 649 def knownkeys(self):
642 650 return {b'cache', b'ctx', b'fctx', b'repo', b'revcache', b'ui'}
643 651
644 652 def lookup(self, mapping, key):
645 653 if key not in self.knownkeys():
646 654 return None
647 655 v = self._getsome(mapping, key)
648 656 if v is _placeholder:
649 657 v = mapping[key] = self._loadermap[key](self, mapping)
650 658 return v
651 659
652 660 def populatemap(self, context, origmapping, newmapping):
653 661 mapping = {}
654 662 if self._hasnodespec(newmapping):
655 663 mapping[b'revcache'] = {} # per-ctx cache
656 664 if self._hasnodespec(origmapping) and self._hasnodespec(newmapping):
657 665 orignode = templateutil.runsymbol(context, origmapping, b'node')
658 666 mapping[b'originalnode'] = orignode
659 667 # put marker to override 'ctx'/'fctx' in mapping if any, and flag
660 668 # its existence to be reported by availablekeys()
661 669 if b'ctx' not in newmapping and self._hasliteral(newmapping, b'node'):
662 670 mapping[b'ctx'] = _placeholder
663 671 if b'fctx' not in newmapping and self._hasliteral(newmapping, b'path'):
664 672 mapping[b'fctx'] = _placeholder
665 673 return mapping
666 674
667 675 def _getsome(self, mapping, key):
668 676 v = mapping.get(key)
669 677 if v is not None:
670 678 return v
671 679 return self._resmap.get(key)
672 680
673 681 def _hasliteral(self, mapping, key):
674 682 """Test if a literal value is set or unset in the given mapping"""
675 683 return key in mapping and not callable(mapping[key])
676 684
677 685 def _getliteral(self, mapping, key):
678 686 """Return value of the given name if it is a literal"""
679 687 v = mapping.get(key)
680 688 if callable(v):
681 689 return None
682 690 return v
683 691
684 692 def _hasnodespec(self, mapping):
685 693 """Test if context revision is set or unset in the given mapping"""
686 694 return b'node' in mapping or b'ctx' in mapping
687 695
688 696 def _loadctx(self, mapping):
689 697 repo = self._getsome(mapping, b'repo')
690 698 node = self._getliteral(mapping, b'node')
691 699 if repo is None or node is None:
692 700 return
693 701 try:
694 702 return repo[node]
695 703 except error.RepoLookupError:
696 704 return None # maybe hidden/non-existent node
697 705
698 706 def _loadfctx(self, mapping):
699 707 ctx = self._getsome(mapping, b'ctx')
700 708 path = self._getliteral(mapping, b'path')
701 709 if ctx is None or path is None:
702 710 return None
703 711 try:
704 712 return ctx[path]
705 713 except error.LookupError:
706 714 return None # maybe removed file?
707 715
708 716 _loadermap = {
709 717 b'ctx': _loadctx,
710 718 b'fctx': _loadfctx,
711 719 }
712 720
713 721
714 722 def formatter(ui, out, topic, opts):
715 template = opts.get(b"template", b"")
716 if template == b"cbor":
723 spec = lookuptemplate(ui, topic, opts.get(b'template', b''))
724 if spec.ref == b"cbor":
717 725 return cborformatter(ui, out, topic, opts)
718 elif template == b"json":
726 elif spec.ref == b"json":
719 727 return jsonformatter(ui, out, topic, opts)
720 elif template == b"pickle":
728 elif spec.ref == b"pickle":
721 729 return pickleformatter(ui, out, topic, opts)
722 elif template == b"debug":
730 elif spec.ref == b"debug":
723 731 return debugformatter(ui, out, topic, opts)
724 elif template != b"":
725 spec = lookuptemplate(ui, topic, opts.get(b'template', b''))
732 elif spec.ref or spec.tmpl or spec.mapfile:
726 733 return templateformatter(ui, out, topic, opts, spec)
727 734 # developer config: ui.formatdebug
728 735 elif ui.configbool(b'ui', b'formatdebug'):
729 736 return debugformatter(ui, out, topic, opts)
730 737 # deprecated config: ui.formatjson
731 738 elif ui.configbool(b'ui', b'formatjson'):
732 739 return jsonformatter(ui, out, topic, opts)
733 740 return plainformatter(ui, out, topic, opts)
734 741
735 742
736 743 @contextlib.contextmanager
737 744 def openformatter(ui, filename, topic, opts):
738 745 """Create a formatter that writes outputs to the specified file
739 746
740 747 Must be invoked using the 'with' statement.
741 748 """
742 749 with util.posixfile(filename, b'wb') as out:
743 750 with formatter(ui, out, topic, opts) as fm:
744 751 yield fm
745 752
746 753
747 754 @contextlib.contextmanager
748 755 def _neverending(fm):
749 756 yield fm
750 757
751 758
752 759 def maybereopen(fm, filename):
753 760 """Create a formatter backed by file if filename specified, else return
754 761 the given formatter
755 762
756 763 Must be invoked using the 'with' statement. This will never call fm.end()
757 764 of the given formatter.
758 765 """
759 766 if filename:
760 767 return openformatter(fm._ui, filename, fm._topic, fm._opts)
761 768 else:
762 769 return _neverending(fm)
@@ -1,1064 +1,1064 b''
1 1 # logcmdutil.py - utility for log-like commands
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import itertools
11 11 import os
12 12 import posixpath
13 13
14 14 from .i18n import _
15 15 from .node import (
16 16 nullid,
17 17 wdirid,
18 18 wdirrev,
19 19 )
20 20
21 21 from . import (
22 22 dagop,
23 23 error,
24 24 formatter,
25 25 graphmod,
26 26 match as matchmod,
27 27 mdiff,
28 28 patch,
29 29 pathutil,
30 30 pycompat,
31 31 revset,
32 32 revsetlang,
33 33 scmutil,
34 34 smartset,
35 35 templatekw,
36 36 templater,
37 37 util,
38 38 )
39 39 from .utils import (
40 40 dateutil,
41 41 stringutil,
42 42 )
43 43
44 44
45 45 def getlimit(opts):
46 46 """get the log limit according to option -l/--limit"""
47 47 limit = opts.get(b'limit')
48 48 if limit:
49 49 try:
50 50 limit = int(limit)
51 51 except ValueError:
52 52 raise error.Abort(_(b'limit must be a positive integer'))
53 53 if limit <= 0:
54 54 raise error.Abort(_(b'limit must be positive'))
55 55 else:
56 56 limit = None
57 57 return limit
58 58
59 59
60 60 def diffordiffstat(
61 61 ui,
62 62 repo,
63 63 diffopts,
64 64 node1,
65 65 node2,
66 66 match,
67 67 changes=None,
68 68 stat=False,
69 69 fp=None,
70 70 graphwidth=0,
71 71 prefix=b'',
72 72 root=b'',
73 73 listsubrepos=False,
74 74 hunksfilterfn=None,
75 75 ):
76 76 '''show diff or diffstat.'''
77 77 ctx1 = repo[node1]
78 78 ctx2 = repo[node2]
79 79 if root:
80 80 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
81 81 else:
82 82 relroot = b''
83 83 copysourcematch = None
84 84
85 85 def compose(f, g):
86 86 return lambda x: f(g(x))
87 87
88 88 def pathfn(f):
89 89 return posixpath.join(prefix, f)
90 90
91 91 if relroot != b'':
92 92 # XXX relative roots currently don't work if the root is within a
93 93 # subrepo
94 94 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
95 95 uirelroot = uipathfn(pathfn(relroot))
96 96 relroot += b'/'
97 97 for matchroot in match.files():
98 98 if not matchroot.startswith(relroot):
99 99 ui.warn(
100 100 _(b'warning: %s not inside relative root %s\n')
101 101 % (uipathfn(pathfn(matchroot)), uirelroot)
102 102 )
103 103
104 104 relrootmatch = scmutil.match(ctx2, pats=[relroot], default=b'path')
105 105 match = matchmod.intersectmatchers(match, relrootmatch)
106 106 copysourcematch = relrootmatch
107 107
108 108 checkroot = repo.ui.configbool(
109 109 b'devel', b'all-warnings'
110 110 ) or repo.ui.configbool(b'devel', b'check-relroot')
111 111
112 112 def relrootpathfn(f):
113 113 if checkroot and not f.startswith(relroot):
114 114 raise AssertionError(
115 115 b"file %s doesn't start with relroot %s" % (f, relroot)
116 116 )
117 117 return f[len(relroot) :]
118 118
119 119 pathfn = compose(relrootpathfn, pathfn)
120 120
121 121 if stat:
122 122 diffopts = diffopts.copy(context=0, noprefix=False)
123 123 width = 80
124 124 if not ui.plain():
125 125 width = ui.termwidth() - graphwidth
126 126 # If an explicit --root was given, don't respect ui.relative-paths
127 127 if not relroot:
128 128 pathfn = compose(scmutil.getuipathfn(repo), pathfn)
129 129
130 130 chunks = ctx2.diff(
131 131 ctx1,
132 132 match,
133 133 changes,
134 134 opts=diffopts,
135 135 pathfn=pathfn,
136 136 copysourcematch=copysourcematch,
137 137 hunksfilterfn=hunksfilterfn,
138 138 )
139 139
140 140 if fp is not None or ui.canwritewithoutlabels():
141 141 out = fp or ui
142 142 if stat:
143 143 chunks = [patch.diffstat(util.iterlines(chunks), width=width)]
144 144 for chunk in util.filechunkiter(util.chunkbuffer(chunks)):
145 145 out.write(chunk)
146 146 else:
147 147 if stat:
148 148 chunks = patch.diffstatui(util.iterlines(chunks), width=width)
149 149 else:
150 150 chunks = patch.difflabel(
151 151 lambda chunks, **kwargs: chunks, chunks, opts=diffopts
152 152 )
153 153 if ui.canbatchlabeledwrites():
154 154
155 155 def gen():
156 156 for chunk, label in chunks:
157 157 yield ui.label(chunk, label=label)
158 158
159 159 for chunk in util.filechunkiter(util.chunkbuffer(gen())):
160 160 ui.write(chunk)
161 161 else:
162 162 for chunk, label in chunks:
163 163 ui.write(chunk, label=label)
164 164
165 165 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
166 166 tempnode2 = node2
167 167 try:
168 168 if node2 is not None:
169 169 tempnode2 = ctx2.substate[subpath][1]
170 170 except KeyError:
171 171 # A subrepo that existed in node1 was deleted between node1 and
172 172 # node2 (inclusive). Thus, ctx2's substate won't contain that
173 173 # subpath. The best we can do is to ignore it.
174 174 tempnode2 = None
175 175 submatch = matchmod.subdirmatcher(subpath, match)
176 176 subprefix = repo.wvfs.reljoin(prefix, subpath)
177 177 if listsubrepos or match.exact(subpath) or any(submatch.files()):
178 178 sub.diff(
179 179 ui,
180 180 diffopts,
181 181 tempnode2,
182 182 submatch,
183 183 changes=changes,
184 184 stat=stat,
185 185 fp=fp,
186 186 prefix=subprefix,
187 187 )
188 188
189 189
190 190 class changesetdiffer(object):
191 191 """Generate diff of changeset with pre-configured filtering functions"""
192 192
193 193 def _makefilematcher(self, ctx):
194 194 return scmutil.matchall(ctx.repo())
195 195
196 196 def _makehunksfilter(self, ctx):
197 197 return None
198 198
199 199 def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False):
200 200 repo = ctx.repo()
201 201 node = ctx.node()
202 202 prev = ctx.p1().node()
203 203 diffordiffstat(
204 204 ui,
205 205 repo,
206 206 diffopts,
207 207 prev,
208 208 node,
209 209 match=self._makefilematcher(ctx),
210 210 stat=stat,
211 211 graphwidth=graphwidth,
212 212 hunksfilterfn=self._makehunksfilter(ctx),
213 213 )
214 214
215 215
216 216 def changesetlabels(ctx):
217 217 labels = [b'log.changeset', b'changeset.%s' % ctx.phasestr()]
218 218 if ctx.obsolete():
219 219 labels.append(b'changeset.obsolete')
220 220 if ctx.isunstable():
221 221 labels.append(b'changeset.unstable')
222 222 for instability in ctx.instabilities():
223 223 labels.append(b'instability.%s' % instability)
224 224 return b' '.join(labels)
225 225
226 226
227 227 class changesetprinter(object):
228 228 '''show changeset information when templating not requested.'''
229 229
230 230 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
231 231 self.ui = ui
232 232 self.repo = repo
233 233 self.buffered = buffered
234 234 self._differ = differ or changesetdiffer()
235 235 self._diffopts = patch.diffallopts(ui, diffopts)
236 236 self._includestat = diffopts and diffopts.get(b'stat')
237 237 self._includediff = diffopts and diffopts.get(b'patch')
238 238 self.header = {}
239 239 self.hunk = {}
240 240 self.lastheader = None
241 241 self.footer = None
242 242 self._columns = templatekw.getlogcolumns()
243 243
244 244 def flush(self, ctx):
245 245 rev = ctx.rev()
246 246 if rev in self.header:
247 247 h = self.header[rev]
248 248 if h != self.lastheader:
249 249 self.lastheader = h
250 250 self.ui.write(h)
251 251 del self.header[rev]
252 252 if rev in self.hunk:
253 253 self.ui.write(self.hunk[rev])
254 254 del self.hunk[rev]
255 255
256 256 def close(self):
257 257 if self.footer:
258 258 self.ui.write(self.footer)
259 259
260 260 def show(self, ctx, copies=None, **props):
261 261 props = pycompat.byteskwargs(props)
262 262 if self.buffered:
263 263 self.ui.pushbuffer(labeled=True)
264 264 self._show(ctx, copies, props)
265 265 self.hunk[ctx.rev()] = self.ui.popbuffer()
266 266 else:
267 267 self._show(ctx, copies, props)
268 268
269 269 def _show(self, ctx, copies, props):
270 270 '''show a single changeset or file revision'''
271 271 changenode = ctx.node()
272 272 graphwidth = props.get(b'graphwidth', 0)
273 273
274 274 if self.ui.quiet:
275 275 self.ui.write(
276 276 b"%s\n" % scmutil.formatchangeid(ctx), label=b'log.node'
277 277 )
278 278 return
279 279
280 280 columns = self._columns
281 281 self.ui.write(
282 282 columns[b'changeset'] % scmutil.formatchangeid(ctx),
283 283 label=changesetlabels(ctx),
284 284 )
285 285
286 286 # branches are shown first before any other names due to backwards
287 287 # compatibility
288 288 branch = ctx.branch()
289 289 # don't show the default branch name
290 290 if branch != b'default':
291 291 self.ui.write(columns[b'branch'] % branch, label=b'log.branch')
292 292
293 293 for nsname, ns in self.repo.names.iteritems():
294 294 # branches has special logic already handled above, so here we just
295 295 # skip it
296 296 if nsname == b'branches':
297 297 continue
298 298 # we will use the templatename as the color name since those two
299 299 # should be the same
300 300 for name in ns.names(self.repo, changenode):
301 301 self.ui.write(ns.logfmt % name, label=b'log.%s' % ns.colorname)
302 302 if self.ui.debugflag:
303 303 self.ui.write(
304 304 columns[b'phase'] % ctx.phasestr(), label=b'log.phase'
305 305 )
306 306 for pctx in scmutil.meaningfulparents(self.repo, ctx):
307 307 label = b'log.parent changeset.%s' % pctx.phasestr()
308 308 self.ui.write(
309 309 columns[b'parent'] % scmutil.formatchangeid(pctx), label=label
310 310 )
311 311
312 312 if self.ui.debugflag:
313 313 mnode = ctx.manifestnode()
314 314 if mnode is None:
315 315 mnode = wdirid
316 316 mrev = wdirrev
317 317 else:
318 318 mrev = self.repo.manifestlog.rev(mnode)
319 319 self.ui.write(
320 320 columns[b'manifest']
321 321 % scmutil.formatrevnode(self.ui, mrev, mnode),
322 322 label=b'ui.debug log.manifest',
323 323 )
324 324 self.ui.write(columns[b'user'] % ctx.user(), label=b'log.user')
325 325 self.ui.write(
326 326 columns[b'date'] % dateutil.datestr(ctx.date()), label=b'log.date'
327 327 )
328 328
329 329 if ctx.isunstable():
330 330 instabilities = ctx.instabilities()
331 331 self.ui.write(
332 332 columns[b'instability'] % b', '.join(instabilities),
333 333 label=b'log.instability',
334 334 )
335 335
336 336 elif ctx.obsolete():
337 337 self._showobsfate(ctx)
338 338
339 339 self._exthook(ctx)
340 340
341 341 if self.ui.debugflag:
342 342 files = ctx.p1().status(ctx)[:3]
343 343 for key, value in zip([b'files', b'files+', b'files-'], files):
344 344 if value:
345 345 self.ui.write(
346 346 columns[key] % b" ".join(value),
347 347 label=b'ui.debug log.files',
348 348 )
349 349 elif ctx.files() and self.ui.verbose:
350 350 self.ui.write(
351 351 columns[b'files'] % b" ".join(ctx.files()),
352 352 label=b'ui.note log.files',
353 353 )
354 354 if copies and self.ui.verbose:
355 355 copies = [b'%s (%s)' % c for c in copies]
356 356 self.ui.write(
357 357 columns[b'copies'] % b' '.join(copies),
358 358 label=b'ui.note log.copies',
359 359 )
360 360
361 361 extra = ctx.extra()
362 362 if extra and self.ui.debugflag:
363 363 for key, value in sorted(extra.items()):
364 364 self.ui.write(
365 365 columns[b'extra'] % (key, stringutil.escapestr(value)),
366 366 label=b'ui.debug log.extra',
367 367 )
368 368
369 369 description = ctx.description().strip()
370 370 if description:
371 371 if self.ui.verbose:
372 372 self.ui.write(
373 373 _(b"description:\n"), label=b'ui.note log.description'
374 374 )
375 375 self.ui.write(description, label=b'ui.note log.description')
376 376 self.ui.write(b"\n\n")
377 377 else:
378 378 self.ui.write(
379 379 columns[b'summary'] % description.splitlines()[0],
380 380 label=b'log.summary',
381 381 )
382 382 self.ui.write(b"\n")
383 383
384 384 self._showpatch(ctx, graphwidth)
385 385
386 386 def _showobsfate(self, ctx):
387 387 # TODO: do not depend on templater
388 388 tres = formatter.templateresources(self.repo.ui, self.repo)
389 389 t = formatter.maketemplater(
390 390 self.repo.ui,
391 391 b'{join(obsfate, "\n")}',
392 392 defaults=templatekw.keywords,
393 393 resources=tres,
394 394 )
395 395 obsfate = t.renderdefault({b'ctx': ctx}).splitlines()
396 396
397 397 if obsfate:
398 398 for obsfateline in obsfate:
399 399 self.ui.write(
400 400 self._columns[b'obsolete'] % obsfateline,
401 401 label=b'log.obsfate',
402 402 )
403 403
404 404 def _exthook(self, ctx):
405 405 '''empty method used by extension as a hook point
406 406 '''
407 407
408 408 def _showpatch(self, ctx, graphwidth=0):
409 409 if self._includestat:
410 410 self._differ.showdiff(
411 411 self.ui, ctx, self._diffopts, graphwidth, stat=True
412 412 )
413 413 if self._includestat and self._includediff:
414 414 self.ui.write(b"\n")
415 415 if self._includediff:
416 416 self._differ.showdiff(
417 417 self.ui, ctx, self._diffopts, graphwidth, stat=False
418 418 )
419 419 if self._includestat or self._includediff:
420 420 self.ui.write(b"\n")
421 421
422 422
423 423 class changesetformatter(changesetprinter):
424 424 """Format changeset information by generic formatter"""
425 425
426 426 def __init__(
427 427 self, ui, repo, fm, differ=None, diffopts=None, buffered=False
428 428 ):
429 429 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
430 430 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
431 431 self._fm = fm
432 432
433 433 def close(self):
434 434 self._fm.end()
435 435
436 436 def _show(self, ctx, copies, props):
437 437 '''show a single changeset or file revision'''
438 438 fm = self._fm
439 439 fm.startitem()
440 440 fm.context(ctx=ctx)
441 441 fm.data(rev=scmutil.intrev(ctx), node=fm.hexfunc(scmutil.binnode(ctx)))
442 442
443 443 if self.ui.quiet:
444 444 return
445 445
446 446 fm.data(
447 447 branch=ctx.branch(),
448 448 phase=ctx.phasestr(),
449 449 user=ctx.user(),
450 450 date=fm.formatdate(ctx.date()),
451 451 desc=ctx.description(),
452 452 bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'),
453 453 tags=fm.formatlist(ctx.tags(), name=b'tag'),
454 454 parents=fm.formatlist(
455 455 [fm.hexfunc(c.node()) for c in ctx.parents()], name=b'node'
456 456 ),
457 457 )
458 458
459 459 if self.ui.debugflag:
460 460 fm.data(
461 461 manifest=fm.hexfunc(ctx.manifestnode() or wdirid),
462 462 extra=fm.formatdict(ctx.extra()),
463 463 )
464 464
465 465 files = ctx.p1().status(ctx)
466 466 fm.data(
467 467 modified=fm.formatlist(files[0], name=b'file'),
468 468 added=fm.formatlist(files[1], name=b'file'),
469 469 removed=fm.formatlist(files[2], name=b'file'),
470 470 )
471 471
472 472 elif self.ui.verbose:
473 473 fm.data(files=fm.formatlist(ctx.files(), name=b'file'))
474 474 if copies:
475 475 fm.data(
476 476 copies=fm.formatdict(copies, key=b'name', value=b'source')
477 477 )
478 478
479 479 if self._includestat:
480 480 self.ui.pushbuffer()
481 481 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
482 482 fm.data(diffstat=self.ui.popbuffer())
483 483 if self._includediff:
484 484 self.ui.pushbuffer()
485 485 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
486 486 fm.data(diff=self.ui.popbuffer())
487 487
488 488
489 489 class changesettemplater(changesetprinter):
490 490 '''format changeset information.
491 491
492 492 Note: there are a variety of convenience functions to build a
493 493 changesettemplater for common cases. See functions such as:
494 494 maketemplater, changesetdisplayer, buildcommittemplate, or other
495 495 functions that use changesest_templater.
496 496 '''
497 497
498 498 # Arguments before "buffered" used to be positional. Consider not
499 499 # adding/removing arguments before "buffered" to not break callers.
500 500 def __init__(
501 501 self, ui, repo, tmplspec, differ=None, diffopts=None, buffered=False
502 502 ):
503 503 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
504 504 # tres is shared with _graphnodeformatter()
505 505 self._tresources = tres = formatter.templateresources(ui, repo)
506 506 self.t = formatter.loadtemplater(
507 507 ui,
508 508 tmplspec,
509 509 defaults=templatekw.keywords,
510 510 resources=tres,
511 511 cache=templatekw.defaulttempl,
512 512 )
513 513 self._counter = itertools.count()
514 514
515 515 self._tref = tmplspec.ref
516 516 self._parts = {
517 517 b'header': b'',
518 518 b'footer': b'',
519 519 tmplspec.ref: tmplspec.ref,
520 520 b'docheader': b'',
521 521 b'docfooter': b'',
522 522 b'separator': b'',
523 523 }
524 524 if tmplspec.mapfile:
525 525 # find correct templates for current mode, for backward
526 526 # compatibility with 'log -v/-q/--debug' using a mapfile
527 527 tmplmodes = [
528 528 (True, b''),
529 529 (self.ui.verbose, b'_verbose'),
530 530 (self.ui.quiet, b'_quiet'),
531 531 (self.ui.debugflag, b'_debug'),
532 532 ]
533 533 for mode, postfix in tmplmodes:
534 534 for t in self._parts:
535 535 cur = t + postfix
536 536 if mode and cur in self.t:
537 537 self._parts[t] = cur
538 538 else:
539 539 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
540 540 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
541 541 self._parts.update(m)
542 542
543 543 if self._parts[b'docheader']:
544 544 self.ui.write(self.t.render(self._parts[b'docheader'], {}))
545 545
546 546 def close(self):
547 547 if self._parts[b'docfooter']:
548 548 if not self.footer:
549 549 self.footer = b""
550 550 self.footer += self.t.render(self._parts[b'docfooter'], {})
551 551 return super(changesettemplater, self).close()
552 552
553 553 def _show(self, ctx, copies, props):
554 554 '''show a single changeset or file revision'''
555 555 props = props.copy()
556 556 props[b'ctx'] = ctx
557 557 props[b'index'] = index = next(self._counter)
558 558 props[b'revcache'] = {b'copies': copies}
559 559 graphwidth = props.get(b'graphwidth', 0)
560 560
561 561 # write separator, which wouldn't work well with the header part below
562 562 # since there's inherently a conflict between header (across items) and
563 563 # separator (per item)
564 564 if self._parts[b'separator'] and index > 0:
565 565 self.ui.write(self.t.render(self._parts[b'separator'], {}))
566 566
567 567 # write header
568 568 if self._parts[b'header']:
569 569 h = self.t.render(self._parts[b'header'], props)
570 570 if self.buffered:
571 571 self.header[ctx.rev()] = h
572 572 else:
573 573 if self.lastheader != h:
574 574 self.lastheader = h
575 575 self.ui.write(h)
576 576
577 577 # write changeset metadata, then patch if requested
578 578 key = self._parts[self._tref]
579 579 self.ui.write(self.t.render(key, props))
580 580 self._showpatch(ctx, graphwidth)
581 581
582 582 if self._parts[b'footer']:
583 583 if not self.footer:
584 584 self.footer = self.t.render(self._parts[b'footer'], props)
585 585
586 586
587 587 def templatespec(tmpl, mapfile):
588 588 if pycompat.ispy3:
589 589 assert not isinstance(tmpl, str), b'tmpl must not be a str'
590 590 if mapfile:
591 591 return formatter.templatespec(b'changeset', tmpl, mapfile)
592 592 else:
593 593 return formatter.templatespec(b'', tmpl, None)
594 594
595 595
596 596 def _lookuptemplate(ui, tmpl, style):
597 597 """Find the template matching the given template spec or style
598 598
599 599 See formatter.lookuptemplate() for details.
600 600 """
601 601
602 602 # ui settings
603 603 if not tmpl and not style: # template are stronger than style
604 604 tmpl = ui.config(b'ui', b'logtemplate')
605 605 if tmpl:
606 606 return templatespec(templater.unquotestring(tmpl), None)
607 607 else:
608 608 style = util.expandpath(ui.config(b'ui', b'style'))
609 609
610 610 if not tmpl and style:
611 611 mapfile = style
612 612 if not os.path.split(mapfile)[0]:
613 613 mapname = templater.templatepath(
614 614 b'map-cmdline.' + mapfile
615 615 ) or templater.templatepath(mapfile)
616 616 if mapname:
617 617 mapfile = mapname
618 618 return templatespec(None, mapfile)
619 619
620 if not tmpl:
621 return templatespec(None, None)
622
623 620 return formatter.lookuptemplate(ui, b'changeset', tmpl)
624 621
625 622
626 623 def maketemplater(ui, repo, tmpl, buffered=False):
627 624 """Create a changesettemplater from a literal template 'tmpl'
628 625 byte-string."""
629 626 spec = templatespec(tmpl, None)
630 627 return changesettemplater(ui, repo, spec, buffered=buffered)
631 628
632 629
633 630 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
634 631 """show one changeset using template or regular display.
635 632
636 633 Display format will be the first non-empty hit of:
637 634 1. option 'template'
638 635 2. option 'style'
639 636 3. [ui] setting 'logtemplate'
640 637 4. [ui] setting 'style'
641 638 If all of these values are either the unset or the empty string,
642 639 regular display via changesetprinter() is done.
643 640 """
644 641 postargs = (differ, opts, buffered)
645 if opts.get(b'template') in {b'cbor', b'json'}:
642 spec = _lookuptemplate(ui, opts.get(b'template'), opts.get(b'style'))
643
644 # machine-readable formats have slightly different keyword set than
645 # plain templates, which are handled by changesetformatter.
646 # note that {b'pickle', b'debug'} can also be added to the list if needed.
647 if spec.ref in {b'cbor', b'json'}:
646 648 fm = ui.formatter(b'log', opts)
647 649 return changesetformatter(ui, repo, fm, *postargs)
648 650
649 spec = _lookuptemplate(ui, opts.get(b'template'), opts.get(b'style'))
650
651 651 if not spec.ref and not spec.tmpl and not spec.mapfile:
652 652 return changesetprinter(ui, repo, *postargs)
653 653
654 654 return changesettemplater(ui, repo, spec, *postargs)
655 655
656 656
657 657 def _makematcher(repo, revs, pats, opts):
658 658 """Build matcher and expanded patterns from log options
659 659
660 660 If --follow, revs are the revisions to follow from.
661 661
662 662 Returns (match, pats, slowpath) where
663 663 - match: a matcher built from the given pats and -I/-X opts
664 664 - pats: patterns used (globs are expanded on Windows)
665 665 - slowpath: True if patterns aren't as simple as scanning filelogs
666 666 """
667 667 # pats/include/exclude are passed to match.match() directly in
668 668 # _matchfiles() revset but walkchangerevs() builds its matcher with
669 669 # scmutil.match(). The difference is input pats are globbed on
670 670 # platforms without shell expansion (windows).
671 671 wctx = repo[None]
672 672 match, pats = scmutil.matchandpats(wctx, pats, opts)
673 673 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
674 674 if not slowpath:
675 675 follow = opts.get(b'follow') or opts.get(b'follow_first')
676 676 startctxs = []
677 677 if follow and opts.get(b'rev'):
678 678 startctxs = [repo[r] for r in revs]
679 679 for f in match.files():
680 680 if follow and startctxs:
681 681 # No idea if the path was a directory at that revision, so
682 682 # take the slow path.
683 683 if any(f not in c for c in startctxs):
684 684 slowpath = True
685 685 continue
686 686 elif follow and f not in wctx:
687 687 # If the file exists, it may be a directory, so let it
688 688 # take the slow path.
689 689 if os.path.exists(repo.wjoin(f)):
690 690 slowpath = True
691 691 continue
692 692 else:
693 693 raise error.Abort(
694 694 _(
695 695 b'cannot follow file not in parent '
696 696 b'revision: "%s"'
697 697 )
698 698 % f
699 699 )
700 700 filelog = repo.file(f)
701 701 if not filelog:
702 702 # A zero count may be a directory or deleted file, so
703 703 # try to find matching entries on the slow path.
704 704 if follow:
705 705 raise error.Abort(
706 706 _(b'cannot follow nonexistent file: "%s"') % f
707 707 )
708 708 slowpath = True
709 709
710 710 # We decided to fall back to the slowpath because at least one
711 711 # of the paths was not a file. Check to see if at least one of them
712 712 # existed in history - in that case, we'll continue down the
713 713 # slowpath; otherwise, we can turn off the slowpath
714 714 if slowpath:
715 715 for path in match.files():
716 716 if path == b'.' or path in repo.store:
717 717 break
718 718 else:
719 719 slowpath = False
720 720
721 721 return match, pats, slowpath
722 722
723 723
724 724 def _fileancestors(repo, revs, match, followfirst):
725 725 fctxs = []
726 726 for r in revs:
727 727 ctx = repo[r]
728 728 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
729 729
730 730 # When displaying a revision with --patch --follow FILE, we have
731 731 # to know which file of the revision must be diffed. With
732 732 # --follow, we want the names of the ancestors of FILE in the
733 733 # revision, stored in "fcache". "fcache" is populated as a side effect
734 734 # of the graph traversal.
735 735 fcache = {}
736 736
737 737 def filematcher(ctx):
738 738 return scmutil.matchfiles(repo, fcache.get(ctx.rev(), []))
739 739
740 740 def revgen():
741 741 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
742 742 fcache[rev] = [c.path() for c in cs]
743 743 yield rev
744 744
745 745 return smartset.generatorset(revgen(), iterasc=False), filematcher
746 746
747 747
748 748 def _makenofollowfilematcher(repo, pats, opts):
749 749 '''hook for extensions to override the filematcher for non-follow cases'''
750 750 return None
751 751
752 752
753 753 _opt2logrevset = {
754 754 b'no_merges': (b'not merge()', None),
755 755 b'only_merges': (b'merge()', None),
756 756 b'_matchfiles': (None, b'_matchfiles(%ps)'),
757 757 b'date': (b'date(%s)', None),
758 758 b'branch': (b'branch(%s)', b'%lr'),
759 759 b'_patslog': (b'filelog(%s)', b'%lr'),
760 760 b'keyword': (b'keyword(%s)', b'%lr'),
761 761 b'prune': (b'ancestors(%s)', b'not %lr'),
762 762 b'user': (b'user(%s)', b'%lr'),
763 763 }
764 764
765 765
766 766 def _makerevset(repo, match, pats, slowpath, opts):
767 767 """Return a revset string built from log options and file patterns"""
768 768 opts = dict(opts)
769 769 # follow or not follow?
770 770 follow = opts.get(b'follow') or opts.get(b'follow_first')
771 771
772 772 # branch and only_branch are really aliases and must be handled at
773 773 # the same time
774 774 opts[b'branch'] = opts.get(b'branch', []) + opts.get(b'only_branch', [])
775 775 opts[b'branch'] = [repo.lookupbranch(b) for b in opts[b'branch']]
776 776
777 777 if slowpath:
778 778 # See walkchangerevs() slow path.
779 779 #
780 780 # pats/include/exclude cannot be represented as separate
781 781 # revset expressions as their filtering logic applies at file
782 782 # level. For instance "-I a -X b" matches a revision touching
783 783 # "a" and "b" while "file(a) and not file(b)" does
784 784 # not. Besides, filesets are evaluated against the working
785 785 # directory.
786 786 matchargs = [b'r:', b'd:relpath']
787 787 for p in pats:
788 788 matchargs.append(b'p:' + p)
789 789 for p in opts.get(b'include', []):
790 790 matchargs.append(b'i:' + p)
791 791 for p in opts.get(b'exclude', []):
792 792 matchargs.append(b'x:' + p)
793 793 opts[b'_matchfiles'] = matchargs
794 794 elif not follow:
795 795 opts[b'_patslog'] = list(pats)
796 796
797 797 expr = []
798 798 for op, val in sorted(opts.iteritems()):
799 799 if not val:
800 800 continue
801 801 if op not in _opt2logrevset:
802 802 continue
803 803 revop, listop = _opt2logrevset[op]
804 804 if revop and b'%' not in revop:
805 805 expr.append(revop)
806 806 elif not listop:
807 807 expr.append(revsetlang.formatspec(revop, val))
808 808 else:
809 809 if revop:
810 810 val = [revsetlang.formatspec(revop, v) for v in val]
811 811 expr.append(revsetlang.formatspec(listop, val))
812 812
813 813 if expr:
814 814 expr = b'(' + b' and '.join(expr) + b')'
815 815 else:
816 816 expr = None
817 817 return expr
818 818
819 819
820 820 def _initialrevs(repo, opts):
821 821 """Return the initial set of revisions to be filtered or followed"""
822 822 follow = opts.get(b'follow') or opts.get(b'follow_first')
823 823 if opts.get(b'rev'):
824 824 revs = scmutil.revrange(repo, opts[b'rev'])
825 825 elif follow and repo.dirstate.p1() == nullid:
826 826 revs = smartset.baseset()
827 827 elif follow:
828 828 revs = repo.revs(b'.')
829 829 else:
830 830 revs = smartset.spanset(repo)
831 831 revs.reverse()
832 832 return revs
833 833
834 834
835 835 def getrevs(repo, pats, opts):
836 836 """Return (revs, differ) where revs is a smartset
837 837
838 838 differ is a changesetdiffer with pre-configured file matcher.
839 839 """
840 840 follow = opts.get(b'follow') or opts.get(b'follow_first')
841 841 followfirst = opts.get(b'follow_first')
842 842 limit = getlimit(opts)
843 843 revs = _initialrevs(repo, opts)
844 844 if not revs:
845 845 return smartset.baseset(), None
846 846 match, pats, slowpath = _makematcher(repo, revs, pats, opts)
847 847 filematcher = None
848 848 if follow:
849 849 if slowpath or match.always():
850 850 revs = dagop.revancestors(repo, revs, followfirst=followfirst)
851 851 else:
852 852 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
853 853 revs.reverse()
854 854 if filematcher is None:
855 855 filematcher = _makenofollowfilematcher(repo, pats, opts)
856 856 if filematcher is None:
857 857
858 858 def filematcher(ctx):
859 859 return match
860 860
861 861 expr = _makerevset(repo, match, pats, slowpath, opts)
862 862 if opts.get(b'graph'):
863 863 # User-specified revs might be unsorted, but don't sort before
864 864 # _makerevset because it might depend on the order of revs
865 865 if repo.ui.configbool(b'experimental', b'log.topo'):
866 866 if not revs.istopo():
867 867 revs = dagop.toposort(revs, repo.changelog.parentrevs)
868 868 # TODO: try to iterate the set lazily
869 869 revs = revset.baseset(list(revs), istopo=True)
870 870 elif not (revs.isdescending() or revs.istopo()):
871 871 revs.sort(reverse=True)
872 872 if expr:
873 873 matcher = revset.match(None, expr)
874 874 revs = matcher(repo, revs)
875 875 if limit is not None:
876 876 revs = revs.slice(0, limit)
877 877
878 878 differ = changesetdiffer()
879 879 differ._makefilematcher = filematcher
880 880 return revs, differ
881 881
882 882
883 883 def _parselinerangeopt(repo, opts):
884 884 """Parse --line-range log option and return a list of tuples (filename,
885 885 (fromline, toline)).
886 886 """
887 887 linerangebyfname = []
888 888 for pat in opts.get(b'line_range', []):
889 889 try:
890 890 pat, linerange = pat.rsplit(b',', 1)
891 891 except ValueError:
892 892 raise error.Abort(_(b'malformatted line-range pattern %s') % pat)
893 893 try:
894 894 fromline, toline = map(int, linerange.split(b':'))
895 895 except ValueError:
896 896 raise error.Abort(_(b"invalid line range for %s") % pat)
897 897 msg = _(b"line range pattern '%s' must match exactly one file") % pat
898 898 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
899 899 linerangebyfname.append(
900 900 (fname, util.processlinerange(fromline, toline))
901 901 )
902 902 return linerangebyfname
903 903
904 904
905 905 def getlinerangerevs(repo, userrevs, opts):
906 906 """Return (revs, differ).
907 907
908 908 "revs" are revisions obtained by processing "line-range" log options and
909 909 walking block ancestors of each specified file/line-range.
910 910
911 911 "differ" is a changesetdiffer with pre-configured file matcher and hunks
912 912 filter.
913 913 """
914 914 wctx = repo[None]
915 915
916 916 # Two-levels map of "rev -> file ctx -> [line range]".
917 917 linerangesbyrev = {}
918 918 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
919 919 if fname not in wctx:
920 920 raise error.Abort(
921 921 _(b'cannot follow file not in parent ' b'revision: "%s"')
922 922 % fname
923 923 )
924 924 fctx = wctx.filectx(fname)
925 925 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
926 926 rev = fctx.introrev()
927 927 if rev not in userrevs:
928 928 continue
929 929 linerangesbyrev.setdefault(rev, {}).setdefault(
930 930 fctx.path(), []
931 931 ).append(linerange)
932 932
933 933 def nofilterhunksfn(fctx, hunks):
934 934 return hunks
935 935
936 936 def hunksfilter(ctx):
937 937 fctxlineranges = linerangesbyrev.get(ctx.rev())
938 938 if fctxlineranges is None:
939 939 return nofilterhunksfn
940 940
941 941 def filterfn(fctx, hunks):
942 942 lineranges = fctxlineranges.get(fctx.path())
943 943 if lineranges is not None:
944 944 for hr, lines in hunks:
945 945 if hr is None: # binary
946 946 yield hr, lines
947 947 continue
948 948 if any(mdiff.hunkinrange(hr[2:], lr) for lr in lineranges):
949 949 yield hr, lines
950 950 else:
951 951 for hunk in hunks:
952 952 yield hunk
953 953
954 954 return filterfn
955 955
956 956 def filematcher(ctx):
957 957 files = list(linerangesbyrev.get(ctx.rev(), []))
958 958 return scmutil.matchfiles(repo, files)
959 959
960 960 revs = sorted(linerangesbyrev, reverse=True)
961 961
962 962 differ = changesetdiffer()
963 963 differ._makefilematcher = filematcher
964 964 differ._makehunksfilter = hunksfilter
965 965 return revs, differ
966 966
967 967
968 968 def _graphnodeformatter(ui, displayer):
969 969 spec = ui.config(b'ui', b'graphnodetemplate')
970 970 if not spec:
971 971 return templatekw.getgraphnode # fast path for "{graphnode}"
972 972
973 973 spec = templater.unquotestring(spec)
974 974 if isinstance(displayer, changesettemplater):
975 975 # reuse cache of slow templates
976 976 tres = displayer._tresources
977 977 else:
978 978 tres = formatter.templateresources(ui)
979 979 templ = formatter.maketemplater(
980 980 ui, spec, defaults=templatekw.keywords, resources=tres
981 981 )
982 982
983 983 def formatnode(repo, ctx):
984 984 props = {b'ctx': ctx, b'repo': repo}
985 985 return templ.renderdefault(props)
986 986
987 987 return formatnode
988 988
989 989
990 990 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None):
991 991 props = props or {}
992 992 formatnode = _graphnodeformatter(ui, displayer)
993 993 state = graphmod.asciistate()
994 994 styles = state[b'styles']
995 995
996 996 # only set graph styling if HGPLAIN is not set.
997 997 if ui.plain(b'graph'):
998 998 # set all edge styles to |, the default pre-3.8 behaviour
999 999 styles.update(dict.fromkeys(styles, b'|'))
1000 1000 else:
1001 1001 edgetypes = {
1002 1002 b'parent': graphmod.PARENT,
1003 1003 b'grandparent': graphmod.GRANDPARENT,
1004 1004 b'missing': graphmod.MISSINGPARENT,
1005 1005 }
1006 1006 for name, key in edgetypes.items():
1007 1007 # experimental config: experimental.graphstyle.*
1008 1008 styles[key] = ui.config(
1009 1009 b'experimental', b'graphstyle.%s' % name, styles[key]
1010 1010 )
1011 1011 if not styles[key]:
1012 1012 styles[key] = None
1013 1013
1014 1014 # experimental config: experimental.graphshorten
1015 1015 state[b'graphshorten'] = ui.configbool(b'experimental', b'graphshorten')
1016 1016
1017 1017 for rev, type, ctx, parents in dag:
1018 1018 char = formatnode(repo, ctx)
1019 1019 copies = getcopies(ctx) if getcopies else None
1020 1020 edges = edgefn(type, char, state, rev, parents)
1021 1021 firstedge = next(edges)
1022 1022 width = firstedge[2]
1023 1023 displayer.show(
1024 1024 ctx, copies=copies, graphwidth=width, **pycompat.strkwargs(props)
1025 1025 )
1026 1026 lines = displayer.hunk.pop(rev).split(b'\n')
1027 1027 if not lines[-1]:
1028 1028 del lines[-1]
1029 1029 displayer.flush(ctx)
1030 1030 for type, char, width, coldata in itertools.chain([firstedge], edges):
1031 1031 graphmod.ascii(ui, state, type, char, lines, coldata)
1032 1032 lines = []
1033 1033 displayer.close()
1034 1034
1035 1035
1036 1036 def displaygraphrevs(ui, repo, revs, displayer, getrenamed):
1037 1037 revdag = graphmod.dagwalker(repo, revs)
1038 1038 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
1039 1039
1040 1040
1041 1041 def displayrevs(ui, repo, revs, displayer, getcopies):
1042 1042 for rev in revs:
1043 1043 ctx = repo[rev]
1044 1044 copies = getcopies(ctx) if getcopies else None
1045 1045 displayer.show(ctx, copies=copies)
1046 1046 displayer.flush(ctx)
1047 1047 displayer.close()
1048 1048
1049 1049
1050 1050 def checkunsupportedgraphflags(pats, opts):
1051 1051 for op in [b"newest_first"]:
1052 1052 if op in opts and opts[op]:
1053 1053 raise error.Abort(
1054 1054 _(b"-G/--graph option is incompatible with --%s")
1055 1055 % op.replace(b"_", b"-")
1056 1056 )
1057 1057
1058 1058
1059 1059 def graphrevs(repo, nodes, opts):
1060 1060 limit = getlimit(opts)
1061 1061 nodes.reverse()
1062 1062 if limit is not None:
1063 1063 nodes = nodes[:limit]
1064 1064 return graphmod.nodes(repo, nodes)
@@ -1,1828 +1,1837 b''
1 1 Test template map files and styles
2 2 ==================================
3 3
4 4 $ hg init a
5 5 $ cd a
6 6 $ echo a > a
7 7 $ hg add a
8 8 $ echo line 1 > b
9 9 $ echo line 2 >> b
10 10 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
11 11
12 12 $ hg add b
13 13 $ echo other 1 > c
14 14 $ echo other 2 >> c
15 15 $ echo >> c
16 16 $ echo other 3 >> c
17 17 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
18 18
19 19 $ hg add c
20 20 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
21 21 $ echo c >> c
22 22 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
23 23
24 24 $ echo foo > .hg/branch
25 25 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
26 26
27 27 $ hg co -q 3
28 28 $ echo other 4 >> d
29 29 $ hg add d
30 30 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
31 31
32 32 $ hg merge -q foo
33 33 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
34 34
35 35 Second branch starting at nullrev:
36 36
37 37 $ hg update null
38 38 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
39 39 $ echo second > second
40 40 $ hg add second
41 41 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
42 42 created new head
43 43
44 44 $ echo third > third
45 45 $ hg add third
46 46 $ hg mv second fourth
47 47 $ hg commit -m third -d "2020-01-01 10:01"
48 48
49 49 Make sure user/global hgrc does not affect tests
50 50
51 51 $ echo '[ui]' > .hg/hgrc
52 52 $ echo 'logtemplate =' >> .hg/hgrc
53 53 $ echo 'style =' >> .hg/hgrc
54 54
55 55 Add some simple styles to settings
56 56
57 57 $ cat <<'EOF' >> .hg/hgrc
58 58 > [templates]
59 59 > simple = "{rev}\n"
60 60 > simple2 = {rev}\n
61 61 > rev = "should not precede {rev} keyword\n"
62 62 > EOF
63 63
64 64 $ hg log -l1 -Tsimple
65 65 8
66 66 $ hg log -l1 -Tsimple2
67 67 8
68 68 $ hg log -l1 -Trev
69 69 should not precede 8 keyword
70 70 $ hg log -l1 -T '{simple}'
71 71 8
72 72
73 73 Map file shouldn't see user templates:
74 74
75 75 $ cat <<EOF > tmpl
76 76 > changeset = 'nothing expanded:{simple}\n'
77 77 > EOF
78 78 $ hg log -l1 --style ./tmpl
79 79 nothing expanded:
80 80
81 81 Test templates and style maps in files:
82 82
83 83 $ echo "{rev}" > tmpl
84 84 $ hg log -l1 -T./tmpl
85 85 8
86 86 $ hg log -l1 -Tblah/blah
87 87 blah/blah (no-eol)
88 88
89 89 $ printf 'changeset = "{rev}\\n"\n' > map-simple
90 90 $ hg log -l1 -T./map-simple
91 91 8
92 92
93 93 a map file may have [templates] and [templatealias] sections:
94 94
95 95 $ cat <<'EOF' > map-simple
96 96 > [templates]
97 97 > changeset = "{a}\n"
98 98 > [templatealias]
99 99 > a = rev
100 100 > EOF
101 101 $ hg log -l1 -T./map-simple
102 102 8
103 103
104 104 so it can be included in hgrc
105 105
106 106 $ cat <<EOF > myhgrc
107 107 > %include $HGRCPATH
108 108 > %include map-simple
109 109 > [templates]
110 110 > foo = "{changeset}"
111 111 > EOF
112 112 $ HGRCPATH=./myhgrc hg log -l1 -Tfoo
113 113 8
114 114 $ HGRCPATH=./myhgrc hg log -l1 -T'{a}\n'
115 115 8
116 116
117 117 Test template map inheritance
118 118
119 119 $ echo "__base__ = map-cmdline.default" > map-simple
120 120 $ printf 'cset = "changeset: ***{rev}***\\n"\n' >> map-simple
121 121 $ hg log -l1 -T./map-simple
122 122 changeset: ***8***
123 123 tag: tip
124 124 user: test
125 125 date: Wed Jan 01 10:01:00 2020 +0000
126 126 summary: third
127 127
128 128
129 129 Test docheader, docfooter and separator in template map
130 130
131 131 $ cat <<'EOF' > map-myjson
132 132 > docheader = '\{\n'
133 133 > docfooter = '\n}\n'
134 134 > separator = ',\n'
135 135 > changeset = ' {dict(rev, node|short)|json}'
136 136 > EOF
137 137 $ hg log -l2 -T./map-myjson
138 138 {
139 139 {"node": "95c24699272e", "rev": 8},
140 140 {"node": "29114dbae42b", "rev": 7}
141 141 }
142 142
143 143 Test docheader, docfooter and separator in [templates] section
144 144
145 145 $ cat <<'EOF' >> .hg/hgrc
146 146 > [templates]
147 147 > myjson = ' {dict(rev, node|short)|json}'
148 148 > myjson:docheader = '\{\n'
149 149 > myjson:docfooter = '\n}\n'
150 150 > myjson:separator = ',\n'
151 151 > :docheader = 'should not be selected as a docheader for literal templates\n'
152 152 > EOF
153 153 $ hg log -l2 -Tmyjson
154 154 {
155 155 {"node": "95c24699272e", "rev": 8},
156 156 {"node": "29114dbae42b", "rev": 7}
157 157 }
158 158 $ hg log -l1 -T'{rev}\n'
159 159 8
160 160
161 161 Template should precede style option
162 162
163 163 $ hg log -l1 --style default -T '{rev}\n'
164 164 8
165 165
166 166 Add a commit with empty description, to ensure that the templates
167 167 below will omit the description line.
168 168
169 169 $ echo c >> c
170 170 $ hg add c
171 171 $ hg commit -qm ' '
172 172
173 173 Default style is like normal output. Phases style should be the same
174 174 as default style, except for extra phase lines.
175 175
176 176 $ hg log > log.out
177 177 $ hg log --style default > style.out
178 178 $ cmp log.out style.out || diff -u log.out style.out
179 179 $ hg log -T phases > phases.out
180 180 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
181 181 +phase: draft
182 182 +phase: draft
183 183 +phase: draft
184 184 +phase: draft
185 185 +phase: draft
186 186 +phase: draft
187 187 +phase: draft
188 188 +phase: draft
189 189 +phase: draft
190 190 +phase: draft
191 191
192 192 $ hg log -v > log.out
193 193 $ hg log -v --style default > style.out
194 194 $ cmp log.out style.out || diff -u log.out style.out
195 195 $ hg log -v -T phases > phases.out
196 196 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
197 197 +phase: draft
198 198 +phase: draft
199 199 +phase: draft
200 200 +phase: draft
201 201 +phase: draft
202 202 +phase: draft
203 203 +phase: draft
204 204 +phase: draft
205 205 +phase: draft
206 206 +phase: draft
207 207
208 208 $ hg log -q > log.out
209 209 $ hg log -q --style default > style.out
210 210 $ cmp log.out style.out || diff -u log.out style.out
211 211 $ hg log -q -T phases > phases.out
212 212 $ cmp log.out phases.out || diff -u log.out phases.out
213 213
214 214 $ hg log --debug > log.out
215 215 $ hg log --debug --style default > style.out
216 216 $ cmp log.out style.out || diff -u log.out style.out
217 217 $ hg log --debug -T phases > phases.out
218 218 $ cmp log.out phases.out || diff -u log.out phases.out
219 219
220 220 Default style of working-directory revision should also be the same (but
221 221 date may change while running tests):
222 222
223 223 $ hg log -r 'wdir()' | sed 's|^date:.*|date:|' > log.out
224 224 $ hg log -r 'wdir()' --style default | sed 's|^date:.*|date:|' > style.out
225 225 $ cmp log.out style.out || diff -u log.out style.out
226 226
227 227 $ hg log -r 'wdir()' -v | sed 's|^date:.*|date:|' > log.out
228 228 $ hg log -r 'wdir()' -v --style default | sed 's|^date:.*|date:|' > style.out
229 229 $ cmp log.out style.out || diff -u log.out style.out
230 230
231 231 $ hg log -r 'wdir()' -q > log.out
232 232 $ hg log -r 'wdir()' -q --style default > style.out
233 233 $ cmp log.out style.out || diff -u log.out style.out
234 234
235 235 $ hg log -r 'wdir()' --debug | sed 's|^date:.*|date:|' > log.out
236 236 $ hg log -r 'wdir()' --debug --style default \
237 237 > | sed 's|^date:.*|date:|' > style.out
238 238 $ cmp log.out style.out || diff -u log.out style.out
239 239
240 240 Default style should also preserve color information (issue2866):
241 241
242 242 $ cp $HGRCPATH $HGRCPATH-bak
243 243 $ cat <<EOF >> $HGRCPATH
244 244 > [extensions]
245 245 > color=
246 246 > EOF
247 247
248 248 $ hg --color=debug log > log.out
249 249 $ hg --color=debug log --style default > style.out
250 250 $ cmp log.out style.out || diff -u log.out style.out
251 251 $ hg --color=debug log -T phases > phases.out
252 252 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
253 253 +[log.phase|phase: draft]
254 254 +[log.phase|phase: draft]
255 255 +[log.phase|phase: draft]
256 256 +[log.phase|phase: draft]
257 257 +[log.phase|phase: draft]
258 258 +[log.phase|phase: draft]
259 259 +[log.phase|phase: draft]
260 260 +[log.phase|phase: draft]
261 261 +[log.phase|phase: draft]
262 262 +[log.phase|phase: draft]
263 263
264 264 $ hg --color=debug -v log > log.out
265 265 $ hg --color=debug -v log --style default > style.out
266 266 $ cmp log.out style.out || diff -u log.out style.out
267 267 $ hg --color=debug -v log -T phases > phases.out
268 268 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
269 269 +[log.phase|phase: draft]
270 270 +[log.phase|phase: draft]
271 271 +[log.phase|phase: draft]
272 272 +[log.phase|phase: draft]
273 273 +[log.phase|phase: draft]
274 274 +[log.phase|phase: draft]
275 275 +[log.phase|phase: draft]
276 276 +[log.phase|phase: draft]
277 277 +[log.phase|phase: draft]
278 278 +[log.phase|phase: draft]
279 279
280 280 $ hg --color=debug -q log > log.out
281 281 $ hg --color=debug -q log --style default > style.out
282 282 $ cmp log.out style.out || diff -u log.out style.out
283 283 $ hg --color=debug -q log -T phases > phases.out
284 284 $ cmp log.out phases.out || diff -u log.out phases.out
285 285
286 286 $ hg --color=debug --debug log > log.out
287 287 $ hg --color=debug --debug log --style default > style.out
288 288 $ cmp log.out style.out || diff -u log.out style.out
289 289 $ hg --color=debug --debug log -T phases > phases.out
290 290 $ cmp log.out phases.out || diff -u log.out phases.out
291 291
292 292 $ mv $HGRCPATH-bak $HGRCPATH
293 293
294 294 Remove commit with empty commit message, so as to not pollute further
295 295 tests.
296 296
297 297 $ hg --config extensions.strip= strip -q .
298 298
299 299 Revision with no copies (used to print a traceback):
300 300
301 301 $ hg tip -v --template '\n'
302 302
303 303
304 304 Compact style works:
305 305
306 306 $ hg log -Tcompact
307 307 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
308 308 third
309 309
310 310 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
311 311 second
312 312
313 313 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
314 314 merge
315 315
316 316 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
317 317 new head
318 318
319 319 4 bbe44766e73d 1970-01-17 04:53 +0000 person
320 320 new branch
321 321
322 322 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
323 323 no user, no domain
324 324
325 325 2 97054abb4ab8 1970-01-14 21:20 +0000 other
326 326 no person
327 327
328 328 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
329 329 other 1
330 330
331 331 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
332 332 line 1
333 333
334 334
335 335 $ hg log -v --style compact
336 336 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
337 337 third
338 338
339 339 7:-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
340 340 second
341 341
342 342 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
343 343 merge
344 344
345 345 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
346 346 new head
347 347
348 348 4 bbe44766e73d 1970-01-17 04:53 +0000 person
349 349 new branch
350 350
351 351 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
352 352 no user, no domain
353 353
354 354 2 97054abb4ab8 1970-01-14 21:20 +0000 other@place
355 355 no person
356 356
357 357 1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
358 358 other 1
359 359 other 2
360 360
361 361 other 3
362 362
363 363 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
364 364 line 1
365 365 line 2
366 366
367 367
368 368 $ hg log --debug --style compact
369 369 8[tip]:7,-1 95c24699272e 2020-01-01 10:01 +0000 test
370 370 third
371 371
372 372 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
373 373 second
374 374
375 375 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
376 376 merge
377 377
378 378 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person
379 379 new head
380 380
381 381 4:3,-1 bbe44766e73d 1970-01-17 04:53 +0000 person
382 382 new branch
383 383
384 384 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
385 385 no user, no domain
386 386
387 387 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other@place
388 388 no person
389 389
390 390 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
391 391 other 1
392 392 other 2
393 393
394 394 other 3
395 395
396 396 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
397 397 line 1
398 398 line 2
399 399
400 400
401 401 Test xml styles:
402 402
403 403 $ hg log --style xml -r 'not all()'
404 404 <?xml version="1.0"?>
405 405 <log>
406 406 </log>
407 407
408 408 $ hg log --style xml
409 409 <?xml version="1.0"?>
410 410 <log>
411 411 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
412 412 <tag>tip</tag>
413 413 <author email="test">test</author>
414 414 <date>2020-01-01T10:01:00+00:00</date>
415 415 <msg xml:space="preserve">third</msg>
416 416 </logentry>
417 417 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
418 418 <parent revision="-1" node="0000000000000000000000000000000000000000" />
419 419 <author email="user@hostname">User Name</author>
420 420 <date>1970-01-12T13:46:40+00:00</date>
421 421 <msg xml:space="preserve">second</msg>
422 422 </logentry>
423 423 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
424 424 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
425 425 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
426 426 <author email="person">person</author>
427 427 <date>1970-01-18T08:40:01+00:00</date>
428 428 <msg xml:space="preserve">merge</msg>
429 429 </logentry>
430 430 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
431 431 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
432 432 <author email="person">person</author>
433 433 <date>1970-01-18T08:40:00+00:00</date>
434 434 <msg xml:space="preserve">new head</msg>
435 435 </logentry>
436 436 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
437 437 <branch>foo</branch>
438 438 <author email="person">person</author>
439 439 <date>1970-01-17T04:53:20+00:00</date>
440 440 <msg xml:space="preserve">new branch</msg>
441 441 </logentry>
442 442 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
443 443 <author email="person">person</author>
444 444 <date>1970-01-16T01:06:40+00:00</date>
445 445 <msg xml:space="preserve">no user, no domain</msg>
446 446 </logentry>
447 447 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
448 448 <author email="other@place">other</author>
449 449 <date>1970-01-14T21:20:00+00:00</date>
450 450 <msg xml:space="preserve">no person</msg>
451 451 </logentry>
452 452 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
453 453 <author email="other@place">A. N. Other</author>
454 454 <date>1970-01-13T17:33:20+00:00</date>
455 455 <msg xml:space="preserve">other 1
456 456 other 2
457 457
458 458 other 3</msg>
459 459 </logentry>
460 460 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
461 461 <author email="user@hostname">User Name</author>
462 462 <date>1970-01-12T13:46:40+00:00</date>
463 463 <msg xml:space="preserve">line 1
464 464 line 2</msg>
465 465 </logentry>
466 466 </log>
467 467
468 468 $ hg log -v --style xml
469 469 <?xml version="1.0"?>
470 470 <log>
471 471 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
472 472 <tag>tip</tag>
473 473 <author email="test">test</author>
474 474 <date>2020-01-01T10:01:00+00:00</date>
475 475 <msg xml:space="preserve">third</msg>
476 476 <paths>
477 477 <path action="A">fourth</path>
478 478 <path action="A">third</path>
479 479 <path action="R">second</path>
480 480 </paths>
481 481 <copies>
482 482 <copy source="second">fourth</copy>
483 483 </copies>
484 484 </logentry>
485 485 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
486 486 <parent revision="-1" node="0000000000000000000000000000000000000000" />
487 487 <author email="user@hostname">User Name</author>
488 488 <date>1970-01-12T13:46:40+00:00</date>
489 489 <msg xml:space="preserve">second</msg>
490 490 <paths>
491 491 <path action="A">second</path>
492 492 </paths>
493 493 </logentry>
494 494 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
495 495 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
496 496 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
497 497 <author email="person">person</author>
498 498 <date>1970-01-18T08:40:01+00:00</date>
499 499 <msg xml:space="preserve">merge</msg>
500 500 <paths>
501 501 </paths>
502 502 </logentry>
503 503 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
504 504 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
505 505 <author email="person">person</author>
506 506 <date>1970-01-18T08:40:00+00:00</date>
507 507 <msg xml:space="preserve">new head</msg>
508 508 <paths>
509 509 <path action="A">d</path>
510 510 </paths>
511 511 </logentry>
512 512 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
513 513 <branch>foo</branch>
514 514 <author email="person">person</author>
515 515 <date>1970-01-17T04:53:20+00:00</date>
516 516 <msg xml:space="preserve">new branch</msg>
517 517 <paths>
518 518 </paths>
519 519 </logentry>
520 520 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
521 521 <author email="person">person</author>
522 522 <date>1970-01-16T01:06:40+00:00</date>
523 523 <msg xml:space="preserve">no user, no domain</msg>
524 524 <paths>
525 525 <path action="M">c</path>
526 526 </paths>
527 527 </logentry>
528 528 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
529 529 <author email="other@place">other</author>
530 530 <date>1970-01-14T21:20:00+00:00</date>
531 531 <msg xml:space="preserve">no person</msg>
532 532 <paths>
533 533 <path action="A">c</path>
534 534 </paths>
535 535 </logentry>
536 536 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
537 537 <author email="other@place">A. N. Other</author>
538 538 <date>1970-01-13T17:33:20+00:00</date>
539 539 <msg xml:space="preserve">other 1
540 540 other 2
541 541
542 542 other 3</msg>
543 543 <paths>
544 544 <path action="A">b</path>
545 545 </paths>
546 546 </logentry>
547 547 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
548 548 <author email="user@hostname">User Name</author>
549 549 <date>1970-01-12T13:46:40+00:00</date>
550 550 <msg xml:space="preserve">line 1
551 551 line 2</msg>
552 552 <paths>
553 553 <path action="A">a</path>
554 554 </paths>
555 555 </logentry>
556 556 </log>
557 557
558 558 $ hg log --debug --style xml
559 559 <?xml version="1.0"?>
560 560 <log>
561 561 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
562 562 <tag>tip</tag>
563 563 <parent revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453" />
564 564 <parent revision="-1" node="0000000000000000000000000000000000000000" />
565 565 <author email="test">test</author>
566 566 <date>2020-01-01T10:01:00+00:00</date>
567 567 <msg xml:space="preserve">third</msg>
568 568 <paths>
569 569 <path action="A">fourth</path>
570 570 <path action="A">third</path>
571 571 <path action="R">second</path>
572 572 </paths>
573 573 <copies>
574 574 <copy source="second">fourth</copy>
575 575 </copies>
576 576 <extra key="branch">default</extra>
577 577 </logentry>
578 578 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
579 579 <parent revision="-1" node="0000000000000000000000000000000000000000" />
580 580 <parent revision="-1" node="0000000000000000000000000000000000000000" />
581 581 <author email="user@hostname">User Name</author>
582 582 <date>1970-01-12T13:46:40+00:00</date>
583 583 <msg xml:space="preserve">second</msg>
584 584 <paths>
585 585 <path action="A">second</path>
586 586 </paths>
587 587 <extra key="branch">default</extra>
588 588 </logentry>
589 589 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
590 590 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
591 591 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
592 592 <author email="person">person</author>
593 593 <date>1970-01-18T08:40:01+00:00</date>
594 594 <msg xml:space="preserve">merge</msg>
595 595 <paths>
596 596 </paths>
597 597 <extra key="branch">default</extra>
598 598 </logentry>
599 599 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
600 600 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
601 601 <parent revision="-1" node="0000000000000000000000000000000000000000" />
602 602 <author email="person">person</author>
603 603 <date>1970-01-18T08:40:00+00:00</date>
604 604 <msg xml:space="preserve">new head</msg>
605 605 <paths>
606 606 <path action="A">d</path>
607 607 </paths>
608 608 <extra key="branch">default</extra>
609 609 </logentry>
610 610 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
611 611 <branch>foo</branch>
612 612 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
613 613 <parent revision="-1" node="0000000000000000000000000000000000000000" />
614 614 <author email="person">person</author>
615 615 <date>1970-01-17T04:53:20+00:00</date>
616 616 <msg xml:space="preserve">new branch</msg>
617 617 <paths>
618 618 </paths>
619 619 <extra key="branch">foo</extra>
620 620 </logentry>
621 621 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
622 622 <parent revision="2" node="97054abb4ab824450e9164180baf491ae0078465" />
623 623 <parent revision="-1" node="0000000000000000000000000000000000000000" />
624 624 <author email="person">person</author>
625 625 <date>1970-01-16T01:06:40+00:00</date>
626 626 <msg xml:space="preserve">no user, no domain</msg>
627 627 <paths>
628 628 <path action="M">c</path>
629 629 </paths>
630 630 <extra key="branch">default</extra>
631 631 </logentry>
632 632 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
633 633 <parent revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965" />
634 634 <parent revision="-1" node="0000000000000000000000000000000000000000" />
635 635 <author email="other@place">other</author>
636 636 <date>1970-01-14T21:20:00+00:00</date>
637 637 <msg xml:space="preserve">no person</msg>
638 638 <paths>
639 639 <path action="A">c</path>
640 640 </paths>
641 641 <extra key="branch">default</extra>
642 642 </logentry>
643 643 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
644 644 <parent revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f" />
645 645 <parent revision="-1" node="0000000000000000000000000000000000000000" />
646 646 <author email="other@place">A. N. Other</author>
647 647 <date>1970-01-13T17:33:20+00:00</date>
648 648 <msg xml:space="preserve">other 1
649 649 other 2
650 650
651 651 other 3</msg>
652 652 <paths>
653 653 <path action="A">b</path>
654 654 </paths>
655 655 <extra key="branch">default</extra>
656 656 </logentry>
657 657 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
658 658 <parent revision="-1" node="0000000000000000000000000000000000000000" />
659 659 <parent revision="-1" node="0000000000000000000000000000000000000000" />
660 660 <author email="user@hostname">User Name</author>
661 661 <date>1970-01-12T13:46:40+00:00</date>
662 662 <msg xml:space="preserve">line 1
663 663 line 2</msg>
664 664 <paths>
665 665 <path action="A">a</path>
666 666 </paths>
667 667 <extra key="branch">default</extra>
668 668 </logentry>
669 669 </log>
670 670
671 671
672 672 test CBOR style:
673 673
674 674 $ cat <<'EOF' > "$TESTTMP/decodecborarray.py"
675 675 > from __future__ import absolute_import
676 676 > from mercurial import (
677 677 > dispatch,
678 678 > pycompat,
679 679 > )
680 680 > from mercurial.utils import (
681 681 > cborutil,
682 682 > stringutil,
683 683 > )
684 684 > dispatch.initstdio()
685 685 > data = pycompat.stdin.read()
686 686 > # our CBOR decoder doesn't support parsing indefinite-length arrays,
687 687 > # but the log output is indefinite stream by nature.
688 688 > assert data[:1] == cborutil.BEGIN_INDEFINITE_ARRAY
689 689 > assert data[-1:] == cborutil.BREAK
690 690 > items = cborutil.decodeall(data[1:-1])
691 691 > pycompat.stdout.write(stringutil.pprint(items, indent=1) + b'\n')
692 692 > EOF
693 693
694 694 $ hg log -k nosuch -Tcbor | "$PYTHON" "$TESTTMP/decodecborarray.py"
695 695 []
696 696
697 697 $ hg log -qr0:1 -Tcbor | "$PYTHON" "$TESTTMP/decodecborarray.py"
698 698 [
699 699 {
700 700 'node': '1e4e1b8f71e05681d422154f5421e385fec3454f',
701 701 'rev': 0
702 702 },
703 703 {
704 704 'node': 'b608e9d1a3f0273ccf70fb85fd6866b3482bf965',
705 705 'rev': 1
706 706 }
707 707 ]
708 708
709 709 $ hg log -vpr . -Tcbor --stat | "$PYTHON" "$TESTTMP/decodecborarray.py"
710 710 [
711 711 {
712 712 'bookmarks': [],
713 713 'branch': 'default',
714 714 'date': [
715 715 1577872860,
716 716 0
717 717 ],
718 718 'desc': 'third',
719 719 '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',
720 720 'diffstat': ' fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n',
721 721 'files': [
722 722 'fourth',
723 723 'second',
724 724 'third'
725 725 ],
726 726 'node': '95c24699272ef57d062b8bccc32c878bf841784a',
727 727 'parents': [
728 728 '29114dbae42b9f078cf2714dbe3a86bba8ec7453'
729 729 ],
730 730 'phase': 'draft',
731 731 'rev': 8,
732 732 'tags': [
733 733 'tip'
734 734 ],
735 735 'user': 'test'
736 736 }
737 737 ]
738 738
739 739
740 740 Test JSON style:
741 741
742 742 $ hg log -k nosuch -Tjson
743 743 [
744 744 ]
745 745
746 746 $ hg log -qr . -Tjson
747 747 [
748 748 {
749 749 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
750 750 "rev": 8
751 751 }
752 752 ]
753 753
754 754 $ hg log -vpr . -Tjson --stat
755 755 [
756 756 {
757 757 "bookmarks": [],
758 758 "branch": "default",
759 759 "date": [1577872860, 0],
760 760 "desc": "third",
761 761 "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",
762 762 "diffstat": " fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n",
763 763 "files": ["fourth", "second", "third"],
764 764 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
765 765 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
766 766 "phase": "draft",
767 767 "rev": 8,
768 768 "tags": ["tip"],
769 769 "user": "test"
770 770 }
771 771 ]
772 772
773 773 honor --git but not format-breaking diffopts
774 774 $ hg --config diff.noprefix=True log --git -vpr . -Tjson
775 775 [
776 776 {
777 777 "bookmarks": [],
778 778 "branch": "default",
779 779 "date": [1577872860, 0],
780 780 "desc": "third",
781 781 "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",
782 782 "files": ["fourth", "second", "third"],
783 783 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
784 784 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
785 785 "phase": "draft",
786 786 "rev": 8,
787 787 "tags": ["tip"],
788 788 "user": "test"
789 789 }
790 790 ]
791 791
792 792 $ hg log -T json
793 793 [
794 794 {
795 795 "bookmarks": [],
796 796 "branch": "default",
797 797 "date": [1577872860, 0],
798 798 "desc": "third",
799 799 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
800 800 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
801 801 "phase": "draft",
802 802 "rev": 8,
803 803 "tags": ["tip"],
804 804 "user": "test"
805 805 },
806 806 {
807 807 "bookmarks": [],
808 808 "branch": "default",
809 809 "date": [1000000, 0],
810 810 "desc": "second",
811 811 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
812 812 "parents": ["0000000000000000000000000000000000000000"],
813 813 "phase": "draft",
814 814 "rev": 7,
815 815 "tags": [],
816 816 "user": "User Name <user@hostname>"
817 817 },
818 818 {
819 819 "bookmarks": [],
820 820 "branch": "default",
821 821 "date": [1500001, 0],
822 822 "desc": "merge",
823 823 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
824 824 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
825 825 "phase": "draft",
826 826 "rev": 6,
827 827 "tags": [],
828 828 "user": "person"
829 829 },
830 830 {
831 831 "bookmarks": [],
832 832 "branch": "default",
833 833 "date": [1500000, 0],
834 834 "desc": "new head",
835 835 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
836 836 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
837 837 "phase": "draft",
838 838 "rev": 5,
839 839 "tags": [],
840 840 "user": "person"
841 841 },
842 842 {
843 843 "bookmarks": [],
844 844 "branch": "foo",
845 845 "date": [1400000, 0],
846 846 "desc": "new branch",
847 847 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
848 848 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
849 849 "phase": "draft",
850 850 "rev": 4,
851 851 "tags": [],
852 852 "user": "person"
853 853 },
854 854 {
855 855 "bookmarks": [],
856 856 "branch": "default",
857 857 "date": [1300000, 0],
858 858 "desc": "no user, no domain",
859 859 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
860 860 "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
861 861 "phase": "draft",
862 862 "rev": 3,
863 863 "tags": [],
864 864 "user": "person"
865 865 },
866 866 {
867 867 "bookmarks": [],
868 868 "branch": "default",
869 869 "date": [1200000, 0],
870 870 "desc": "no person",
871 871 "node": "97054abb4ab824450e9164180baf491ae0078465",
872 872 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
873 873 "phase": "draft",
874 874 "rev": 2,
875 875 "tags": [],
876 876 "user": "other@place"
877 877 },
878 878 {
879 879 "bookmarks": [],
880 880 "branch": "default",
881 881 "date": [1100000, 0],
882 882 "desc": "other 1\nother 2\n\nother 3",
883 883 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
884 884 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
885 885 "phase": "draft",
886 886 "rev": 1,
887 887 "tags": [],
888 888 "user": "A. N. Other <other@place>"
889 889 },
890 890 {
891 891 "bookmarks": [],
892 892 "branch": "default",
893 893 "date": [1000000, 0],
894 894 "desc": "line 1\nline 2",
895 895 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
896 896 "parents": ["0000000000000000000000000000000000000000"],
897 897 "phase": "draft",
898 898 "rev": 0,
899 899 "tags": [],
900 900 "user": "User Name <user@hostname>"
901 901 }
902 902 ]
903 903
904 904 $ hg heads -v -Tjson
905 905 [
906 906 {
907 907 "bookmarks": [],
908 908 "branch": "default",
909 909 "date": [1577872860, 0],
910 910 "desc": "third",
911 911 "files": ["fourth", "second", "third"],
912 912 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
913 913 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
914 914 "phase": "draft",
915 915 "rev": 8,
916 916 "tags": ["tip"],
917 917 "user": "test"
918 918 },
919 919 {
920 920 "bookmarks": [],
921 921 "branch": "default",
922 922 "date": [1500001, 0],
923 923 "desc": "merge",
924 924 "files": [],
925 925 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
926 926 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
927 927 "phase": "draft",
928 928 "rev": 6,
929 929 "tags": [],
930 930 "user": "person"
931 931 },
932 932 {
933 933 "bookmarks": [],
934 934 "branch": "foo",
935 935 "date": [1400000, 0],
936 936 "desc": "new branch",
937 937 "files": [],
938 938 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
939 939 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
940 940 "phase": "draft",
941 941 "rev": 4,
942 942 "tags": [],
943 943 "user": "person"
944 944 }
945 945 ]
946 946
947 947 $ hg log --debug -Tjson
948 948 [
949 949 {
950 950 "added": ["fourth", "third"],
951 951 "bookmarks": [],
952 952 "branch": "default",
953 953 "date": [1577872860, 0],
954 954 "desc": "third",
955 955 "extra": {"branch": "default"},
956 956 "manifest": "94961b75a2da554b4df6fb599e5bfc7d48de0c64",
957 957 "modified": [],
958 958 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
959 959 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
960 960 "phase": "draft",
961 961 "removed": ["second"],
962 962 "rev": 8,
963 963 "tags": ["tip"],
964 964 "user": "test"
965 965 },
966 966 {
967 967 "added": ["second"],
968 968 "bookmarks": [],
969 969 "branch": "default",
970 970 "date": [1000000, 0],
971 971 "desc": "second",
972 972 "extra": {"branch": "default"},
973 973 "manifest": "f2dbc354b94e5ec0b4f10680ee0cee816101d0bf",
974 974 "modified": [],
975 975 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
976 976 "parents": ["0000000000000000000000000000000000000000"],
977 977 "phase": "draft",
978 978 "removed": [],
979 979 "rev": 7,
980 980 "tags": [],
981 981 "user": "User Name <user@hostname>"
982 982 },
983 983 {
984 984 "added": [],
985 985 "bookmarks": [],
986 986 "branch": "default",
987 987 "date": [1500001, 0],
988 988 "desc": "merge",
989 989 "extra": {"branch": "default"},
990 990 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
991 991 "modified": [],
992 992 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
993 993 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
994 994 "phase": "draft",
995 995 "removed": [],
996 996 "rev": 6,
997 997 "tags": [],
998 998 "user": "person"
999 999 },
1000 1000 {
1001 1001 "added": ["d"],
1002 1002 "bookmarks": [],
1003 1003 "branch": "default",
1004 1004 "date": [1500000, 0],
1005 1005 "desc": "new head",
1006 1006 "extra": {"branch": "default"},
1007 1007 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
1008 1008 "modified": [],
1009 1009 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
1010 1010 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1011 1011 "phase": "draft",
1012 1012 "removed": [],
1013 1013 "rev": 5,
1014 1014 "tags": [],
1015 1015 "user": "person"
1016 1016 },
1017 1017 {
1018 1018 "added": [],
1019 1019 "bookmarks": [],
1020 1020 "branch": "foo",
1021 1021 "date": [1400000, 0],
1022 1022 "desc": "new branch",
1023 1023 "extra": {"branch": "foo"},
1024 1024 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
1025 1025 "modified": [],
1026 1026 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
1027 1027 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1028 1028 "phase": "draft",
1029 1029 "removed": [],
1030 1030 "rev": 4,
1031 1031 "tags": [],
1032 1032 "user": "person"
1033 1033 },
1034 1034 {
1035 1035 "added": [],
1036 1036 "bookmarks": [],
1037 1037 "branch": "default",
1038 1038 "date": [1300000, 0],
1039 1039 "desc": "no user, no domain",
1040 1040 "extra": {"branch": "default"},
1041 1041 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
1042 1042 "modified": ["c"],
1043 1043 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
1044 1044 "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
1045 1045 "phase": "draft",
1046 1046 "removed": [],
1047 1047 "rev": 3,
1048 1048 "tags": [],
1049 1049 "user": "person"
1050 1050 },
1051 1051 {
1052 1052 "added": ["c"],
1053 1053 "bookmarks": [],
1054 1054 "branch": "default",
1055 1055 "date": [1200000, 0],
1056 1056 "desc": "no person",
1057 1057 "extra": {"branch": "default"},
1058 1058 "manifest": "6e0e82995c35d0d57a52aca8da4e56139e06b4b1",
1059 1059 "modified": [],
1060 1060 "node": "97054abb4ab824450e9164180baf491ae0078465",
1061 1061 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
1062 1062 "phase": "draft",
1063 1063 "removed": [],
1064 1064 "rev": 2,
1065 1065 "tags": [],
1066 1066 "user": "other@place"
1067 1067 },
1068 1068 {
1069 1069 "added": ["b"],
1070 1070 "bookmarks": [],
1071 1071 "branch": "default",
1072 1072 "date": [1100000, 0],
1073 1073 "desc": "other 1\nother 2\n\nother 3",
1074 1074 "extra": {"branch": "default"},
1075 1075 "manifest": "4e8d705b1e53e3f9375e0e60dc7b525d8211fe55",
1076 1076 "modified": [],
1077 1077 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
1078 1078 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
1079 1079 "phase": "draft",
1080 1080 "removed": [],
1081 1081 "rev": 1,
1082 1082 "tags": [],
1083 1083 "user": "A. N. Other <other@place>"
1084 1084 },
1085 1085 {
1086 1086 "added": ["a"],
1087 1087 "bookmarks": [],
1088 1088 "branch": "default",
1089 1089 "date": [1000000, 0],
1090 1090 "desc": "line 1\nline 2",
1091 1091 "extra": {"branch": "default"},
1092 1092 "manifest": "a0c8bcbbb45c63b90b70ad007bf38961f64f2af0",
1093 1093 "modified": [],
1094 1094 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
1095 1095 "parents": ["0000000000000000000000000000000000000000"],
1096 1096 "phase": "draft",
1097 1097 "removed": [],
1098 1098 "rev": 0,
1099 1099 "tags": [],
1100 1100 "user": "User Name <user@hostname>"
1101 1101 }
1102 1102 ]
1103 1103
1104 Other unsupported formatter styles:
1105
1106 $ hg log -qr . -Tpickle
1107 abort: "pickle" not in template map
1108 [255]
1109 $ hg log -qr . -Tdebug
1110 abort: "debug" not in template map
1111 [255]
1112
1104 1113 Error if style not readable:
1105 1114
1106 1115 #if unix-permissions no-root
1107 1116 $ touch q
1108 1117 $ chmod 0 q
1109 1118 $ hg log --style ./q
1110 1119 abort: Permission denied: './q'
1111 1120 [255]
1112 1121 #endif
1113 1122
1114 1123 Error if no style:
1115 1124
1116 1125 $ hg log --style notexist
1117 1126 abort: style 'notexist' not found
1118 1127 (available styles: bisect, changelog, compact, default, phases, show, status, xml)
1119 1128 [255]
1120 1129
1121 1130 $ hg log -T list
1122 1131 available styles: bisect, changelog, compact, default, phases, show, status, xml
1123 1132 abort: specify a template
1124 1133 [255]
1125 1134
1126 1135 Error if style missing key:
1127 1136
1128 1137 $ echo 'q = q' > t
1129 1138 $ hg log --style ./t
1130 1139 abort: "changeset" not in template map
1131 1140 [255]
1132 1141
1133 1142 Error if style missing value:
1134 1143
1135 1144 $ echo 'changeset =' > t
1136 1145 $ hg log --style t
1137 1146 hg: parse error at t:1: missing value
1138 1147 [255]
1139 1148
1140 1149 Error if include fails:
1141 1150
1142 1151 $ echo 'changeset = q' >> t
1143 1152 #if unix-permissions no-root
1144 1153 $ hg log --style ./t
1145 1154 abort: template file ./q: Permission denied
1146 1155 [255]
1147 1156 $ rm -f q
1148 1157 #endif
1149 1158
1150 1159 Include works:
1151 1160
1152 1161 $ echo '{rev}' > q
1153 1162 $ hg log --style ./t
1154 1163 8
1155 1164 7
1156 1165 6
1157 1166 5
1158 1167 4
1159 1168 3
1160 1169 2
1161 1170 1
1162 1171 0
1163 1172
1164 1173 $ hg phase -r 5 --public
1165 1174 $ hg phase -r 7 --secret --force
1166 1175
1167 1176 Missing non-standard names give no error (backward compatibility):
1168 1177
1169 1178 $ echo "changeset = '{c}'" > t
1170 1179 $ hg log --style ./t
1171 1180
1172 1181 Defining non-standard name works:
1173 1182
1174 1183 $ cat <<EOF > t
1175 1184 > changeset = '{c}'
1176 1185 > c = q
1177 1186 > EOF
1178 1187 $ hg log --style ./t
1179 1188 8
1180 1189 7
1181 1190 6
1182 1191 5
1183 1192 4
1184 1193 3
1185 1194 2
1186 1195 1
1187 1196 0
1188 1197
1189 1198 ui.style works:
1190 1199
1191 1200 $ echo '[ui]' > .hg/hgrc
1192 1201 $ echo 'style = t' >> .hg/hgrc
1193 1202 $ hg log
1194 1203 8
1195 1204 7
1196 1205 6
1197 1206 5
1198 1207 4
1199 1208 3
1200 1209 2
1201 1210 1
1202 1211 0
1203 1212
1204 1213 Issue338:
1205 1214
1206 1215 $ hg log --style=changelog > changelog
1207 1216
1208 1217 $ cat changelog
1209 1218 2020-01-01 test <test>
1210 1219
1211 1220 * fourth, second, third:
1212 1221 third
1213 1222 [95c24699272e] [tip]
1214 1223
1215 1224 1970-01-12 User Name <user@hostname>
1216 1225
1217 1226 * second:
1218 1227 second
1219 1228 [29114dbae42b]
1220 1229
1221 1230 1970-01-18 person <person>
1222 1231
1223 1232 * merge
1224 1233 [d41e714fe50d]
1225 1234
1226 1235 * d:
1227 1236 new head
1228 1237 [13207e5a10d9]
1229 1238
1230 1239 1970-01-17 person <person>
1231 1240
1232 1241 * new branch
1233 1242 [bbe44766e73d] <foo>
1234 1243
1235 1244 1970-01-16 person <person>
1236 1245
1237 1246 * c:
1238 1247 no user, no domain
1239 1248 [10e46f2dcbf4]
1240 1249
1241 1250 1970-01-14 other <other@place>
1242 1251
1243 1252 * c:
1244 1253 no person
1245 1254 [97054abb4ab8]
1246 1255
1247 1256 1970-01-13 A. N. Other <other@place>
1248 1257
1249 1258 * b:
1250 1259 other 1 other 2
1251 1260
1252 1261 other 3
1253 1262 [b608e9d1a3f0]
1254 1263
1255 1264 1970-01-12 User Name <user@hostname>
1256 1265
1257 1266 * a:
1258 1267 line 1 line 2
1259 1268 [1e4e1b8f71e0]
1260 1269
1261 1270
1262 1271 Issue2130: xml output for 'hg heads' is malformed
1263 1272
1264 1273 $ hg heads --style changelog
1265 1274 2020-01-01 test <test>
1266 1275
1267 1276 * fourth, second, third:
1268 1277 third
1269 1278 [95c24699272e] [tip]
1270 1279
1271 1280 1970-01-18 person <person>
1272 1281
1273 1282 * merge
1274 1283 [d41e714fe50d]
1275 1284
1276 1285 1970-01-17 person <person>
1277 1286
1278 1287 * new branch
1279 1288 [bbe44766e73d] <foo>
1280 1289
1281 1290
1282 1291 Add a dummy commit to make up for the instability of the above:
1283 1292
1284 1293 $ echo a > a
1285 1294 $ hg add a
1286 1295 $ hg ci -m future
1287 1296
1288 1297 Add a commit that does all possible modifications at once
1289 1298
1290 1299 $ echo modify >> third
1291 1300 $ touch b
1292 1301 $ hg add b
1293 1302 $ hg mv fourth fifth
1294 1303 $ hg rm a
1295 1304 $ hg ci -m "Modify, add, remove, rename"
1296 1305
1297 1306 Check the status template
1298 1307
1299 1308 $ cat <<EOF >> $HGRCPATH
1300 1309 > [extensions]
1301 1310 > color=
1302 1311 > EOF
1303 1312
1304 1313 $ hg log -T status -r 10
1305 1314 changeset: 10:0f9759ec227a
1306 1315 tag: tip
1307 1316 user: test
1308 1317 date: Thu Jan 01 00:00:00 1970 +0000
1309 1318 summary: Modify, add, remove, rename
1310 1319 files:
1311 1320 M third
1312 1321 A b
1313 1322 A fifth
1314 1323 R a
1315 1324 R fourth
1316 1325
1317 1326 $ hg log -T status -C -r 10
1318 1327 changeset: 10:0f9759ec227a
1319 1328 tag: tip
1320 1329 user: test
1321 1330 date: Thu Jan 01 00:00:00 1970 +0000
1322 1331 summary: Modify, add, remove, rename
1323 1332 files:
1324 1333 M third
1325 1334 A b
1326 1335 A fifth
1327 1336 fourth
1328 1337 R a
1329 1338 R fourth
1330 1339
1331 1340 $ hg log -T status -C -r 10 -v
1332 1341 changeset: 10:0f9759ec227a
1333 1342 tag: tip
1334 1343 user: test
1335 1344 date: Thu Jan 01 00:00:00 1970 +0000
1336 1345 description:
1337 1346 Modify, add, remove, rename
1338 1347
1339 1348 files:
1340 1349 M third
1341 1350 A b
1342 1351 A fifth
1343 1352 fourth
1344 1353 R a
1345 1354 R fourth
1346 1355
1347 1356 $ hg log -T status -C -r 10 --debug
1348 1357 changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c
1349 1358 tag: tip
1350 1359 phase: secret
1351 1360 parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066
1352 1361 parent: -1:0000000000000000000000000000000000000000
1353 1362 manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567
1354 1363 user: test
1355 1364 date: Thu Jan 01 00:00:00 1970 +0000
1356 1365 extra: branch=default
1357 1366 description:
1358 1367 Modify, add, remove, rename
1359 1368
1360 1369 files:
1361 1370 M third
1362 1371 A b
1363 1372 A fifth
1364 1373 fourth
1365 1374 R a
1366 1375 R fourth
1367 1376
1368 1377 $ hg log -T status -C -r 10 --quiet
1369 1378 10:0f9759ec227a
1370 1379 $ hg --color=debug log -T status -r 10
1371 1380 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
1372 1381 [log.tag|tag: tip]
1373 1382 [log.user|user: test]
1374 1383 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
1375 1384 [log.summary|summary: Modify, add, remove, rename]
1376 1385 [ui.note log.files|files:]
1377 1386 [status.modified|M third]
1378 1387 [status.added|A b]
1379 1388 [status.added|A fifth]
1380 1389 [status.removed|R a]
1381 1390 [status.removed|R fourth]
1382 1391
1383 1392 $ hg --color=debug log -T status -C -r 10
1384 1393 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
1385 1394 [log.tag|tag: tip]
1386 1395 [log.user|user: test]
1387 1396 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
1388 1397 [log.summary|summary: Modify, add, remove, rename]
1389 1398 [ui.note log.files|files:]
1390 1399 [status.modified|M third]
1391 1400 [status.added|A b]
1392 1401 [status.added|A fifth]
1393 1402 [status.copied| fourth]
1394 1403 [status.removed|R a]
1395 1404 [status.removed|R fourth]
1396 1405
1397 1406 $ hg --color=debug log -T status -C -r 10 -v
1398 1407 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
1399 1408 [log.tag|tag: tip]
1400 1409 [log.user|user: test]
1401 1410 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
1402 1411 [ui.note log.description|description:]
1403 1412 [ui.note log.description|Modify, add, remove, rename]
1404 1413
1405 1414 [ui.note log.files|files:]
1406 1415 [status.modified|M third]
1407 1416 [status.added|A b]
1408 1417 [status.added|A fifth]
1409 1418 [status.copied| fourth]
1410 1419 [status.removed|R a]
1411 1420 [status.removed|R fourth]
1412 1421
1413 1422 $ hg --color=debug log -T status -C -r 10 --debug
1414 1423 [log.changeset changeset.secret|changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c]
1415 1424 [log.tag|tag: tip]
1416 1425 [log.phase|phase: secret]
1417 1426 [log.parent changeset.secret|parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066]
1418 1427 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1419 1428 [ui.debug log.manifest|manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567]
1420 1429 [log.user|user: test]
1421 1430 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
1422 1431 [ui.debug log.extra|extra: branch=default]
1423 1432 [ui.note log.description|description:]
1424 1433 [ui.note log.description|Modify, add, remove, rename]
1425 1434
1426 1435 [ui.note log.files|files:]
1427 1436 [status.modified|M third]
1428 1437 [status.added|A b]
1429 1438 [status.added|A fifth]
1430 1439 [status.copied| fourth]
1431 1440 [status.removed|R a]
1432 1441 [status.removed|R fourth]
1433 1442
1434 1443 $ hg --color=debug log -T status -C -r 10 --quiet
1435 1444 [log.node|10:0f9759ec227a]
1436 1445
1437 1446 Check the bisect template
1438 1447
1439 1448 $ hg bisect -g 1
1440 1449 $ hg bisect -b 3 --noupdate
1441 1450 Testing changeset 2:97054abb4ab8 (2 changesets remaining, ~1 tests)
1442 1451 $ hg log -T bisect -r 0:4
1443 1452 changeset: 0:1e4e1b8f71e0
1444 1453 bisect: good (implicit)
1445 1454 user: User Name <user@hostname>
1446 1455 date: Mon Jan 12 13:46:40 1970 +0000
1447 1456 summary: line 1
1448 1457
1449 1458 changeset: 1:b608e9d1a3f0
1450 1459 bisect: good
1451 1460 user: A. N. Other <other@place>
1452 1461 date: Tue Jan 13 17:33:20 1970 +0000
1453 1462 summary: other 1
1454 1463
1455 1464 changeset: 2:97054abb4ab8
1456 1465 bisect: untested
1457 1466 user: other@place
1458 1467 date: Wed Jan 14 21:20:00 1970 +0000
1459 1468 summary: no person
1460 1469
1461 1470 changeset: 3:10e46f2dcbf4
1462 1471 bisect: bad
1463 1472 user: person
1464 1473 date: Fri Jan 16 01:06:40 1970 +0000
1465 1474 summary: no user, no domain
1466 1475
1467 1476 changeset: 4:bbe44766e73d
1468 1477 bisect: bad (implicit)
1469 1478 branch: foo
1470 1479 user: person
1471 1480 date: Sat Jan 17 04:53:20 1970 +0000
1472 1481 summary: new branch
1473 1482
1474 1483 $ hg log --debug -T bisect -r 0:4
1475 1484 changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
1476 1485 bisect: good (implicit)
1477 1486 phase: public
1478 1487 parent: -1:0000000000000000000000000000000000000000
1479 1488 parent: -1:0000000000000000000000000000000000000000
1480 1489 manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
1481 1490 user: User Name <user@hostname>
1482 1491 date: Mon Jan 12 13:46:40 1970 +0000
1483 1492 files+: a
1484 1493 extra: branch=default
1485 1494 description:
1486 1495 line 1
1487 1496 line 2
1488 1497
1489 1498
1490 1499 changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1491 1500 bisect: good
1492 1501 phase: public
1493 1502 parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
1494 1503 parent: -1:0000000000000000000000000000000000000000
1495 1504 manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
1496 1505 user: A. N. Other <other@place>
1497 1506 date: Tue Jan 13 17:33:20 1970 +0000
1498 1507 files+: b
1499 1508 extra: branch=default
1500 1509 description:
1501 1510 other 1
1502 1511 other 2
1503 1512
1504 1513 other 3
1505 1514
1506 1515
1507 1516 changeset: 2:97054abb4ab824450e9164180baf491ae0078465
1508 1517 bisect: untested
1509 1518 phase: public
1510 1519 parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1511 1520 parent: -1:0000000000000000000000000000000000000000
1512 1521 manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
1513 1522 user: other@place
1514 1523 date: Wed Jan 14 21:20:00 1970 +0000
1515 1524 files+: c
1516 1525 extra: branch=default
1517 1526 description:
1518 1527 no person
1519 1528
1520 1529
1521 1530 changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
1522 1531 bisect: bad
1523 1532 phase: public
1524 1533 parent: 2:97054abb4ab824450e9164180baf491ae0078465
1525 1534 parent: -1:0000000000000000000000000000000000000000
1526 1535 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1527 1536 user: person
1528 1537 date: Fri Jan 16 01:06:40 1970 +0000
1529 1538 files: c
1530 1539 extra: branch=default
1531 1540 description:
1532 1541 no user, no domain
1533 1542
1534 1543
1535 1544 changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1536 1545 bisect: bad (implicit)
1537 1546 branch: foo
1538 1547 phase: draft
1539 1548 parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
1540 1549 parent: -1:0000000000000000000000000000000000000000
1541 1550 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1542 1551 user: person
1543 1552 date: Sat Jan 17 04:53:20 1970 +0000
1544 1553 extra: branch=foo
1545 1554 description:
1546 1555 new branch
1547 1556
1548 1557
1549 1558 $ hg log -v -T bisect -r 0:4
1550 1559 changeset: 0:1e4e1b8f71e0
1551 1560 bisect: good (implicit)
1552 1561 user: User Name <user@hostname>
1553 1562 date: Mon Jan 12 13:46:40 1970 +0000
1554 1563 files: a
1555 1564 description:
1556 1565 line 1
1557 1566 line 2
1558 1567
1559 1568
1560 1569 changeset: 1:b608e9d1a3f0
1561 1570 bisect: good
1562 1571 user: A. N. Other <other@place>
1563 1572 date: Tue Jan 13 17:33:20 1970 +0000
1564 1573 files: b
1565 1574 description:
1566 1575 other 1
1567 1576 other 2
1568 1577
1569 1578 other 3
1570 1579
1571 1580
1572 1581 changeset: 2:97054abb4ab8
1573 1582 bisect: untested
1574 1583 user: other@place
1575 1584 date: Wed Jan 14 21:20:00 1970 +0000
1576 1585 files: c
1577 1586 description:
1578 1587 no person
1579 1588
1580 1589
1581 1590 changeset: 3:10e46f2dcbf4
1582 1591 bisect: bad
1583 1592 user: person
1584 1593 date: Fri Jan 16 01:06:40 1970 +0000
1585 1594 files: c
1586 1595 description:
1587 1596 no user, no domain
1588 1597
1589 1598
1590 1599 changeset: 4:bbe44766e73d
1591 1600 bisect: bad (implicit)
1592 1601 branch: foo
1593 1602 user: person
1594 1603 date: Sat Jan 17 04:53:20 1970 +0000
1595 1604 description:
1596 1605 new branch
1597 1606
1598 1607
1599 1608 $ hg --color=debug log -T bisect -r 0:4
1600 1609 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
1601 1610 [log.bisect bisect.good|bisect: good (implicit)]
1602 1611 [log.user|user: User Name <user@hostname>]
1603 1612 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
1604 1613 [log.summary|summary: line 1]
1605 1614
1606 1615 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
1607 1616 [log.bisect bisect.good|bisect: good]
1608 1617 [log.user|user: A. N. Other <other@place>]
1609 1618 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
1610 1619 [log.summary|summary: other 1]
1611 1620
1612 1621 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
1613 1622 [log.bisect bisect.untested|bisect: untested]
1614 1623 [log.user|user: other@place]
1615 1624 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
1616 1625 [log.summary|summary: no person]
1617 1626
1618 1627 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
1619 1628 [log.bisect bisect.bad|bisect: bad]
1620 1629 [log.user|user: person]
1621 1630 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
1622 1631 [log.summary|summary: no user, no domain]
1623 1632
1624 1633 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
1625 1634 [log.bisect bisect.bad|bisect: bad (implicit)]
1626 1635 [log.branch|branch: foo]
1627 1636 [log.user|user: person]
1628 1637 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
1629 1638 [log.summary|summary: new branch]
1630 1639
1631 1640 $ hg --color=debug log --debug -T bisect -r 0:4
1632 1641 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
1633 1642 [log.bisect bisect.good|bisect: good (implicit)]
1634 1643 [log.phase|phase: public]
1635 1644 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1636 1645 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1637 1646 [ui.debug log.manifest|manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0]
1638 1647 [log.user|user: User Name <user@hostname>]
1639 1648 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
1640 1649 [ui.debug log.files|files+: a]
1641 1650 [ui.debug log.extra|extra: branch=default]
1642 1651 [ui.note log.description|description:]
1643 1652 [ui.note log.description|line 1
1644 1653 line 2]
1645 1654
1646 1655
1647 1656 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
1648 1657 [log.bisect bisect.good|bisect: good]
1649 1658 [log.phase|phase: public]
1650 1659 [log.parent changeset.public|parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
1651 1660 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1652 1661 [ui.debug log.manifest|manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55]
1653 1662 [log.user|user: A. N. Other <other@place>]
1654 1663 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
1655 1664 [ui.debug log.files|files+: b]
1656 1665 [ui.debug log.extra|extra: branch=default]
1657 1666 [ui.note log.description|description:]
1658 1667 [ui.note log.description|other 1
1659 1668 other 2
1660 1669
1661 1670 other 3]
1662 1671
1663 1672
1664 1673 [log.changeset changeset.public|changeset: 2:97054abb4ab824450e9164180baf491ae0078465]
1665 1674 [log.bisect bisect.untested|bisect: untested]
1666 1675 [log.phase|phase: public]
1667 1676 [log.parent changeset.public|parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
1668 1677 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1669 1678 [ui.debug log.manifest|manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1]
1670 1679 [log.user|user: other@place]
1671 1680 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
1672 1681 [ui.debug log.files|files+: c]
1673 1682 [ui.debug log.extra|extra: branch=default]
1674 1683 [ui.note log.description|description:]
1675 1684 [ui.note log.description|no person]
1676 1685
1677 1686
1678 1687 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
1679 1688 [log.bisect bisect.bad|bisect: bad]
1680 1689 [log.phase|phase: public]
1681 1690 [log.parent changeset.public|parent: 2:97054abb4ab824450e9164180baf491ae0078465]
1682 1691 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1683 1692 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
1684 1693 [log.user|user: person]
1685 1694 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
1686 1695 [ui.debug log.files|files: c]
1687 1696 [ui.debug log.extra|extra: branch=default]
1688 1697 [ui.note log.description|description:]
1689 1698 [ui.note log.description|no user, no domain]
1690 1699
1691 1700
1692 1701 [log.changeset changeset.draft|changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74]
1693 1702 [log.bisect bisect.bad|bisect: bad (implicit)]
1694 1703 [log.branch|branch: foo]
1695 1704 [log.phase|phase: draft]
1696 1705 [log.parent changeset.public|parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
1697 1706 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1698 1707 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
1699 1708 [log.user|user: person]
1700 1709 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
1701 1710 [ui.debug log.extra|extra: branch=foo]
1702 1711 [ui.note log.description|description:]
1703 1712 [ui.note log.description|new branch]
1704 1713
1705 1714
1706 1715 $ hg --color=debug log -v -T bisect -r 0:4
1707 1716 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
1708 1717 [log.bisect bisect.good|bisect: good (implicit)]
1709 1718 [log.user|user: User Name <user@hostname>]
1710 1719 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
1711 1720 [ui.note log.files|files: a]
1712 1721 [ui.note log.description|description:]
1713 1722 [ui.note log.description|line 1
1714 1723 line 2]
1715 1724
1716 1725
1717 1726 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
1718 1727 [log.bisect bisect.good|bisect: good]
1719 1728 [log.user|user: A. N. Other <other@place>]
1720 1729 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
1721 1730 [ui.note log.files|files: b]
1722 1731 [ui.note log.description|description:]
1723 1732 [ui.note log.description|other 1
1724 1733 other 2
1725 1734
1726 1735 other 3]
1727 1736
1728 1737
1729 1738 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
1730 1739 [log.bisect bisect.untested|bisect: untested]
1731 1740 [log.user|user: other@place]
1732 1741 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
1733 1742 [ui.note log.files|files: c]
1734 1743 [ui.note log.description|description:]
1735 1744 [ui.note log.description|no person]
1736 1745
1737 1746
1738 1747 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
1739 1748 [log.bisect bisect.bad|bisect: bad]
1740 1749 [log.user|user: person]
1741 1750 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
1742 1751 [ui.note log.files|files: c]
1743 1752 [ui.note log.description|description:]
1744 1753 [ui.note log.description|no user, no domain]
1745 1754
1746 1755
1747 1756 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
1748 1757 [log.bisect bisect.bad|bisect: bad (implicit)]
1749 1758 [log.branch|branch: foo]
1750 1759 [log.user|user: person]
1751 1760 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
1752 1761 [ui.note log.description|description:]
1753 1762 [ui.note log.description|new branch]
1754 1763
1755 1764
1756 1765 $ hg bisect --reset
1757 1766
1758 1767 $ cd ..
1759 1768
1760 1769 Set up latesttag repository:
1761 1770
1762 1771 $ hg init latesttag
1763 1772 $ cd latesttag
1764 1773
1765 1774 $ echo a > file
1766 1775 $ hg ci -Am a -d '0 0'
1767 1776 adding file
1768 1777
1769 1778 $ echo b >> file
1770 1779 $ hg ci -m b -d '1 0'
1771 1780
1772 1781 $ echo c >> head1
1773 1782 $ hg ci -Am h1c -d '2 0'
1774 1783 adding head1
1775 1784
1776 1785 $ hg update -q 1
1777 1786 $ echo d >> head2
1778 1787 $ hg ci -Am h2d -d '3 0'
1779 1788 adding head2
1780 1789 created new head
1781 1790
1782 1791 $ echo e >> head2
1783 1792 $ hg ci -m h2e -d '4 0'
1784 1793
1785 1794 $ hg merge -q
1786 1795 $ hg ci -m merge -d '5 -3600'
1787 1796
1788 1797 $ hg tag -r 1 -m t1 -d '6 0' t1
1789 1798 $ hg tag -r 2 -m t2 -d '7 0' t2
1790 1799 $ hg tag -r 3 -m t3 -d '8 0' t3
1791 1800 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
1792 1801 $ hg tag -r 5 -m t5 -d '9 0' t5
1793 1802 $ hg tag -r 3 -m at3 -d '10 0' at3
1794 1803
1795 1804 $ cd ..
1796 1805
1797 1806 Style path expansion: issue1948 - ui.style option doesn't work on OSX
1798 1807 if it is a relative path
1799 1808
1800 1809 $ mkdir -p home/styles
1801 1810
1802 1811 $ cat > home/styles/teststyle <<EOF
1803 1812 > changeset = 'test {rev}:{node|short}\n'
1804 1813 > EOF
1805 1814
1806 1815 $ HOME=`pwd`/home; export HOME
1807 1816
1808 1817 $ cat > latesttag/.hg/hgrc <<EOF
1809 1818 > [ui]
1810 1819 > style = ~/styles/teststyle
1811 1820 > EOF
1812 1821
1813 1822 $ hg -R latesttag tip
1814 1823 test 11:97e5943b523a
1815 1824
1816 1825 Test recursive showlist template (issue1989):
1817 1826
1818 1827 $ cat > style1989 <<EOF
1819 1828 > changeset = '{file_mods}{manifest}{extras}'
1820 1829 > file_mod = 'M|{author|person}\n'
1821 1830 > manifest = '{rev},{author}\n'
1822 1831 > extra = '{key}: {author}\n'
1823 1832 > EOF
1824 1833
1825 1834 $ hg -R latesttag log -r tip --style=style1989
1826 1835 M|test
1827 1836 11,
1828 1837 branch: test
General Comments 0
You need to be logged in to leave comments. Login now