##// END OF EJS Templates
merge with stable
Matt Mackall -
r20670:0084fcd5 merge default
parent child Browse files
Show More
@@ -1,572 +1,570 b''
1 1 # color.py color output for the status and qseries commands
2 2 #
3 3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.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 '''colorize output from some commands
9 9
10 10 This extension modifies the status and resolve commands to add color
11 11 to their output to reflect file status, the qseries command to add
12 12 color to reflect patch status (applied, unapplied, missing), and to
13 13 diff-related commands to highlight additions, removals, diff headers,
14 14 and trailing whitespace.
15 15
16 16 Other effects in addition to color, like bold and underlined text, are
17 17 also available. By default, the terminfo database is used to find the
18 18 terminal codes used to change color and effect. If terminfo is not
19 19 available, then effects are rendered with the ECMA-48 SGR control
20 20 function (aka ANSI escape codes).
21 21
22 22 Default effects may be overridden from your configuration file::
23 23
24 24 [color]
25 25 status.modified = blue bold underline red_background
26 26 status.added = green bold
27 27 status.removed = red bold blue_background
28 28 status.deleted = cyan bold underline
29 29 status.unknown = magenta bold underline
30 30 status.ignored = black bold
31 31
32 32 # 'none' turns off all effects
33 33 status.clean = none
34 34 status.copied = none
35 35
36 36 qseries.applied = blue bold underline
37 37 qseries.unapplied = black bold
38 38 qseries.missing = red bold
39 39
40 40 diff.diffline = bold
41 41 diff.extended = cyan bold
42 42 diff.file_a = red bold
43 43 diff.file_b = green bold
44 44 diff.hunk = magenta
45 45 diff.deleted = red
46 46 diff.inserted = green
47 47 diff.changed = white
48 48 diff.trailingwhitespace = bold red_background
49 49
50 50 resolve.unresolved = red bold
51 51 resolve.resolved = green bold
52 52
53 53 bookmarks.current = green
54 54
55 55 branches.active = none
56 56 branches.closed = black bold
57 57 branches.current = green
58 58 branches.inactive = none
59 59
60 60 tags.normal = green
61 61 tags.local = black bold
62 62
63 63 rebase.rebased = blue
64 64 rebase.remaining = red bold
65 65
66 66 shelve.age = cyan
67 67 shelve.newest = green bold
68 68 shelve.name = blue bold
69 69
70 70 histedit.remaining = red bold
71 71
72 72 The available effects in terminfo mode are 'blink', 'bold', 'dim',
73 73 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
74 74 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
75 75 'underline'. How each is rendered depends on the terminal emulator.
76 76 Some may not be available for a given terminal type, and will be
77 77 silently ignored.
78 78
79 79 Note that on some systems, terminfo mode may cause problems when using
80 80 color with the pager extension and less -R. less with the -R option
81 81 will only display ECMA-48 color codes, and terminfo mode may sometimes
82 82 emit codes that less doesn't understand. You can work around this by
83 83 either using ansi mode (or auto mode), or by using less -r (which will
84 84 pass through all terminal control codes, not just color control
85 85 codes).
86 86
87 87 Because there are only eight standard colors, this module allows you
88 88 to define color names for other color slots which might be available
89 89 for your terminal type, assuming terminfo mode. For instance::
90 90
91 91 color.brightblue = 12
92 92 color.pink = 207
93 93 color.orange = 202
94 94
95 95 to set 'brightblue' to color slot 12 (useful for 16 color terminals
96 96 that have brighter colors defined in the upper eight) and, 'pink' and
97 97 'orange' to colors in 256-color xterm's default color cube. These
98 98 defined colors may then be used as any of the pre-defined eight,
99 99 including appending '_background' to set the background to that color.
100 100
101 101 By default, the color extension will use ANSI mode (or win32 mode on
102 102 Windows) if it detects a terminal. To override auto mode (to enable
103 103 terminfo mode, for example), set the following configuration option::
104 104
105 105 [color]
106 106 mode = terminfo
107 107
108 108 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
109 109 disable color.
110 110 '''
111 111
112 112 import os
113 113
114 114 from mercurial import commands, dispatch, extensions, ui as uimod, util
115 115 from mercurial import templater, error
116 116 from mercurial.i18n import _
117 117
118 118 testedwith = 'internal'
119 119
120 120 # start and stop parameters for effects
121 121 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
122 122 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
123 123 'italic': 3, 'underline': 4, 'inverse': 7,
124 124 'black_background': 40, 'red_background': 41,
125 125 'green_background': 42, 'yellow_background': 43,
126 126 'blue_background': 44, 'purple_background': 45,
127 127 'cyan_background': 46, 'white_background': 47}
128 128
129 129 def _terminfosetup(ui, mode):
130 130 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
131 131
132 132 global _terminfo_params
133 133 # If we failed to load curses, we go ahead and return.
134 134 if not _terminfo_params:
135 135 return
136 136 # Otherwise, see what the config file says.
137 137 if mode not in ('auto', 'terminfo'):
138 138 return
139 139
140 140 _terminfo_params.update((key[6:], (False, int(val)))
141 141 for key, val in ui.configitems('color')
142 142 if key.startswith('color.'))
143 143
144 144 try:
145 145 curses.setupterm()
146 146 except curses.error, e:
147 147 _terminfo_params = {}
148 148 return
149 149
150 150 for key, (b, e) in _terminfo_params.items():
151 151 if not b:
152 152 continue
153 153 if not curses.tigetstr(e):
154 154 # Most terminals don't support dim, invis, etc, so don't be
155 155 # noisy and use ui.debug().
156 156 ui.debug("no terminfo entry for %s\n" % e)
157 157 del _terminfo_params[key]
158 158 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
159 159 # Only warn about missing terminfo entries if we explicitly asked for
160 160 # terminfo mode.
161 161 if mode == "terminfo":
162 162 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
163 163 "ECMA-48 color\n"))
164 164 _terminfo_params = {}
165 165
166 166 def _modesetup(ui, coloropt):
167 167 global _terminfo_params
168 168
169 169 auto = coloropt == 'auto'
170 170 always = not auto and util.parsebool(coloropt)
171 171 if not always and not auto:
172 172 return None
173 173
174 174 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
175 175
176 176 mode = ui.config('color', 'mode', 'auto')
177 177 realmode = mode
178 178 if mode == 'auto':
179 179 if os.name == 'nt' and 'TERM' not in os.environ:
180 180 # looks line a cmd.exe console, use win32 API or nothing
181 181 realmode = 'win32'
182 182 else:
183 183 realmode = 'ansi'
184 184
185 185 if realmode == 'win32':
186 186 _terminfo_params = {}
187 187 if not w32effects:
188 188 if mode == 'win32':
189 189 # only warn if color.mode is explicitly set to win32
190 190 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
191 191 return None
192 192 _effects.update(w32effects)
193 193 elif realmode == 'ansi':
194 194 _terminfo_params = {}
195 195 elif realmode == 'terminfo':
196 196 _terminfosetup(ui, mode)
197 197 if not _terminfo_params:
198 198 if mode == 'terminfo':
199 199 ## FIXME Shouldn't we return None in this case too?
200 200 # only warn if color.mode is explicitly set to win32
201 201 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
202 202 realmode = 'ansi'
203 203 else:
204 204 return None
205 205
206 206 if always or (auto and formatted):
207 207 return realmode
208 208 return None
209 209
210 210 try:
211 211 import curses
212 212 # Mapping from effect name to terminfo attribute name or color number.
213 213 # This will also force-load the curses module.
214 214 _terminfo_params = {'none': (True, 'sgr0'),
215 215 'standout': (True, 'smso'),
216 216 'underline': (True, 'smul'),
217 217 'reverse': (True, 'rev'),
218 218 'inverse': (True, 'rev'),
219 219 'blink': (True, 'blink'),
220 220 'dim': (True, 'dim'),
221 221 'bold': (True, 'bold'),
222 222 'invisible': (True, 'invis'),
223 223 'italic': (True, 'sitm'),
224 224 'black': (False, curses.COLOR_BLACK),
225 225 'red': (False, curses.COLOR_RED),
226 226 'green': (False, curses.COLOR_GREEN),
227 227 'yellow': (False, curses.COLOR_YELLOW),
228 228 'blue': (False, curses.COLOR_BLUE),
229 229 'magenta': (False, curses.COLOR_MAGENTA),
230 230 'cyan': (False, curses.COLOR_CYAN),
231 231 'white': (False, curses.COLOR_WHITE)}
232 232 except ImportError:
233 233 _terminfo_params = False
234 234
235 235 _styles = {'grep.match': 'red bold',
236 236 'grep.linenumber': 'green',
237 237 'grep.rev': 'green',
238 238 'grep.change': 'green',
239 239 'grep.sep': 'cyan',
240 240 'grep.filename': 'magenta',
241 241 'grep.user': 'magenta',
242 242 'grep.date': 'magenta',
243 243 'bookmarks.current': 'green',
244 244 'branches.active': 'none',
245 245 'branches.closed': 'black bold',
246 246 'branches.current': 'green',
247 247 'branches.inactive': 'none',
248 248 'diff.changed': 'white',
249 249 'diff.deleted': 'red',
250 250 'diff.diffline': 'bold',
251 251 'diff.extended': 'cyan bold',
252 252 'diff.file_a': 'red bold',
253 253 'diff.file_b': 'green bold',
254 254 'diff.hunk': 'magenta',
255 255 'diff.inserted': 'green',
256 256 'diff.trailingwhitespace': 'bold red_background',
257 257 'diffstat.deleted': 'red',
258 258 'diffstat.inserted': 'green',
259 259 'histedit.remaining': 'red bold',
260 260 'ui.prompt': 'yellow',
261 261 'log.changeset': 'yellow',
262 262 'rebase.rebased': 'blue',
263 263 'rebase.remaining': 'red bold',
264 264 'resolve.resolved': 'green bold',
265 265 'resolve.unresolved': 'red bold',
266 266 'shelve.age': 'cyan',
267 267 'shelve.newest': 'green bold',
268 268 'shelve.name': 'blue bold',
269 269 'status.added': 'green bold',
270 270 'status.clean': 'none',
271 271 'status.copied': 'none',
272 272 'status.deleted': 'cyan bold underline',
273 273 'status.ignored': 'black bold',
274 274 'status.modified': 'blue bold',
275 275 'status.removed': 'red bold',
276 276 'status.unknown': 'magenta bold underline',
277 277 'tags.normal': 'green',
278 278 'tags.local': 'black bold'}
279 279
280 280
281 281 def _effect_str(effect):
282 282 '''Helper function for render_effects().'''
283 283
284 284 bg = False
285 285 if effect.endswith('_background'):
286 286 bg = True
287 287 effect = effect[:-11]
288 288 attr, val = _terminfo_params[effect]
289 289 if attr:
290 290 return curses.tigetstr(val)
291 291 elif bg:
292 292 return curses.tparm(curses.tigetstr('setab'), val)
293 293 else:
294 294 return curses.tparm(curses.tigetstr('setaf'), val)
295 295
296 296 def render_effects(text, effects):
297 297 'Wrap text in commands to turn on each effect.'
298 298 if not text:
299 299 return text
300 300 if not _terminfo_params:
301 301 start = [str(_effects[e]) for e in ['none'] + effects.split()]
302 302 start = '\033[' + ';'.join(start) + 'm'
303 303 stop = '\033[' + str(_effects['none']) + 'm'
304 304 else:
305 305 start = ''.join(_effect_str(effect)
306 306 for effect in ['none'] + effects.split())
307 307 stop = _effect_str('none')
308 308 return ''.join([start, text, stop])
309 309
310 310 def extstyles():
311 311 for name, ext in extensions.extensions():
312 312 _styles.update(getattr(ext, 'colortable', {}))
313 313
314 314 def configstyles(ui):
315 315 for status, cfgeffects in ui.configitems('color'):
316 316 if '.' not in status or status.startswith('color.'):
317 317 continue
318 318 cfgeffects = ui.configlist('color', status)
319 319 if cfgeffects:
320 320 good = []
321 321 for e in cfgeffects:
322 322 if not _terminfo_params and e in _effects:
323 323 good.append(e)
324 324 elif e in _terminfo_params or e[:-11] in _terminfo_params:
325 325 good.append(e)
326 326 else:
327 327 ui.warn(_("ignoring unknown color/effect %r "
328 328 "(configured in color.%s)\n")
329 329 % (e, status))
330 330 _styles[status] = ' '.join(good)
331 331
332 332 class colorui(uimod.ui):
333 333 def popbuffer(self, labeled=False):
334 334 if self._colormode is None:
335 335 return super(colorui, self).popbuffer(labeled)
336 336
337 337 if labeled:
338 338 return ''.join(self.label(a, label) for a, label
339 339 in self._buffers.pop())
340 340 return ''.join(a for a, label in self._buffers.pop())
341 341
342 342 _colormode = 'ansi'
343 343 def write(self, *args, **opts):
344 344 if self._colormode is None:
345 345 return super(colorui, self).write(*args, **opts)
346 346
347 347 label = opts.get('label', '')
348 348 if self._buffers:
349 349 self._buffers[-1].extend([(str(a), label) for a in args])
350 350 elif self._colormode == 'win32':
351 351 for a in args:
352 352 win32print(a, super(colorui, self).write, **opts)
353 353 else:
354 354 return super(colorui, self).write(
355 355 *[self.label(str(a), label) for a in args], **opts)
356 356
357 357 def write_err(self, *args, **opts):
358 358 if self._colormode is None:
359 359 return super(colorui, self).write_err(*args, **opts)
360 360
361 361 label = opts.get('label', '')
362 362 if self._colormode == 'win32':
363 363 for a in args:
364 364 win32print(a, super(colorui, self).write_err, **opts)
365 365 else:
366 366 return super(colorui, self).write_err(
367 367 *[self.label(str(a), label) for a in args], **opts)
368 368
369 369 def label(self, msg, label):
370 370 if self._colormode is None:
371 371 return super(colorui, self).label(msg, label)
372 372
373 373 effects = []
374 374 for l in label.split():
375 375 s = _styles.get(l, '')
376 376 if s:
377 377 effects.append(s)
378 378 effects = ' '.join(effects)
379 379 if effects:
380 380 return '\n'.join([render_effects(s, effects)
381 381 for s in msg.split('\n')])
382 382 return msg
383 383
384 384 def templatelabel(context, mapping, args):
385 385 if len(args) != 2:
386 386 # i18n: "label" is a keyword
387 387 raise error.ParseError(_("label expects two arguments"))
388 388
389 389 thing = templater._evalifliteral(args[1], context, mapping)
390 390
391 391 # apparently, repo could be a string that is the favicon?
392 392 repo = mapping.get('repo', '')
393 393 if isinstance(repo, str):
394 394 return thing
395 395
396 label = templater.stringify(args[0][0](context, mapping, args[0][1]))
397 label = templater.runtemplate(context, mapping,
398 templater.compiletemplate(label, context))
396 label = templater._evalifliteral(args[0], context, mapping)
399 397
400 398 thing = templater.stringify(thing)
401 399 label = templater.stringify(label)
402 400
403 401 return repo.ui.label(thing, label)
404 402
405 403 def uisetup(ui):
406 404 if ui.plain():
407 405 return
408 406 if not isinstance(ui, colorui):
409 407 colorui.__bases__ = (ui.__class__,)
410 408 ui.__class__ = colorui
411 409 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
412 410 mode = _modesetup(ui_, opts['color'])
413 411 colorui._colormode = mode
414 412 if mode:
415 413 extstyles()
416 414 configstyles(ui_)
417 415 return orig(ui_, opts, cmd, cmdfunc)
418 416 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
419 417 templater.funcs['label'] = templatelabel
420 418
421 419 def extsetup(ui):
422 420 commands.globalopts.append(
423 421 ('', 'color', 'auto',
424 422 # i18n: 'always', 'auto', and 'never' are keywords and should
425 423 # not be translated
426 424 _("when to colorize (boolean, always, auto, or never)"),
427 425 _('TYPE')))
428 426
429 427 def debugcolor(ui, repo, **opts):
430 428 global _styles
431 429 _styles = {}
432 430 for effect in _effects.keys():
433 431 _styles[effect] = effect
434 432 ui.write(('colormode: %s\n') % ui._colormode)
435 433 ui.write(_('available colors:\n'))
436 434 for label, colors in _styles.items():
437 435 ui.write(('%s\n') % colors, label=label)
438 436
439 437 if os.name != 'nt':
440 438 w32effects = None
441 439 else:
442 440 import re, ctypes
443 441
444 442 _kernel32 = ctypes.windll.kernel32
445 443
446 444 _WORD = ctypes.c_ushort
447 445
448 446 _INVALID_HANDLE_VALUE = -1
449 447
450 448 class _COORD(ctypes.Structure):
451 449 _fields_ = [('X', ctypes.c_short),
452 450 ('Y', ctypes.c_short)]
453 451
454 452 class _SMALL_RECT(ctypes.Structure):
455 453 _fields_ = [('Left', ctypes.c_short),
456 454 ('Top', ctypes.c_short),
457 455 ('Right', ctypes.c_short),
458 456 ('Bottom', ctypes.c_short)]
459 457
460 458 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
461 459 _fields_ = [('dwSize', _COORD),
462 460 ('dwCursorPosition', _COORD),
463 461 ('wAttributes', _WORD),
464 462 ('srWindow', _SMALL_RECT),
465 463 ('dwMaximumWindowSize', _COORD)]
466 464
467 465 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
468 466 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
469 467
470 468 _FOREGROUND_BLUE = 0x0001
471 469 _FOREGROUND_GREEN = 0x0002
472 470 _FOREGROUND_RED = 0x0004
473 471 _FOREGROUND_INTENSITY = 0x0008
474 472
475 473 _BACKGROUND_BLUE = 0x0010
476 474 _BACKGROUND_GREEN = 0x0020
477 475 _BACKGROUND_RED = 0x0040
478 476 _BACKGROUND_INTENSITY = 0x0080
479 477
480 478 _COMMON_LVB_REVERSE_VIDEO = 0x4000
481 479 _COMMON_LVB_UNDERSCORE = 0x8000
482 480
483 481 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
484 482 w32effects = {
485 483 'none': -1,
486 484 'black': 0,
487 485 'red': _FOREGROUND_RED,
488 486 'green': _FOREGROUND_GREEN,
489 487 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
490 488 'blue': _FOREGROUND_BLUE,
491 489 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
492 490 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
493 491 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
494 492 'bold': _FOREGROUND_INTENSITY,
495 493 'black_background': 0x100, # unused value > 0x0f
496 494 'red_background': _BACKGROUND_RED,
497 495 'green_background': _BACKGROUND_GREEN,
498 496 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
499 497 'blue_background': _BACKGROUND_BLUE,
500 498 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
501 499 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
502 500 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
503 501 _BACKGROUND_BLUE),
504 502 'bold_background': _BACKGROUND_INTENSITY,
505 503 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
506 504 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
507 505 }
508 506
509 507 passthrough = set([_FOREGROUND_INTENSITY,
510 508 _BACKGROUND_INTENSITY,
511 509 _COMMON_LVB_UNDERSCORE,
512 510 _COMMON_LVB_REVERSE_VIDEO])
513 511
514 512 stdout = _kernel32.GetStdHandle(
515 513 _STD_OUTPUT_HANDLE) # don't close the handle returned
516 514 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
517 515 w32effects = None
518 516 else:
519 517 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
520 518 if not _kernel32.GetConsoleScreenBufferInfo(
521 519 stdout, ctypes.byref(csbi)):
522 520 # stdout may not support GetConsoleScreenBufferInfo()
523 521 # when called from subprocess or redirected
524 522 w32effects = None
525 523 else:
526 524 origattr = csbi.wAttributes
527 525 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
528 526 re.MULTILINE | re.DOTALL)
529 527
530 528 def win32print(text, orig, **opts):
531 529 label = opts.get('label', '')
532 530 attr = origattr
533 531
534 532 def mapcolor(val, attr):
535 533 if val == -1:
536 534 return origattr
537 535 elif val in passthrough:
538 536 return attr | val
539 537 elif val > 0x0f:
540 538 return (val & 0x70) | (attr & 0x8f)
541 539 else:
542 540 return (val & 0x07) | (attr & 0xf8)
543 541
544 542 # determine console attributes based on labels
545 543 for l in label.split():
546 544 style = _styles.get(l, '')
547 545 for effect in style.split():
548 546 attr = mapcolor(w32effects[effect], attr)
549 547
550 548 # hack to ensure regexp finds data
551 549 if not text.startswith('\033['):
552 550 text = '\033[m' + text
553 551
554 552 # Look for ANSI-like codes embedded in text
555 553 m = re.match(ansire, text)
556 554
557 555 try:
558 556 while m:
559 557 for sattr in m.group(1).split(';'):
560 558 if sattr:
561 559 attr = mapcolor(int(sattr), attr)
562 560 _kernel32.SetConsoleTextAttribute(stdout, attr)
563 561 orig(m.group(2), **opts)
564 562 m = re.match(ansire, m.group(3))
565 563 finally:
566 564 # Explicitly reset original attributes
567 565 _kernel32.SetConsoleTextAttribute(stdout, origattr)
568 566
569 567 cmdtable = {
570 568 'debugcolor':
571 569 (debugcolor, [], ('hg debugcolor'))
572 570 }
@@ -1,708 +1,707 b''
1 1 # templater.py - template expansion for output
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import sys, os, re
10 10 import util, config, templatefilters, templatekw, parser, error
11 11 import types
12 12 import minirst
13 13
14 14 # template parsing
15 15
16 16 elements = {
17 17 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
18 18 ",": (2, None, ("list", 2)),
19 19 "|": (5, None, ("|", 5)),
20 20 "%": (6, None, ("%", 6)),
21 21 ")": (0, None, None),
22 22 "symbol": (0, ("symbol",), None),
23 23 "string": (0, ("string",), None),
24 "rawstring": (0, ("rawstring",), None),
24 25 "end": (0, None, None),
25 26 }
26 27
27 28 def tokenizer(data):
28 29 program, start, end = data
29 30 pos = start
30 31 while pos < end:
31 32 c = program[pos]
32 33 if c.isspace(): # skip inter-token whitespace
33 34 pass
34 35 elif c in "(,)%|": # handle simple operators
35 36 yield (c, None, pos)
36 37 elif (c in '"\'' or c == 'r' and
37 38 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
38 39 if c == 'r':
39 40 pos += 1
40 41 c = program[pos]
41 42 decode = False
42 43 else:
43 44 decode = True
44 45 pos += 1
45 46 s = pos
46 47 while pos < end: # find closing quote
47 48 d = program[pos]
48 49 if decode and d == '\\': # skip over escaped characters
49 50 pos += 2
50 51 continue
51 52 if d == c:
52 53 if not decode:
53 yield ('string', program[s:pos].replace('\\', r'\\'), s)
54 yield ('rawstring', program[s:pos], s)
54 55 break
55 56 yield ('string', program[s:pos], s)
56 57 break
57 58 pos += 1
58 59 else:
59 60 raise error.ParseError(_("unterminated string"), s)
60 61 elif c.isalnum() or c in '_':
61 62 s = pos
62 63 pos += 1
63 64 while pos < end: # find end of symbol
64 65 d = program[pos]
65 66 if not (d.isalnum() or d == "_"):
66 67 break
67 68 pos += 1
68 69 sym = program[s:pos]
69 70 yield ('symbol', sym, s)
70 71 pos -= 1
71 72 elif c == '}':
72 73 pos += 1
73 74 break
74 75 else:
75 76 raise error.ParseError(_("syntax error"), pos)
76 77 pos += 1
77 78 yield ('end', None, pos)
78 79
79 def compiletemplate(tmpl, context):
80 def compiletemplate(tmpl, context, strtoken="string"):
80 81 parsed = []
81 82 pos, stop = 0, len(tmpl)
82 83 p = parser.parser(tokenizer, elements)
83 84 while pos < stop:
84 85 n = tmpl.find('{', pos)
85 86 if n < 0:
86 parsed.append(("string", tmpl[pos:].decode("string-escape")))
87 parsed.append((strtoken, tmpl[pos:]))
87 88 break
88 89 if n > 0 and tmpl[n - 1] == '\\':
89 90 # escaped
90 parsed.append(("string",
91 (tmpl[pos:n - 1] + "{").decode("string-escape")))
91 parsed.append((strtoken, (tmpl[pos:n - 1] + "{")))
92 92 pos = n + 1
93 93 continue
94 94 if n > pos:
95 parsed.append(("string", tmpl[pos:n].decode("string-escape")))
95 parsed.append((strtoken, tmpl[pos:n]))
96 96
97 97 pd = [tmpl, n + 1, stop]
98 98 parseres, pos = p.parse(pd)
99 99 parsed.append(parseres)
100 100
101 101 return [compileexp(e, context) for e in parsed]
102 102
103 103 def compileexp(exp, context):
104 104 t = exp[0]
105 105 if t in methods:
106 106 return methods[t](exp, context)
107 107 raise error.ParseError(_("unknown method '%s'") % t)
108 108
109 109 # template evaluation
110 110
111 111 def getsymbol(exp):
112 112 if exp[0] == 'symbol':
113 113 return exp[1]
114 114 raise error.ParseError(_("expected a symbol"))
115 115
116 116 def getlist(x):
117 117 if not x:
118 118 return []
119 119 if x[0] == 'list':
120 120 return getlist(x[1]) + [x[2]]
121 121 return [x]
122 122
123 123 def getfilter(exp, context):
124 124 f = getsymbol(exp)
125 125 if f not in context._filters:
126 126 raise error.ParseError(_("unknown function '%s'") % f)
127 127 return context._filters[f]
128 128
129 129 def gettemplate(exp, context):
130 if exp[0] == 'string':
131 return compiletemplate(exp[1], context)
130 if exp[0] == 'string' or exp[0] == 'rawstring':
131 return compiletemplate(exp[1], context, strtoken=exp[0])
132 132 if exp[0] == 'symbol':
133 133 return context._load(exp[1])
134 134 raise error.ParseError(_("expected template specifier"))
135 135
136 136 def runstring(context, mapping, data):
137 return data.decode("string-escape")
138
139 def runrawstring(context, mapping, data):
137 140 return data
138 141
139 142 def runsymbol(context, mapping, key):
140 143 v = mapping.get(key)
141 144 if v is None:
142 145 v = context._defaults.get(key)
143 146 if v is None:
144 147 try:
145 148 v = context.process(key, mapping)
146 149 except TemplateNotFound:
147 150 v = ''
148 151 if util.safehasattr(v, '__call__'):
149 152 return v(**mapping)
150 153 if isinstance(v, types.GeneratorType):
151 154 v = list(v)
152 155 mapping[key] = v
153 156 return v
154 157 return v
155 158
156 159 def buildfilter(exp, context):
157 160 func, data = compileexp(exp[1], context)
158 161 filt = getfilter(exp[2], context)
159 162 return (runfilter, (func, data, filt))
160 163
161 164 def runfilter(context, mapping, data):
162 165 func, data, filt = data
163 166 try:
164 167 return filt(func(context, mapping, data))
165 168 except (ValueError, AttributeError, TypeError):
166 169 if isinstance(data, tuple):
167 170 dt = data[1]
168 171 else:
169 172 dt = data
170 173 raise util.Abort(_("template filter '%s' is not compatible with "
171 174 "keyword '%s'") % (filt.func_name, dt))
172 175
173 176 def buildmap(exp, context):
174 177 func, data = compileexp(exp[1], context)
175 178 ctmpl = gettemplate(exp[2], context)
176 179 return (runmap, (func, data, ctmpl))
177 180
178 181 def runtemplate(context, mapping, template):
179 182 for func, data in template:
180 183 yield func(context, mapping, data)
181 184
182 185 def runmap(context, mapping, data):
183 186 func, data, ctmpl = data
184 187 d = func(context, mapping, data)
185 188 if util.safehasattr(d, '__call__'):
186 189 d = d()
187 190
188 191 lm = mapping.copy()
189 192
190 193 for i in d:
191 194 if isinstance(i, dict):
192 195 lm.update(i)
193 196 lm['originalnode'] = mapping.get('node')
194 197 yield runtemplate(context, lm, ctmpl)
195 198 else:
196 199 # v is not an iterable of dicts, this happen when 'key'
197 200 # has been fully expanded already and format is useless.
198 201 # If so, return the expanded value.
199 202 yield i
200 203
201 204 def buildfunc(exp, context):
202 205 n = getsymbol(exp[1])
203 206 args = [compileexp(x, context) for x in getlist(exp[2])]
204 207 if n in funcs:
205 208 f = funcs[n]
206 209 return (f, args)
207 210 if n in context._filters:
208 211 if len(args) != 1:
209 212 raise error.ParseError(_("filter %s expects one argument") % n)
210 213 f = context._filters[n]
211 214 return (runfilter, (args[0][0], args[0][1], f))
212 215
213 216 def date(context, mapping, args):
214 217 if not (1 <= len(args) <= 2):
215 218 raise error.ParseError(_("date expects one or two arguments"))
216 219
217 220 date = args[0][0](context, mapping, args[0][1])
218 221 if len(args) == 2:
219 222 fmt = stringify(args[1][0](context, mapping, args[1][1]))
220 223 return util.datestr(date, fmt)
221 224 return util.datestr(date)
222 225
223 226 def fill(context, mapping, args):
224 227 if not (1 <= len(args) <= 4):
225 228 raise error.ParseError(_("fill expects one to four arguments"))
226 229
227 230 text = stringify(args[0][0](context, mapping, args[0][1]))
228 231 width = 76
229 232 initindent = ''
230 233 hangindent = ''
231 234 if 2 <= len(args) <= 4:
232 235 try:
233 236 width = int(stringify(args[1][0](context, mapping, args[1][1])))
234 237 except ValueError:
235 238 raise error.ParseError(_("fill expects an integer width"))
236 239 try:
237 initindent = stringify(args[2][0](context, mapping, args[2][1]))
238 initindent = stringify(runtemplate(context, mapping,
239 compiletemplate(initindent, context)))
240 hangindent = stringify(args[3][0](context, mapping, args[3][1]))
241 hangindent = stringify(runtemplate(context, mapping,
242 compiletemplate(hangindent, context)))
240 initindent = stringify(_evalifliteral(args[2], context, mapping))
241 hangindent = stringify(_evalifliteral(args[3], context, mapping))
243 242 except IndexError:
244 243 pass
245 244
246 245 return templatefilters.fill(text, width, initindent, hangindent)
247 246
248 247 def pad(context, mapping, args):
249 248 """usage: pad(text, width, fillchar=' ', right=False)
250 249 """
251 250 if not (2 <= len(args) <= 4):
252 251 raise error.ParseError(_("pad() expects two to four arguments"))
253 252
254 253 width = int(args[1][1])
255 254
256 255 text = stringify(args[0][0](context, mapping, args[0][1]))
257 256 if args[0][0] == runstring:
258 257 text = stringify(runtemplate(context, mapping,
259 258 compiletemplate(text, context)))
260 259
261 260 right = False
262 261 fillchar = ' '
263 262 if len(args) > 2:
264 263 fillchar = stringify(args[2][0](context, mapping, args[2][1]))
265 264 if len(args) > 3:
266 265 right = util.parsebool(args[3][1])
267 266
268 267 if right:
269 268 return text.rjust(width, fillchar)
270 269 else:
271 270 return text.ljust(width, fillchar)
272 271
273 272 def get(context, mapping, args):
274 273 if len(args) != 2:
275 274 # i18n: "get" is a keyword
276 275 raise error.ParseError(_("get() expects two arguments"))
277 276
278 277 dictarg = args[0][0](context, mapping, args[0][1])
279 278 if not util.safehasattr(dictarg, 'get'):
280 279 # i18n: "get" is a keyword
281 280 raise error.ParseError(_("get() expects a dict as first argument"))
282 281
283 282 key = args[1][0](context, mapping, args[1][1])
284 283 yield dictarg.get(key)
285 284
286 285 def _evalifliteral(arg, context, mapping):
287 286 t = stringify(arg[0](context, mapping, arg[1]))
288 if arg[0] == runstring:
289 yield runtemplate(context, mapping, compiletemplate(t, context))
287 if arg[0] == runstring or arg[0] == runrawstring:
288 yield runtemplate(context, mapping,
289 compiletemplate(t, context, strtoken='rawstring'))
290 290 else:
291 291 yield t
292 292
293 293 def if_(context, mapping, args):
294 294 if not (2 <= len(args) <= 3):
295 295 # i18n: "if" is a keyword
296 296 raise error.ParseError(_("if expects two or three arguments"))
297 297
298 298 test = stringify(args[0][0](context, mapping, args[0][1]))
299 299 if test:
300 300 yield _evalifliteral(args[1], context, mapping)
301 301 elif len(args) == 3:
302 302 yield _evalifliteral(args[2], context, mapping)
303 303
304 304 def ifcontains(context, mapping, args):
305 305 if not (3 <= len(args) <= 4):
306 306 # i18n: "ifcontains" is a keyword
307 307 raise error.ParseError(_("ifcontains expects three or four arguments"))
308 308
309 309 item = stringify(args[0][0](context, mapping, args[0][1]))
310 310 items = args[1][0](context, mapping, args[1][1])
311 311
312 312 if item in items:
313 313 yield _evalifliteral(args[2], context, mapping)
314 314 elif len(args) == 4:
315 315 yield _evalifliteral(args[3], context, mapping)
316 316
317 317 def ifeq(context, mapping, args):
318 318 if not (3 <= len(args) <= 4):
319 319 # i18n: "ifeq" is a keyword
320 320 raise error.ParseError(_("ifeq expects three or four arguments"))
321 321
322 322 test = stringify(args[0][0](context, mapping, args[0][1]))
323 323 match = stringify(args[1][0](context, mapping, args[1][1]))
324 324 if test == match:
325 325 yield _evalifliteral(args[2], context, mapping)
326 326 elif len(args) == 4:
327 327 yield _evalifliteral(args[3], context, mapping)
328 328
329 329 def join(context, mapping, args):
330 330 if not (1 <= len(args) <= 2):
331 331 # i18n: "join" is a keyword
332 332 raise error.ParseError(_("join expects one or two arguments"))
333 333
334 334 joinset = args[0][0](context, mapping, args[0][1])
335 335 if util.safehasattr(joinset, '__call__'):
336 336 jf = joinset.joinfmt
337 337 joinset = [jf(x) for x in joinset()]
338 338
339 339 joiner = " "
340 340 if len(args) > 1:
341 joiner = args[1][0](context, mapping, args[1][1])
341 joiner = stringify(args[1][0](context, mapping, args[1][1]))
342 342
343 343 first = True
344 344 for x in joinset:
345 345 if first:
346 346 first = False
347 347 else:
348 348 yield joiner
349 349 yield x
350 350
351 351 def label(context, mapping, args):
352 352 if len(args) != 2:
353 353 # i18n: "label" is a keyword
354 354 raise error.ParseError(_("label expects two arguments"))
355 355
356 356 # ignore args[0] (the label string) since this is supposed to be a a no-op
357 357 yield _evalifliteral(args[1], context, mapping)
358 358
359 359 def revset(context, mapping, args):
360 360 """usage: revset(query[, formatargs...])
361 361 """
362 362 if not len(args) > 0:
363 363 # i18n: "revset" is a keyword
364 364 raise error.ParseError(_("revset expects one or more arguments"))
365 365
366 366 raw = args[0][1]
367 367 ctx = mapping['ctx']
368 368 repo = ctx._repo
369 369
370 370 if len(args) > 1:
371 371 formatargs = list([a[0](context, mapping, a[1]) for a in args[1:]])
372 372 revs = repo.revs(raw, *formatargs)
373 373 revs = list([str(r) for r in revs])
374 374 else:
375 375 revsetcache = mapping['cache'].setdefault("revsetcache", {})
376 376 if raw in revsetcache:
377 377 revs = revsetcache[raw]
378 378 else:
379 379 revs = repo.revs(raw)
380 380 revs = list([str(r) for r in revs])
381 381 revsetcache[raw] = revs
382 382
383 383 return templatekw.showlist("revision", revs, **mapping)
384 384
385 385 def rstdoc(context, mapping, args):
386 386 if len(args) != 2:
387 387 # i18n: "rstdoc" is a keyword
388 388 raise error.ParseError(_("rstdoc expects two arguments"))
389 389
390 390 text = stringify(args[0][0](context, mapping, args[0][1]))
391 391 style = stringify(args[1][0](context, mapping, args[1][1]))
392 392
393 393 return minirst.format(text, style=style, keep=['verbose'])
394 394
395 395 def shortest(context, mapping, args):
396 396 """usage: shortest(node, minlength=4)
397 397 """
398 398 if not (1 <= len(args) <= 2):
399 399 raise error.ParseError(_("shortest() expects one or two arguments"))
400 400
401 401 node = stringify(args[0][0](context, mapping, args[0][1]))
402 402
403 403 minlength = 4
404 404 if len(args) > 1:
405 405 minlength = int(args[1][1])
406 406
407 407 cl = mapping['ctx']._repo.changelog
408 408 def isvalid(test):
409 409 try:
410 410 try:
411 411 cl.index.partialmatch(test)
412 412 except AttributeError:
413 413 # Pure mercurial doesn't support partialmatch on the index.
414 414 # Fallback to the slow way.
415 415 if cl._partialmatch(test) is None:
416 416 return False
417 417
418 418 try:
419 419 i = int(test)
420 420 # if we are a pure int, then starting with zero will not be
421 421 # confused as a rev; or, obviously, if the int is larger than
422 422 # the value of the tip rev
423 423 if test[0] == '0' or i > len(cl):
424 424 return True
425 425 return False
426 426 except ValueError:
427 427 return True
428 428 except error.RevlogError:
429 429 return False
430 430
431 431 shortest = node
432 432 startlength = max(6, minlength)
433 433 length = startlength
434 434 while True:
435 435 test = node[:length]
436 436 if isvalid(test):
437 437 shortest = test
438 438 if length == minlength or length > startlength:
439 439 return shortest
440 440 length -= 1
441 441 else:
442 442 length += 1
443 443 if len(shortest) <= length:
444 444 return shortest
445 445
446 446 def strip(context, mapping, args):
447 447 if not (1 <= len(args) <= 2):
448 448 raise error.ParseError(_("strip expects one or two arguments"))
449 449
450 text = args[0][0](context, mapping, args[0][1])
450 text = stringify(args[0][0](context, mapping, args[0][1]))
451 451 if len(args) == 2:
452 chars = args[1][0](context, mapping, args[1][1])
452 chars = stringify(args[1][0](context, mapping, args[1][1]))
453 453 return text.strip(chars)
454 454 return text.strip()
455 455
456 456 def sub(context, mapping, args):
457 457 if len(args) != 3:
458 458 # i18n: "sub" is a keyword
459 459 raise error.ParseError(_("sub expects three arguments"))
460 460
461 461 pat = stringify(args[0][0](context, mapping, args[0][1]))
462 462 rpl = stringify(args[1][0](context, mapping, args[1][1]))
463 src = stringify(args[2][0](context, mapping, args[2][1]))
464 src = stringify(runtemplate(context, mapping,
465 compiletemplate(src, context)))
463 src = stringify(_evalifliteral(args[2], context, mapping))
466 464 yield re.sub(pat, rpl, src)
467 465
468 466 methods = {
469 467 "string": lambda e, c: (runstring, e[1]),
468 "rawstring": lambda e, c: (runrawstring, e[1]),
470 469 "symbol": lambda e, c: (runsymbol, e[1]),
471 470 "group": lambda e, c: compileexp(e[1], c),
472 471 # ".": buildmember,
473 472 "|": buildfilter,
474 473 "%": buildmap,
475 474 "func": buildfunc,
476 475 }
477 476
478 477 funcs = {
479 478 "date": date,
480 479 "fill": fill,
481 480 "get": get,
482 481 "if": if_,
483 482 "ifcontains": ifcontains,
484 483 "ifeq": ifeq,
485 484 "join": join,
486 485 "label": label,
487 486 "pad": pad,
488 487 "revset": revset,
489 488 "rstdoc": rstdoc,
490 489 "shortest": shortest,
491 490 "strip": strip,
492 491 "sub": sub,
493 492 }
494 493
495 494 # template engine
496 495
497 496 path = ['templates', '../templates']
498 497 stringify = templatefilters.stringify
499 498
500 499 def _flatten(thing):
501 500 '''yield a single stream from a possibly nested set of iterators'''
502 501 if isinstance(thing, str):
503 502 yield thing
504 503 elif not util.safehasattr(thing, '__iter__'):
505 504 if thing is not None:
506 505 yield str(thing)
507 506 else:
508 507 for i in thing:
509 508 if isinstance(i, str):
510 509 yield i
511 510 elif not util.safehasattr(i, '__iter__'):
512 511 if i is not None:
513 512 yield str(i)
514 513 elif i is not None:
515 514 for j in _flatten(i):
516 515 yield j
517 516
518 517 def parsestring(s, quoted=True):
519 518 '''parse a string using simple c-like syntax.
520 519 string must be in quotes if quoted is True.'''
521 520 if quoted:
522 521 if len(s) < 2 or s[0] != s[-1]:
523 522 raise SyntaxError(_('unmatched quotes'))
524 523 return s[1:-1].decode('string_escape')
525 524
526 525 return s.decode('string_escape')
527 526
528 527 class engine(object):
529 528 '''template expansion engine.
530 529
531 530 template expansion works like this. a map file contains key=value
532 531 pairs. if value is quoted, it is treated as string. otherwise, it
533 532 is treated as name of template file.
534 533
535 534 templater is asked to expand a key in map. it looks up key, and
536 535 looks for strings like this: {foo}. it expands {foo} by looking up
537 536 foo in map, and substituting it. expansion is recursive: it stops
538 537 when there is no more {foo} to replace.
539 538
540 539 expansion also allows formatting and filtering.
541 540
542 541 format uses key to expand each item in list. syntax is
543 542 {key%format}.
544 543
545 544 filter uses function to transform value. syntax is
546 545 {key|filter1|filter2|...}.'''
547 546
548 547 def __init__(self, loader, filters={}, defaults={}):
549 548 self._loader = loader
550 549 self._filters = filters
551 550 self._defaults = defaults
552 551 self._cache = {}
553 552
554 553 def _load(self, t):
555 554 '''load, parse, and cache a template'''
556 555 if t not in self._cache:
557 556 self._cache[t] = compiletemplate(self._loader(t), self)
558 557 return self._cache[t]
559 558
560 559 def process(self, t, mapping):
561 560 '''Perform expansion. t is name of map element to expand.
562 561 mapping contains added elements for use during expansion. Is a
563 562 generator.'''
564 563 return _flatten(runtemplate(self, mapping, self._load(t)))
565 564
566 565 engines = {'default': engine}
567 566
568 567 def stylelist():
569 568 paths = templatepath()
570 569 if not paths:
571 570 return _('no templates found, try `hg debuginstall` for more info')
572 571 dirlist = os.listdir(paths[0])
573 572 stylelist = []
574 573 for file in dirlist:
575 574 split = file.split(".")
576 575 if split[0] == "map-cmdline":
577 576 stylelist.append(split[1])
578 577 return ", ".join(sorted(stylelist))
579 578
580 579 class TemplateNotFound(util.Abort):
581 580 pass
582 581
583 582 class templater(object):
584 583
585 584 def __init__(self, mapfile, filters={}, defaults={}, cache={},
586 585 minchunk=1024, maxchunk=65536):
587 586 '''set up template engine.
588 587 mapfile is name of file to read map definitions from.
589 588 filters is dict of functions. each transforms a value into another.
590 589 defaults is dict of default map definitions.'''
591 590 self.mapfile = mapfile or 'template'
592 591 self.cache = cache.copy()
593 592 self.map = {}
594 593 self.base = (mapfile and os.path.dirname(mapfile)) or ''
595 594 self.filters = templatefilters.filters.copy()
596 595 self.filters.update(filters)
597 596 self.defaults = defaults
598 597 self.minchunk, self.maxchunk = minchunk, maxchunk
599 598 self.ecache = {}
600 599
601 600 if not mapfile:
602 601 return
603 602 if not os.path.exists(mapfile):
604 603 raise util.Abort(_("style '%s' not found") % mapfile,
605 604 hint=_("available styles: %s") % stylelist())
606 605
607 606 conf = config.config()
608 607 conf.read(mapfile)
609 608
610 609 for key, val in conf[''].items():
611 610 if not val:
612 611 raise SyntaxError(_('%s: missing value') % conf.source('', key))
613 612 if val[0] in "'\"":
614 613 try:
615 614 self.cache[key] = parsestring(val)
616 615 except SyntaxError, inst:
617 616 raise SyntaxError('%s: %s' %
618 617 (conf.source('', key), inst.args[0]))
619 618 else:
620 619 val = 'default', val
621 620 if ':' in val[1]:
622 621 val = val[1].split(':', 1)
623 622 self.map[key] = val[0], os.path.join(self.base, val[1])
624 623
625 624 def __contains__(self, key):
626 625 return key in self.cache or key in self.map
627 626
628 627 def load(self, t):
629 628 '''Get the template for the given template name. Use a local cache.'''
630 629 if t not in self.cache:
631 630 try:
632 631 self.cache[t] = util.readfile(self.map[t][1])
633 632 except KeyError, inst:
634 633 raise TemplateNotFound(_('"%s" not in template map') %
635 634 inst.args[0])
636 635 except IOError, inst:
637 636 raise IOError(inst.args[0], _('template file %s: %s') %
638 637 (self.map[t][1], inst.args[1]))
639 638 return self.cache[t]
640 639
641 640 def __call__(self, t, **mapping):
642 641 ttype = t in self.map and self.map[t][0] or 'default'
643 642 if ttype not in self.ecache:
644 643 self.ecache[ttype] = engines[ttype](self.load,
645 644 self.filters, self.defaults)
646 645 proc = self.ecache[ttype]
647 646
648 647 stream = proc.process(t, mapping)
649 648 if self.minchunk:
650 649 stream = util.increasingchunks(stream, min=self.minchunk,
651 650 max=self.maxchunk)
652 651 return stream
653 652
654 653 def templatepath(name=None):
655 654 '''return location of template file or directory (if no name).
656 655 returns None if not found.'''
657 656 normpaths = []
658 657
659 658 # executable version (py2exe) doesn't support __file__
660 659 if util.mainfrozen():
661 660 module = sys.executable
662 661 else:
663 662 module = __file__
664 663 for f in path:
665 664 if f.startswith('/'):
666 665 p = f
667 666 else:
668 667 fl = f.split('/')
669 668 p = os.path.join(os.path.dirname(module), *fl)
670 669 if name:
671 670 p = os.path.join(p, name)
672 671 if name and os.path.exists(p):
673 672 return os.path.normpath(p)
674 673 elif os.path.isdir(p):
675 674 normpaths.append(os.path.normpath(p))
676 675
677 676 return normpaths
678 677
679 678 def stylemap(styles, paths=None):
680 679 """Return path to mapfile for a given style.
681 680
682 681 Searches mapfile in the following locations:
683 682 1. templatepath/style/map
684 683 2. templatepath/map-style
685 684 3. templatepath/map
686 685 """
687 686
688 687 if paths is None:
689 688 paths = templatepath()
690 689 elif isinstance(paths, str):
691 690 paths = [paths]
692 691
693 692 if isinstance(styles, str):
694 693 styles = [styles]
695 694
696 695 for style in styles:
697 696 if not style:
698 697 continue
699 698 locations = [os.path.join(style, 'map'), 'map-' + style]
700 699 locations.append('map')
701 700
702 701 for path in paths:
703 702 for location in locations:
704 703 mapfile = os.path.join(path, location)
705 704 if os.path.isfile(mapfile):
706 705 return style, mapfile
707 706
708 707 raise RuntimeError("No hgweb templates found in %r" % paths)
@@ -1,1708 +1,1850 b''
1 1 $ hg init a
2 2 $ cd a
3 3 $ echo a > a
4 4 $ hg add a
5 5 $ echo line 1 > b
6 6 $ echo line 2 >> b
7 7 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
8 8
9 9 $ hg add b
10 10 $ echo other 1 > c
11 11 $ echo other 2 >> c
12 12 $ echo >> c
13 13 $ echo other 3 >> c
14 14 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
15 15
16 16 $ hg add c
17 17 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
18 18 $ echo c >> c
19 19 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
20 20
21 21 $ echo foo > .hg/branch
22 22 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
23 23
24 24 $ hg co -q 3
25 25 $ echo other 4 >> d
26 26 $ hg add d
27 27 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
28 28
29 29 $ hg merge -q foo
30 30 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
31 31
32 32 Second branch starting at nullrev:
33 33
34 34 $ hg update null
35 35 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
36 36 $ echo second > second
37 37 $ hg add second
38 38 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
39 39 created new head
40 40
41 41 $ echo third > third
42 42 $ hg add third
43 43 $ hg mv second fourth
44 44 $ hg commit -m third -d "2020-01-01 10:01"
45 45
46 46 $ hg log --template '{join(file_copies, ",\n")}\n' -r .
47 47 fourth (second)
48 48 $ hg log -T '{file_copies % "{source} -> {name}\n"}' -r .
49 49 second -> fourth
50 50
51 51 Quoting for ui.logtemplate
52 52
53 53 $ hg tip --config "ui.logtemplate={rev}\n"
54 54 8
55 55 $ hg tip --config "ui.logtemplate='{rev}\n'"
56 56 8
57 57 $ hg tip --config 'ui.logtemplate="{rev}\n"'
58 58 8
59 59
60 60 Make sure user/global hgrc does not affect tests
61 61
62 62 $ echo '[ui]' > .hg/hgrc
63 63 $ echo 'logtemplate =' >> .hg/hgrc
64 64 $ echo 'style =' >> .hg/hgrc
65 65
66 66 Add some simple styles to settings
67 67
68 68 $ echo '[templates]' >> .hg/hgrc
69 69 $ printf 'simple = "{rev}\\n"\n' >> .hg/hgrc
70 70 $ printf 'simple2 = {rev}\\n\n' >> .hg/hgrc
71 71
72 72 $ hg log -l1 -Tsimple
73 73 8
74 74 $ hg log -l1 -Tsimple2
75 75 8
76 76
77 77 Test templates and style maps in files:
78 78
79 79 $ echo "{rev}" > tmpl
80 80 $ hg log -l1 -T./tmpl
81 81 8
82 82 $ hg log -l1 -Tblah/blah
83 83 blah/blah (no-eol)
84 84
85 85 $ printf 'changeset = "{rev}\\n"\n' > map-simple
86 86 $ hg log -l1 -T./map-simple
87 87 8
88 88
89 89 Default style is like normal output:
90 90
91 91 $ hg log > log.out
92 92 $ hg log --style default > style.out
93 93 $ cmp log.out style.out || diff -u log.out style.out
94 94
95 95 $ hg log -v > log.out
96 96 $ hg log -v --style default > style.out
97 97 $ cmp log.out style.out || diff -u log.out style.out
98 98
99 99 $ hg log --debug > log.out
100 100 $ hg log --debug --style default > style.out
101 101 $ cmp log.out style.out || diff -u log.out style.out
102 102
103 103 Revision with no copies (used to print a traceback):
104 104
105 105 $ hg tip -v --template '\n'
106 106
107 107
108 108 Compact style works:
109 109
110 110 $ hg log -Tcompact
111 111 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
112 112 third
113 113
114 114 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
115 115 second
116 116
117 117 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
118 118 merge
119 119
120 120 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
121 121 new head
122 122
123 123 4 bbe44766e73d 1970-01-17 04:53 +0000 person
124 124 new branch
125 125
126 126 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
127 127 no user, no domain
128 128
129 129 2 97054abb4ab8 1970-01-14 21:20 +0000 other
130 130 no person
131 131
132 132 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
133 133 other 1
134 134
135 135 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
136 136 line 1
137 137
138 138
139 139 $ hg log -v --style compact
140 140 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
141 141 third
142 142
143 143 7:-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
144 144 second
145 145
146 146 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
147 147 merge
148 148
149 149 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
150 150 new head
151 151
152 152 4 bbe44766e73d 1970-01-17 04:53 +0000 person
153 153 new branch
154 154
155 155 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
156 156 no user, no domain
157 157
158 158 2 97054abb4ab8 1970-01-14 21:20 +0000 other@place
159 159 no person
160 160
161 161 1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
162 162 other 1
163 163 other 2
164 164
165 165 other 3
166 166
167 167 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
168 168 line 1
169 169 line 2
170 170
171 171
172 172 $ hg log --debug --style compact
173 173 8[tip]:7,-1 95c24699272e 2020-01-01 10:01 +0000 test
174 174 third
175 175
176 176 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
177 177 second
178 178
179 179 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
180 180 merge
181 181
182 182 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person
183 183 new head
184 184
185 185 4:3,-1 bbe44766e73d 1970-01-17 04:53 +0000 person
186 186 new branch
187 187
188 188 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
189 189 no user, no domain
190 190
191 191 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other@place
192 192 no person
193 193
194 194 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
195 195 other 1
196 196 other 2
197 197
198 198 other 3
199 199
200 200 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
201 201 line 1
202 202 line 2
203 203
204 204
205 205 Test xml styles:
206 206
207 207 $ hg log --style xml
208 208 <?xml version="1.0"?>
209 209 <log>
210 210 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
211 211 <tag>tip</tag>
212 212 <author email="test">test</author>
213 213 <date>2020-01-01T10:01:00+00:00</date>
214 214 <msg xml:space="preserve">third</msg>
215 215 </logentry>
216 216 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
217 217 <parent revision="-1" node="0000000000000000000000000000000000000000" />
218 218 <author email="user@hostname">User Name</author>
219 219 <date>1970-01-12T13:46:40+00:00</date>
220 220 <msg xml:space="preserve">second</msg>
221 221 </logentry>
222 222 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
223 223 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
224 224 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
225 225 <author email="person">person</author>
226 226 <date>1970-01-18T08:40:01+00:00</date>
227 227 <msg xml:space="preserve">merge</msg>
228 228 </logentry>
229 229 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
230 230 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
231 231 <author email="person">person</author>
232 232 <date>1970-01-18T08:40:00+00:00</date>
233 233 <msg xml:space="preserve">new head</msg>
234 234 </logentry>
235 235 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
236 236 <branch>foo</branch>
237 237 <author email="person">person</author>
238 238 <date>1970-01-17T04:53:20+00:00</date>
239 239 <msg xml:space="preserve">new branch</msg>
240 240 </logentry>
241 241 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
242 242 <author email="person">person</author>
243 243 <date>1970-01-16T01:06:40+00:00</date>
244 244 <msg xml:space="preserve">no user, no domain</msg>
245 245 </logentry>
246 246 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
247 247 <author email="other@place">other</author>
248 248 <date>1970-01-14T21:20:00+00:00</date>
249 249 <msg xml:space="preserve">no person</msg>
250 250 </logentry>
251 251 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
252 252 <author email="other@place">A. N. Other</author>
253 253 <date>1970-01-13T17:33:20+00:00</date>
254 254 <msg xml:space="preserve">other 1
255 255 other 2
256 256
257 257 other 3</msg>
258 258 </logentry>
259 259 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
260 260 <author email="user@hostname">User Name</author>
261 261 <date>1970-01-12T13:46:40+00:00</date>
262 262 <msg xml:space="preserve">line 1
263 263 line 2</msg>
264 264 </logentry>
265 265 </log>
266 266
267 267 $ hg log -v --style xml
268 268 <?xml version="1.0"?>
269 269 <log>
270 270 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
271 271 <tag>tip</tag>
272 272 <author email="test">test</author>
273 273 <date>2020-01-01T10:01:00+00:00</date>
274 274 <msg xml:space="preserve">third</msg>
275 275 <paths>
276 276 <path action="A">fourth</path>
277 277 <path action="A">third</path>
278 278 <path action="R">second</path>
279 279 </paths>
280 280 <copies>
281 281 <copy source="second">fourth</copy>
282 282 </copies>
283 283 </logentry>
284 284 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
285 285 <parent revision="-1" node="0000000000000000000000000000000000000000" />
286 286 <author email="user@hostname">User Name</author>
287 287 <date>1970-01-12T13:46:40+00:00</date>
288 288 <msg xml:space="preserve">second</msg>
289 289 <paths>
290 290 <path action="A">second</path>
291 291 </paths>
292 292 </logentry>
293 293 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
294 294 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
295 295 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
296 296 <author email="person">person</author>
297 297 <date>1970-01-18T08:40:01+00:00</date>
298 298 <msg xml:space="preserve">merge</msg>
299 299 <paths>
300 300 </paths>
301 301 </logentry>
302 302 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
303 303 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
304 304 <author email="person">person</author>
305 305 <date>1970-01-18T08:40:00+00:00</date>
306 306 <msg xml:space="preserve">new head</msg>
307 307 <paths>
308 308 <path action="A">d</path>
309 309 </paths>
310 310 </logentry>
311 311 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
312 312 <branch>foo</branch>
313 313 <author email="person">person</author>
314 314 <date>1970-01-17T04:53:20+00:00</date>
315 315 <msg xml:space="preserve">new branch</msg>
316 316 <paths>
317 317 </paths>
318 318 </logentry>
319 319 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
320 320 <author email="person">person</author>
321 321 <date>1970-01-16T01:06:40+00:00</date>
322 322 <msg xml:space="preserve">no user, no domain</msg>
323 323 <paths>
324 324 <path action="M">c</path>
325 325 </paths>
326 326 </logentry>
327 327 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
328 328 <author email="other@place">other</author>
329 329 <date>1970-01-14T21:20:00+00:00</date>
330 330 <msg xml:space="preserve">no person</msg>
331 331 <paths>
332 332 <path action="A">c</path>
333 333 </paths>
334 334 </logentry>
335 335 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
336 336 <author email="other@place">A. N. Other</author>
337 337 <date>1970-01-13T17:33:20+00:00</date>
338 338 <msg xml:space="preserve">other 1
339 339 other 2
340 340
341 341 other 3</msg>
342 342 <paths>
343 343 <path action="A">b</path>
344 344 </paths>
345 345 </logentry>
346 346 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
347 347 <author email="user@hostname">User Name</author>
348 348 <date>1970-01-12T13:46:40+00:00</date>
349 349 <msg xml:space="preserve">line 1
350 350 line 2</msg>
351 351 <paths>
352 352 <path action="A">a</path>
353 353 </paths>
354 354 </logentry>
355 355 </log>
356 356
357 357 $ hg log --debug --style xml
358 358 <?xml version="1.0"?>
359 359 <log>
360 360 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
361 361 <tag>tip</tag>
362 362 <parent revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453" />
363 363 <parent revision="-1" node="0000000000000000000000000000000000000000" />
364 364 <author email="test">test</author>
365 365 <date>2020-01-01T10:01:00+00:00</date>
366 366 <msg xml:space="preserve">third</msg>
367 367 <paths>
368 368 <path action="A">fourth</path>
369 369 <path action="A">third</path>
370 370 <path action="R">second</path>
371 371 </paths>
372 372 <copies>
373 373 <copy source="second">fourth</copy>
374 374 </copies>
375 375 <extra key="branch">default</extra>
376 376 </logentry>
377 377 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
378 378 <parent revision="-1" node="0000000000000000000000000000000000000000" />
379 379 <parent revision="-1" node="0000000000000000000000000000000000000000" />
380 380 <author email="user@hostname">User Name</author>
381 381 <date>1970-01-12T13:46:40+00:00</date>
382 382 <msg xml:space="preserve">second</msg>
383 383 <paths>
384 384 <path action="A">second</path>
385 385 </paths>
386 386 <extra key="branch">default</extra>
387 387 </logentry>
388 388 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
389 389 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
390 390 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
391 391 <author email="person">person</author>
392 392 <date>1970-01-18T08:40:01+00:00</date>
393 393 <msg xml:space="preserve">merge</msg>
394 394 <paths>
395 395 </paths>
396 396 <extra key="branch">default</extra>
397 397 </logentry>
398 398 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
399 399 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
400 400 <parent revision="-1" node="0000000000000000000000000000000000000000" />
401 401 <author email="person">person</author>
402 402 <date>1970-01-18T08:40:00+00:00</date>
403 403 <msg xml:space="preserve">new head</msg>
404 404 <paths>
405 405 <path action="A">d</path>
406 406 </paths>
407 407 <extra key="branch">default</extra>
408 408 </logentry>
409 409 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
410 410 <branch>foo</branch>
411 411 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
412 412 <parent revision="-1" node="0000000000000000000000000000000000000000" />
413 413 <author email="person">person</author>
414 414 <date>1970-01-17T04:53:20+00:00</date>
415 415 <msg xml:space="preserve">new branch</msg>
416 416 <paths>
417 417 </paths>
418 418 <extra key="branch">foo</extra>
419 419 </logentry>
420 420 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
421 421 <parent revision="2" node="97054abb4ab824450e9164180baf491ae0078465" />
422 422 <parent revision="-1" node="0000000000000000000000000000000000000000" />
423 423 <author email="person">person</author>
424 424 <date>1970-01-16T01:06:40+00:00</date>
425 425 <msg xml:space="preserve">no user, no domain</msg>
426 426 <paths>
427 427 <path action="M">c</path>
428 428 </paths>
429 429 <extra key="branch">default</extra>
430 430 </logentry>
431 431 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
432 432 <parent revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965" />
433 433 <parent revision="-1" node="0000000000000000000000000000000000000000" />
434 434 <author email="other@place">other</author>
435 435 <date>1970-01-14T21:20:00+00:00</date>
436 436 <msg xml:space="preserve">no person</msg>
437 437 <paths>
438 438 <path action="A">c</path>
439 439 </paths>
440 440 <extra key="branch">default</extra>
441 441 </logentry>
442 442 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
443 443 <parent revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f" />
444 444 <parent revision="-1" node="0000000000000000000000000000000000000000" />
445 445 <author email="other@place">A. N. Other</author>
446 446 <date>1970-01-13T17:33:20+00:00</date>
447 447 <msg xml:space="preserve">other 1
448 448 other 2
449 449
450 450 other 3</msg>
451 451 <paths>
452 452 <path action="A">b</path>
453 453 </paths>
454 454 <extra key="branch">default</extra>
455 455 </logentry>
456 456 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
457 457 <parent revision="-1" node="0000000000000000000000000000000000000000" />
458 458 <parent revision="-1" node="0000000000000000000000000000000000000000" />
459 459 <author email="user@hostname">User Name</author>
460 460 <date>1970-01-12T13:46:40+00:00</date>
461 461 <msg xml:space="preserve">line 1
462 462 line 2</msg>
463 463 <paths>
464 464 <path action="A">a</path>
465 465 </paths>
466 466 <extra key="branch">default</extra>
467 467 </logentry>
468 468 </log>
469 469
470 470
471 471 Error if style not readable:
472 472
473 473 #if unix-permissions no-root
474 474 $ touch q
475 475 $ chmod 0 q
476 476 $ hg log --style ./q
477 477 abort: Permission denied: ./q
478 478 [255]
479 479 #endif
480 480
481 481 Error if no style:
482 482
483 483 $ hg log --style notexist
484 484 abort: style 'notexist' not found
485 485 (available styles: bisect, changelog, compact, default, phases, xml)
486 486 [255]
487 487
488 488 Error if style missing key:
489 489
490 490 $ echo 'q = q' > t
491 491 $ hg log --style ./t
492 492 abort: "changeset" not in template map
493 493 [255]
494 494
495 495 Error if style missing value:
496 496
497 497 $ echo 'changeset =' > t
498 498 $ hg log --style t
499 499 abort: t:1: missing value
500 500 [255]
501 501
502 502 Error if include fails:
503 503
504 504 $ echo 'changeset = q' >> t
505 505 #if unix-permissions no-root
506 506 $ hg log --style ./t
507 507 abort: template file ./q: Permission denied
508 508 [255]
509 509 $ rm q
510 510 #endif
511 511
512 512 Include works:
513 513
514 514 $ echo '{rev}' > q
515 515 $ hg log --style ./t
516 516 8
517 517 7
518 518 6
519 519 5
520 520 4
521 521 3
522 522 2
523 523 1
524 524 0
525 525
526 526 Missing non-standard names give no error (backward compatibility):
527 527
528 528 $ echo "changeset = '{c}'" > t
529 529 $ hg log --style ./t
530 530
531 531 Defining non-standard name works:
532 532
533 533 $ cat <<EOF > t
534 534 > changeset = '{c}'
535 535 > c = q
536 536 > EOF
537 537 $ hg log --style ./t
538 538 8
539 539 7
540 540 6
541 541 5
542 542 4
543 543 3
544 544 2
545 545 1
546 546 0
547 547
548 548 ui.style works:
549 549
550 550 $ echo '[ui]' > .hg/hgrc
551 551 $ echo 'style = t' >> .hg/hgrc
552 552 $ hg log
553 553 8
554 554 7
555 555 6
556 556 5
557 557 4
558 558 3
559 559 2
560 560 1
561 561 0
562 562
563 563
564 564 Issue338:
565 565
566 566 $ hg log --style=changelog > changelog
567 567
568 568 $ cat changelog
569 569 2020-01-01 test <test>
570 570
571 571 * fourth, second, third:
572 572 third
573 573 [95c24699272e] [tip]
574 574
575 575 1970-01-12 User Name <user@hostname>
576 576
577 577 * second:
578 578 second
579 579 [29114dbae42b]
580 580
581 581 1970-01-18 person <person>
582 582
583 583 * merge
584 584 [d41e714fe50d]
585 585
586 586 * d:
587 587 new head
588 588 [13207e5a10d9]
589 589
590 590 1970-01-17 person <person>
591 591
592 592 * new branch
593 593 [bbe44766e73d] <foo>
594 594
595 595 1970-01-16 person <person>
596 596
597 597 * c:
598 598 no user, no domain
599 599 [10e46f2dcbf4]
600 600
601 601 1970-01-14 other <other@place>
602 602
603 603 * c:
604 604 no person
605 605 [97054abb4ab8]
606 606
607 607 1970-01-13 A. N. Other <other@place>
608 608
609 609 * b:
610 610 other 1 other 2
611 611
612 612 other 3
613 613 [b608e9d1a3f0]
614 614
615 615 1970-01-12 User Name <user@hostname>
616 616
617 617 * a:
618 618 line 1 line 2
619 619 [1e4e1b8f71e0]
620 620
621 621
622 622 Issue2130: xml output for 'hg heads' is malformed
623 623
624 624 $ hg heads --style changelog
625 625 2020-01-01 test <test>
626 626
627 627 * fourth, second, third:
628 628 third
629 629 [95c24699272e] [tip]
630 630
631 631 1970-01-18 person <person>
632 632
633 633 * merge
634 634 [d41e714fe50d]
635 635
636 636 1970-01-17 person <person>
637 637
638 638 * new branch
639 639 [bbe44766e73d] <foo>
640 640
641 641
642 642 Keys work:
643 643
644 644 $ for key in author branch branches date desc file_adds file_dels file_mods \
645 645 > file_copies file_copies_switch files \
646 646 > manifest node parents rev tags diffstat extras \
647 647 > p1rev p2rev p1node p2node; do
648 648 > for mode in '' --verbose --debug; do
649 649 > hg log $mode --template "$key$mode: {$key}\n"
650 650 > done
651 651 > done
652 652 author: test
653 653 author: User Name <user@hostname>
654 654 author: person
655 655 author: person
656 656 author: person
657 657 author: person
658 658 author: other@place
659 659 author: A. N. Other <other@place>
660 660 author: User Name <user@hostname>
661 661 author--verbose: test
662 662 author--verbose: User Name <user@hostname>
663 663 author--verbose: person
664 664 author--verbose: person
665 665 author--verbose: person
666 666 author--verbose: person
667 667 author--verbose: other@place
668 668 author--verbose: A. N. Other <other@place>
669 669 author--verbose: User Name <user@hostname>
670 670 author--debug: test
671 671 author--debug: User Name <user@hostname>
672 672 author--debug: person
673 673 author--debug: person
674 674 author--debug: person
675 675 author--debug: person
676 676 author--debug: other@place
677 677 author--debug: A. N. Other <other@place>
678 678 author--debug: User Name <user@hostname>
679 679 branch: default
680 680 branch: default
681 681 branch: default
682 682 branch: default
683 683 branch: foo
684 684 branch: default
685 685 branch: default
686 686 branch: default
687 687 branch: default
688 688 branch--verbose: default
689 689 branch--verbose: default
690 690 branch--verbose: default
691 691 branch--verbose: default
692 692 branch--verbose: foo
693 693 branch--verbose: default
694 694 branch--verbose: default
695 695 branch--verbose: default
696 696 branch--verbose: default
697 697 branch--debug: default
698 698 branch--debug: default
699 699 branch--debug: default
700 700 branch--debug: default
701 701 branch--debug: foo
702 702 branch--debug: default
703 703 branch--debug: default
704 704 branch--debug: default
705 705 branch--debug: default
706 706 branches:
707 707 branches:
708 708 branches:
709 709 branches:
710 710 branches: foo
711 711 branches:
712 712 branches:
713 713 branches:
714 714 branches:
715 715 branches--verbose:
716 716 branches--verbose:
717 717 branches--verbose:
718 718 branches--verbose:
719 719 branches--verbose: foo
720 720 branches--verbose:
721 721 branches--verbose:
722 722 branches--verbose:
723 723 branches--verbose:
724 724 branches--debug:
725 725 branches--debug:
726 726 branches--debug:
727 727 branches--debug:
728 728 branches--debug: foo
729 729 branches--debug:
730 730 branches--debug:
731 731 branches--debug:
732 732 branches--debug:
733 733 date: 1577872860.00
734 734 date: 1000000.00
735 735 date: 1500001.00
736 736 date: 1500000.00
737 737 date: 1400000.00
738 738 date: 1300000.00
739 739 date: 1200000.00
740 740 date: 1100000.00
741 741 date: 1000000.00
742 742 date--verbose: 1577872860.00
743 743 date--verbose: 1000000.00
744 744 date--verbose: 1500001.00
745 745 date--verbose: 1500000.00
746 746 date--verbose: 1400000.00
747 747 date--verbose: 1300000.00
748 748 date--verbose: 1200000.00
749 749 date--verbose: 1100000.00
750 750 date--verbose: 1000000.00
751 751 date--debug: 1577872860.00
752 752 date--debug: 1000000.00
753 753 date--debug: 1500001.00
754 754 date--debug: 1500000.00
755 755 date--debug: 1400000.00
756 756 date--debug: 1300000.00
757 757 date--debug: 1200000.00
758 758 date--debug: 1100000.00
759 759 date--debug: 1000000.00
760 760 desc: third
761 761 desc: second
762 762 desc: merge
763 763 desc: new head
764 764 desc: new branch
765 765 desc: no user, no domain
766 766 desc: no person
767 767 desc: other 1
768 768 other 2
769 769
770 770 other 3
771 771 desc: line 1
772 772 line 2
773 773 desc--verbose: third
774 774 desc--verbose: second
775 775 desc--verbose: merge
776 776 desc--verbose: new head
777 777 desc--verbose: new branch
778 778 desc--verbose: no user, no domain
779 779 desc--verbose: no person
780 780 desc--verbose: other 1
781 781 other 2
782 782
783 783 other 3
784 784 desc--verbose: line 1
785 785 line 2
786 786 desc--debug: third
787 787 desc--debug: second
788 788 desc--debug: merge
789 789 desc--debug: new head
790 790 desc--debug: new branch
791 791 desc--debug: no user, no domain
792 792 desc--debug: no person
793 793 desc--debug: other 1
794 794 other 2
795 795
796 796 other 3
797 797 desc--debug: line 1
798 798 line 2
799 799 file_adds: fourth third
800 800 file_adds: second
801 801 file_adds:
802 802 file_adds: d
803 803 file_adds:
804 804 file_adds:
805 805 file_adds: c
806 806 file_adds: b
807 807 file_adds: a
808 808 file_adds--verbose: fourth third
809 809 file_adds--verbose: second
810 810 file_adds--verbose:
811 811 file_adds--verbose: d
812 812 file_adds--verbose:
813 813 file_adds--verbose:
814 814 file_adds--verbose: c
815 815 file_adds--verbose: b
816 816 file_adds--verbose: a
817 817 file_adds--debug: fourth third
818 818 file_adds--debug: second
819 819 file_adds--debug:
820 820 file_adds--debug: d
821 821 file_adds--debug:
822 822 file_adds--debug:
823 823 file_adds--debug: c
824 824 file_adds--debug: b
825 825 file_adds--debug: a
826 826 file_dels: second
827 827 file_dels:
828 828 file_dels:
829 829 file_dels:
830 830 file_dels:
831 831 file_dels:
832 832 file_dels:
833 833 file_dels:
834 834 file_dels:
835 835 file_dels--verbose: second
836 836 file_dels--verbose:
837 837 file_dels--verbose:
838 838 file_dels--verbose:
839 839 file_dels--verbose:
840 840 file_dels--verbose:
841 841 file_dels--verbose:
842 842 file_dels--verbose:
843 843 file_dels--verbose:
844 844 file_dels--debug: second
845 845 file_dels--debug:
846 846 file_dels--debug:
847 847 file_dels--debug:
848 848 file_dels--debug:
849 849 file_dels--debug:
850 850 file_dels--debug:
851 851 file_dels--debug:
852 852 file_dels--debug:
853 853 file_mods:
854 854 file_mods:
855 855 file_mods:
856 856 file_mods:
857 857 file_mods:
858 858 file_mods: c
859 859 file_mods:
860 860 file_mods:
861 861 file_mods:
862 862 file_mods--verbose:
863 863 file_mods--verbose:
864 864 file_mods--verbose:
865 865 file_mods--verbose:
866 866 file_mods--verbose:
867 867 file_mods--verbose: c
868 868 file_mods--verbose:
869 869 file_mods--verbose:
870 870 file_mods--verbose:
871 871 file_mods--debug:
872 872 file_mods--debug:
873 873 file_mods--debug:
874 874 file_mods--debug:
875 875 file_mods--debug:
876 876 file_mods--debug: c
877 877 file_mods--debug:
878 878 file_mods--debug:
879 879 file_mods--debug:
880 880 file_copies: fourth (second)
881 881 file_copies:
882 882 file_copies:
883 883 file_copies:
884 884 file_copies:
885 885 file_copies:
886 886 file_copies:
887 887 file_copies:
888 888 file_copies:
889 889 file_copies--verbose: fourth (second)
890 890 file_copies--verbose:
891 891 file_copies--verbose:
892 892 file_copies--verbose:
893 893 file_copies--verbose:
894 894 file_copies--verbose:
895 895 file_copies--verbose:
896 896 file_copies--verbose:
897 897 file_copies--verbose:
898 898 file_copies--debug: fourth (second)
899 899 file_copies--debug:
900 900 file_copies--debug:
901 901 file_copies--debug:
902 902 file_copies--debug:
903 903 file_copies--debug:
904 904 file_copies--debug:
905 905 file_copies--debug:
906 906 file_copies--debug:
907 907 file_copies_switch:
908 908 file_copies_switch:
909 909 file_copies_switch:
910 910 file_copies_switch:
911 911 file_copies_switch:
912 912 file_copies_switch:
913 913 file_copies_switch:
914 914 file_copies_switch:
915 915 file_copies_switch:
916 916 file_copies_switch--verbose:
917 917 file_copies_switch--verbose:
918 918 file_copies_switch--verbose:
919 919 file_copies_switch--verbose:
920 920 file_copies_switch--verbose:
921 921 file_copies_switch--verbose:
922 922 file_copies_switch--verbose:
923 923 file_copies_switch--verbose:
924 924 file_copies_switch--verbose:
925 925 file_copies_switch--debug:
926 926 file_copies_switch--debug:
927 927 file_copies_switch--debug:
928 928 file_copies_switch--debug:
929 929 file_copies_switch--debug:
930 930 file_copies_switch--debug:
931 931 file_copies_switch--debug:
932 932 file_copies_switch--debug:
933 933 file_copies_switch--debug:
934 934 files: fourth second third
935 935 files: second
936 936 files:
937 937 files: d
938 938 files:
939 939 files: c
940 940 files: c
941 941 files: b
942 942 files: a
943 943 files--verbose: fourth second third
944 944 files--verbose: second
945 945 files--verbose:
946 946 files--verbose: d
947 947 files--verbose:
948 948 files--verbose: c
949 949 files--verbose: c
950 950 files--verbose: b
951 951 files--verbose: a
952 952 files--debug: fourth second third
953 953 files--debug: second
954 954 files--debug:
955 955 files--debug: d
956 956 files--debug:
957 957 files--debug: c
958 958 files--debug: c
959 959 files--debug: b
960 960 files--debug: a
961 961 manifest: 6:94961b75a2da
962 962 manifest: 5:f2dbc354b94e
963 963 manifest: 4:4dc3def4f9b4
964 964 manifest: 4:4dc3def4f9b4
965 965 manifest: 3:cb5a1327723b
966 966 manifest: 3:cb5a1327723b
967 967 manifest: 2:6e0e82995c35
968 968 manifest: 1:4e8d705b1e53
969 969 manifest: 0:a0c8bcbbb45c
970 970 manifest--verbose: 6:94961b75a2da
971 971 manifest--verbose: 5:f2dbc354b94e
972 972 manifest--verbose: 4:4dc3def4f9b4
973 973 manifest--verbose: 4:4dc3def4f9b4
974 974 manifest--verbose: 3:cb5a1327723b
975 975 manifest--verbose: 3:cb5a1327723b
976 976 manifest--verbose: 2:6e0e82995c35
977 977 manifest--verbose: 1:4e8d705b1e53
978 978 manifest--verbose: 0:a0c8bcbbb45c
979 979 manifest--debug: 6:94961b75a2da554b4df6fb599e5bfc7d48de0c64
980 980 manifest--debug: 5:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
981 981 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
982 982 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
983 983 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
984 984 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
985 985 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
986 986 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
987 987 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
988 988 node: 95c24699272ef57d062b8bccc32c878bf841784a
989 989 node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
990 990 node: d41e714fe50d9e4a5f11b4d595d543481b5f980b
991 991 node: 13207e5a10d9fd28ec424934298e176197f2c67f
992 992 node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
993 993 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
994 994 node: 97054abb4ab824450e9164180baf491ae0078465
995 995 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
996 996 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
997 997 node--verbose: 95c24699272ef57d062b8bccc32c878bf841784a
998 998 node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
999 999 node--verbose: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1000 1000 node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1001 1001 node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1002 1002 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1003 1003 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
1004 1004 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1005 1005 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
1006 1006 node--debug: 95c24699272ef57d062b8bccc32c878bf841784a
1007 1007 node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1008 1008 node--debug: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1009 1009 node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
1010 1010 node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1011 1011 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1012 1012 node--debug: 97054abb4ab824450e9164180baf491ae0078465
1013 1013 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1014 1014 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
1015 1015 parents:
1016 1016 parents: -1:000000000000
1017 1017 parents: 5:13207e5a10d9 4:bbe44766e73d
1018 1018 parents: 3:10e46f2dcbf4
1019 1019 parents:
1020 1020 parents:
1021 1021 parents:
1022 1022 parents:
1023 1023 parents:
1024 1024 parents--verbose:
1025 1025 parents--verbose: -1:000000000000
1026 1026 parents--verbose: 5:13207e5a10d9 4:bbe44766e73d
1027 1027 parents--verbose: 3:10e46f2dcbf4
1028 1028 parents--verbose:
1029 1029 parents--verbose:
1030 1030 parents--verbose:
1031 1031 parents--verbose:
1032 1032 parents--verbose:
1033 1033 parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000
1034 1034 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1035 1035 parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1036 1036 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1037 1037 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1038 1038 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
1039 1039 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
1040 1040 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
1041 1041 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1042 1042 rev: 8
1043 1043 rev: 7
1044 1044 rev: 6
1045 1045 rev: 5
1046 1046 rev: 4
1047 1047 rev: 3
1048 1048 rev: 2
1049 1049 rev: 1
1050 1050 rev: 0
1051 1051 rev--verbose: 8
1052 1052 rev--verbose: 7
1053 1053 rev--verbose: 6
1054 1054 rev--verbose: 5
1055 1055 rev--verbose: 4
1056 1056 rev--verbose: 3
1057 1057 rev--verbose: 2
1058 1058 rev--verbose: 1
1059 1059 rev--verbose: 0
1060 1060 rev--debug: 8
1061 1061 rev--debug: 7
1062 1062 rev--debug: 6
1063 1063 rev--debug: 5
1064 1064 rev--debug: 4
1065 1065 rev--debug: 3
1066 1066 rev--debug: 2
1067 1067 rev--debug: 1
1068 1068 rev--debug: 0
1069 1069 tags: tip
1070 1070 tags:
1071 1071 tags:
1072 1072 tags:
1073 1073 tags:
1074 1074 tags:
1075 1075 tags:
1076 1076 tags:
1077 1077 tags:
1078 1078 tags--verbose: tip
1079 1079 tags--verbose:
1080 1080 tags--verbose:
1081 1081 tags--verbose:
1082 1082 tags--verbose:
1083 1083 tags--verbose:
1084 1084 tags--verbose:
1085 1085 tags--verbose:
1086 1086 tags--verbose:
1087 1087 tags--debug: tip
1088 1088 tags--debug:
1089 1089 tags--debug:
1090 1090 tags--debug:
1091 1091 tags--debug:
1092 1092 tags--debug:
1093 1093 tags--debug:
1094 1094 tags--debug:
1095 1095 tags--debug:
1096 1096 diffstat: 3: +2/-1
1097 1097 diffstat: 1: +1/-0
1098 1098 diffstat: 0: +0/-0
1099 1099 diffstat: 1: +1/-0
1100 1100 diffstat: 0: +0/-0
1101 1101 diffstat: 1: +1/-0
1102 1102 diffstat: 1: +4/-0
1103 1103 diffstat: 1: +2/-0
1104 1104 diffstat: 1: +1/-0
1105 1105 diffstat--verbose: 3: +2/-1
1106 1106 diffstat--verbose: 1: +1/-0
1107 1107 diffstat--verbose: 0: +0/-0
1108 1108 diffstat--verbose: 1: +1/-0
1109 1109 diffstat--verbose: 0: +0/-0
1110 1110 diffstat--verbose: 1: +1/-0
1111 1111 diffstat--verbose: 1: +4/-0
1112 1112 diffstat--verbose: 1: +2/-0
1113 1113 diffstat--verbose: 1: +1/-0
1114 1114 diffstat--debug: 3: +2/-1
1115 1115 diffstat--debug: 1: +1/-0
1116 1116 diffstat--debug: 0: +0/-0
1117 1117 diffstat--debug: 1: +1/-0
1118 1118 diffstat--debug: 0: +0/-0
1119 1119 diffstat--debug: 1: +1/-0
1120 1120 diffstat--debug: 1: +4/-0
1121 1121 diffstat--debug: 1: +2/-0
1122 1122 diffstat--debug: 1: +1/-0
1123 1123 extras: branch=default
1124 1124 extras: branch=default
1125 1125 extras: branch=default
1126 1126 extras: branch=default
1127 1127 extras: branch=foo
1128 1128 extras: branch=default
1129 1129 extras: branch=default
1130 1130 extras: branch=default
1131 1131 extras: branch=default
1132 1132 extras--verbose: branch=default
1133 1133 extras--verbose: branch=default
1134 1134 extras--verbose: branch=default
1135 1135 extras--verbose: branch=default
1136 1136 extras--verbose: branch=foo
1137 1137 extras--verbose: branch=default
1138 1138 extras--verbose: branch=default
1139 1139 extras--verbose: branch=default
1140 1140 extras--verbose: branch=default
1141 1141 extras--debug: branch=default
1142 1142 extras--debug: branch=default
1143 1143 extras--debug: branch=default
1144 1144 extras--debug: branch=default
1145 1145 extras--debug: branch=foo
1146 1146 extras--debug: branch=default
1147 1147 extras--debug: branch=default
1148 1148 extras--debug: branch=default
1149 1149 extras--debug: branch=default
1150 1150 p1rev: 7
1151 1151 p1rev: -1
1152 1152 p1rev: 5
1153 1153 p1rev: 3
1154 1154 p1rev: 3
1155 1155 p1rev: 2
1156 1156 p1rev: 1
1157 1157 p1rev: 0
1158 1158 p1rev: -1
1159 1159 p1rev--verbose: 7
1160 1160 p1rev--verbose: -1
1161 1161 p1rev--verbose: 5
1162 1162 p1rev--verbose: 3
1163 1163 p1rev--verbose: 3
1164 1164 p1rev--verbose: 2
1165 1165 p1rev--verbose: 1
1166 1166 p1rev--verbose: 0
1167 1167 p1rev--verbose: -1
1168 1168 p1rev--debug: 7
1169 1169 p1rev--debug: -1
1170 1170 p1rev--debug: 5
1171 1171 p1rev--debug: 3
1172 1172 p1rev--debug: 3
1173 1173 p1rev--debug: 2
1174 1174 p1rev--debug: 1
1175 1175 p1rev--debug: 0
1176 1176 p1rev--debug: -1
1177 1177 p2rev: -1
1178 1178 p2rev: -1
1179 1179 p2rev: 4
1180 1180 p2rev: -1
1181 1181 p2rev: -1
1182 1182 p2rev: -1
1183 1183 p2rev: -1
1184 1184 p2rev: -1
1185 1185 p2rev: -1
1186 1186 p2rev--verbose: -1
1187 1187 p2rev--verbose: -1
1188 1188 p2rev--verbose: 4
1189 1189 p2rev--verbose: -1
1190 1190 p2rev--verbose: -1
1191 1191 p2rev--verbose: -1
1192 1192 p2rev--verbose: -1
1193 1193 p2rev--verbose: -1
1194 1194 p2rev--verbose: -1
1195 1195 p2rev--debug: -1
1196 1196 p2rev--debug: -1
1197 1197 p2rev--debug: 4
1198 1198 p2rev--debug: -1
1199 1199 p2rev--debug: -1
1200 1200 p2rev--debug: -1
1201 1201 p2rev--debug: -1
1202 1202 p2rev--debug: -1
1203 1203 p2rev--debug: -1
1204 1204 p1node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1205 1205 p1node: 0000000000000000000000000000000000000000
1206 1206 p1node: 13207e5a10d9fd28ec424934298e176197f2c67f
1207 1207 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1208 1208 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1209 1209 p1node: 97054abb4ab824450e9164180baf491ae0078465
1210 1210 p1node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1211 1211 p1node: 1e4e1b8f71e05681d422154f5421e385fec3454f
1212 1212 p1node: 0000000000000000000000000000000000000000
1213 1213 p1node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1214 1214 p1node--verbose: 0000000000000000000000000000000000000000
1215 1215 p1node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1216 1216 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1217 1217 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1218 1218 p1node--verbose: 97054abb4ab824450e9164180baf491ae0078465
1219 1219 p1node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1220 1220 p1node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
1221 1221 p1node--verbose: 0000000000000000000000000000000000000000
1222 1222 p1node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1223 1223 p1node--debug: 0000000000000000000000000000000000000000
1224 1224 p1node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
1225 1225 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1226 1226 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1227 1227 p1node--debug: 97054abb4ab824450e9164180baf491ae0078465
1228 1228 p1node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1229 1229 p1node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
1230 1230 p1node--debug: 0000000000000000000000000000000000000000
1231 1231 p2node: 0000000000000000000000000000000000000000
1232 1232 p2node: 0000000000000000000000000000000000000000
1233 1233 p2node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1234 1234 p2node: 0000000000000000000000000000000000000000
1235 1235 p2node: 0000000000000000000000000000000000000000
1236 1236 p2node: 0000000000000000000000000000000000000000
1237 1237 p2node: 0000000000000000000000000000000000000000
1238 1238 p2node: 0000000000000000000000000000000000000000
1239 1239 p2node: 0000000000000000000000000000000000000000
1240 1240 p2node--verbose: 0000000000000000000000000000000000000000
1241 1241 p2node--verbose: 0000000000000000000000000000000000000000
1242 1242 p2node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1243 1243 p2node--verbose: 0000000000000000000000000000000000000000
1244 1244 p2node--verbose: 0000000000000000000000000000000000000000
1245 1245 p2node--verbose: 0000000000000000000000000000000000000000
1246 1246 p2node--verbose: 0000000000000000000000000000000000000000
1247 1247 p2node--verbose: 0000000000000000000000000000000000000000
1248 1248 p2node--verbose: 0000000000000000000000000000000000000000
1249 1249 p2node--debug: 0000000000000000000000000000000000000000
1250 1250 p2node--debug: 0000000000000000000000000000000000000000
1251 1251 p2node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1252 1252 p2node--debug: 0000000000000000000000000000000000000000
1253 1253 p2node--debug: 0000000000000000000000000000000000000000
1254 1254 p2node--debug: 0000000000000000000000000000000000000000
1255 1255 p2node--debug: 0000000000000000000000000000000000000000
1256 1256 p2node--debug: 0000000000000000000000000000000000000000
1257 1257 p2node--debug: 0000000000000000000000000000000000000000
1258 1258
1259 1259 Filters work:
1260 1260
1261 1261 $ hg log --template '{author|domain}\n'
1262 1262
1263 1263 hostname
1264 1264
1265 1265
1266 1266
1267 1267
1268 1268 place
1269 1269 place
1270 1270 hostname
1271 1271
1272 1272 $ hg log --template '{author|person}\n'
1273 1273 test
1274 1274 User Name
1275 1275 person
1276 1276 person
1277 1277 person
1278 1278 person
1279 1279 other
1280 1280 A. N. Other
1281 1281 User Name
1282 1282
1283 1283 $ hg log --template '{author|user}\n'
1284 1284 test
1285 1285 user
1286 1286 person
1287 1287 person
1288 1288 person
1289 1289 person
1290 1290 other
1291 1291 other
1292 1292 user
1293 1293
1294 1294 $ hg log --template '{date|date}\n'
1295 1295 Wed Jan 01 10:01:00 2020 +0000
1296 1296 Mon Jan 12 13:46:40 1970 +0000
1297 1297 Sun Jan 18 08:40:01 1970 +0000
1298 1298 Sun Jan 18 08:40:00 1970 +0000
1299 1299 Sat Jan 17 04:53:20 1970 +0000
1300 1300 Fri Jan 16 01:06:40 1970 +0000
1301 1301 Wed Jan 14 21:20:00 1970 +0000
1302 1302 Tue Jan 13 17:33:20 1970 +0000
1303 1303 Mon Jan 12 13:46:40 1970 +0000
1304 1304
1305 1305 $ hg log --template '{date|isodate}\n'
1306 1306 2020-01-01 10:01 +0000
1307 1307 1970-01-12 13:46 +0000
1308 1308 1970-01-18 08:40 +0000
1309 1309 1970-01-18 08:40 +0000
1310 1310 1970-01-17 04:53 +0000
1311 1311 1970-01-16 01:06 +0000
1312 1312 1970-01-14 21:20 +0000
1313 1313 1970-01-13 17:33 +0000
1314 1314 1970-01-12 13:46 +0000
1315 1315
1316 1316 $ hg log --template '{date|isodatesec}\n'
1317 1317 2020-01-01 10:01:00 +0000
1318 1318 1970-01-12 13:46:40 +0000
1319 1319 1970-01-18 08:40:01 +0000
1320 1320 1970-01-18 08:40:00 +0000
1321 1321 1970-01-17 04:53:20 +0000
1322 1322 1970-01-16 01:06:40 +0000
1323 1323 1970-01-14 21:20:00 +0000
1324 1324 1970-01-13 17:33:20 +0000
1325 1325 1970-01-12 13:46:40 +0000
1326 1326
1327 1327 $ hg log --template '{date|rfc822date}\n'
1328 1328 Wed, 01 Jan 2020 10:01:00 +0000
1329 1329 Mon, 12 Jan 1970 13:46:40 +0000
1330 1330 Sun, 18 Jan 1970 08:40:01 +0000
1331 1331 Sun, 18 Jan 1970 08:40:00 +0000
1332 1332 Sat, 17 Jan 1970 04:53:20 +0000
1333 1333 Fri, 16 Jan 1970 01:06:40 +0000
1334 1334 Wed, 14 Jan 1970 21:20:00 +0000
1335 1335 Tue, 13 Jan 1970 17:33:20 +0000
1336 1336 Mon, 12 Jan 1970 13:46:40 +0000
1337 1337
1338 1338 $ hg log --template '{desc|firstline}\n'
1339 1339 third
1340 1340 second
1341 1341 merge
1342 1342 new head
1343 1343 new branch
1344 1344 no user, no domain
1345 1345 no person
1346 1346 other 1
1347 1347 line 1
1348 1348
1349 1349 $ hg log --template '{node|short}\n'
1350 1350 95c24699272e
1351 1351 29114dbae42b
1352 1352 d41e714fe50d
1353 1353 13207e5a10d9
1354 1354 bbe44766e73d
1355 1355 10e46f2dcbf4
1356 1356 97054abb4ab8
1357 1357 b608e9d1a3f0
1358 1358 1e4e1b8f71e0
1359 1359
1360 1360 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
1361 1361 <changeset author="test"/>
1362 1362 <changeset author="User Name &lt;user@hostname&gt;"/>
1363 1363 <changeset author="person"/>
1364 1364 <changeset author="person"/>
1365 1365 <changeset author="person"/>
1366 1366 <changeset author="person"/>
1367 1367 <changeset author="other@place"/>
1368 1368 <changeset author="A. N. Other &lt;other@place&gt;"/>
1369 1369 <changeset author="User Name &lt;user@hostname&gt;"/>
1370 1370
1371 1371 $ hg log --template '{rev}: {children}\n'
1372 1372 8:
1373 1373 7: 8:95c24699272e
1374 1374 6:
1375 1375 5: 6:d41e714fe50d
1376 1376 4: 6:d41e714fe50d
1377 1377 3: 4:bbe44766e73d 5:13207e5a10d9
1378 1378 2: 3:10e46f2dcbf4
1379 1379 1: 2:97054abb4ab8
1380 1380 0: 1:b608e9d1a3f0
1381 1381
1382 1382 Formatnode filter works:
1383 1383
1384 1384 $ hg -q log -r 0 --template '{node|formatnode}\n'
1385 1385 1e4e1b8f71e0
1386 1386
1387 1387 $ hg log -r 0 --template '{node|formatnode}\n'
1388 1388 1e4e1b8f71e0
1389 1389
1390 1390 $ hg -v log -r 0 --template '{node|formatnode}\n'
1391 1391 1e4e1b8f71e0
1392 1392
1393 1393 $ hg --debug log -r 0 --template '{node|formatnode}\n'
1394 1394 1e4e1b8f71e05681d422154f5421e385fec3454f
1395 1395
1396 1396 Age filter:
1397 1397
1398 1398 $ hg log --template '{date|age}\n' > /dev/null || exit 1
1399 1399
1400 1400 >>> from datetime import datetime, timedelta
1401 1401 >>> fp = open('a', 'w')
1402 1402 >>> n = datetime.now() + timedelta(366 * 7)
1403 1403 >>> fp.write('%d-%d-%d 00:00' % (n.year, n.month, n.day))
1404 1404 >>> fp.close()
1405 1405 $ hg add a
1406 1406 $ hg commit -m future -d "`cat a`"
1407 1407
1408 1408 $ hg log -l1 --template '{date|age}\n'
1409 1409 7 years from now
1410 1410
1411 1411 Error on syntax:
1412 1412
1413 1413 $ echo 'x = "f' >> t
1414 1414 $ hg log
1415 1415 abort: t:3: unmatched quotes
1416 1416 [255]
1417 1417
1418 1418 Behind the scenes, this will throw TypeError
1419 1419
1420 1420 $ hg log -l 3 --template '{date|obfuscate}\n'
1421 1421 abort: template filter 'obfuscate' is not compatible with keyword 'date'
1422 1422 [255]
1423 1423
1424 1424 Behind the scenes, this will throw a ValueError
1425 1425
1426 1426 $ hg log -l 3 --template 'line: {desc|shortdate}\n'
1427 1427 abort: template filter 'shortdate' is not compatible with keyword 'desc'
1428 1428 [255]
1429 1429
1430 1430 Behind the scenes, this will throw AttributeError
1431 1431
1432 1432 $ hg log -l 3 --template 'line: {date|escape}\n'
1433 1433 abort: template filter 'escape' is not compatible with keyword 'date'
1434 1434 [255]
1435 1435
1436 1436 Behind the scenes, this will throw ValueError
1437 1437
1438 1438 $ hg tip --template '{author|email|date}\n'
1439 1439 abort: template filter 'datefilter' is not compatible with keyword 'author'
1440 1440 [255]
1441 1441
1442 1442 $ cd ..
1443 1443
1444 1444
1445 1445 latesttag:
1446 1446
1447 1447 $ hg init latesttag
1448 1448 $ cd latesttag
1449 1449
1450 1450 $ echo a > file
1451 1451 $ hg ci -Am a -d '0 0'
1452 1452 adding file
1453 1453
1454 1454 $ echo b >> file
1455 1455 $ hg ci -m b -d '1 0'
1456 1456
1457 1457 $ echo c >> head1
1458 1458 $ hg ci -Am h1c -d '2 0'
1459 1459 adding head1
1460 1460
1461 1461 $ hg update -q 1
1462 1462 $ echo d >> head2
1463 1463 $ hg ci -Am h2d -d '3 0'
1464 1464 adding head2
1465 1465 created new head
1466 1466
1467 1467 $ echo e >> head2
1468 1468 $ hg ci -m h2e -d '4 0'
1469 1469
1470 1470 $ hg merge -q
1471 1471 $ hg ci -m merge -d '5 -3600'
1472 1472
1473 1473 No tag set:
1474 1474
1475 1475 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
1476 1476 5: null+5
1477 1477 4: null+4
1478 1478 3: null+3
1479 1479 2: null+3
1480 1480 1: null+2
1481 1481 0: null+1
1482 1482
1483 1483 One common tag: longuest path wins:
1484 1484
1485 1485 $ hg tag -r 1 -m t1 -d '6 0' t1
1486 1486 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
1487 1487 6: t1+4
1488 1488 5: t1+3
1489 1489 4: t1+2
1490 1490 3: t1+1
1491 1491 2: t1+1
1492 1492 1: t1+0
1493 1493 0: null+1
1494 1494
1495 1495 One ancestor tag: more recent wins:
1496 1496
1497 1497 $ hg tag -r 2 -m t2 -d '7 0' t2
1498 1498 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
1499 1499 7: t2+3
1500 1500 6: t2+2
1501 1501 5: t2+1
1502 1502 4: t1+2
1503 1503 3: t1+1
1504 1504 2: t2+0
1505 1505 1: t1+0
1506 1506 0: null+1
1507 1507
1508 1508 Two branch tags: more recent wins:
1509 1509
1510 1510 $ hg tag -r 3 -m t3 -d '8 0' t3
1511 1511 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
1512 1512 8: t3+5
1513 1513 7: t3+4
1514 1514 6: t3+3
1515 1515 5: t3+2
1516 1516 4: t3+1
1517 1517 3: t3+0
1518 1518 2: t2+0
1519 1519 1: t1+0
1520 1520 0: null+1
1521 1521
1522 1522 Merged tag overrides:
1523 1523
1524 1524 $ hg tag -r 5 -m t5 -d '9 0' t5
1525 1525 $ hg tag -r 3 -m at3 -d '10 0' at3
1526 1526 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
1527 1527 10: t5+5
1528 1528 9: t5+4
1529 1529 8: t5+3
1530 1530 7: t5+2
1531 1531 6: t5+1
1532 1532 5: t5+0
1533 1533 4: at3:t3+1
1534 1534 3: at3:t3+0
1535 1535 2: t2+0
1536 1536 1: t1+0
1537 1537 0: null+1
1538 1538
1539 1539 $ cd ..
1540 1540
1541 1541
1542 1542 Style path expansion: issue1948 - ui.style option doesn't work on OSX
1543 1543 if it is a relative path
1544 1544
1545 1545 $ mkdir -p home/styles
1546 1546
1547 1547 $ cat > home/styles/teststyle <<EOF
1548 1548 > changeset = 'test {rev}:{node|short}\n'
1549 1549 > EOF
1550 1550
1551 1551 $ HOME=`pwd`/home; export HOME
1552 1552
1553 1553 $ cat > latesttag/.hg/hgrc <<EOF
1554 1554 > [ui]
1555 1555 > style = ~/styles/teststyle
1556 1556 > EOF
1557 1557
1558 1558 $ hg -R latesttag tip
1559 1559 test 10:9b4a630e5f5f
1560 1560
1561 1561 Test recursive showlist template (issue1989):
1562 1562
1563 1563 $ cat > style1989 <<EOF
1564 1564 > changeset = '{file_mods}{manifest}{extras}'
1565 1565 > file_mod = 'M|{author|person}\n'
1566 1566 > manifest = '{rev},{author}\n'
1567 1567 > extra = '{key}: {author}\n'
1568 1568 > EOF
1569 1569
1570 1570 $ hg -R latesttag log -r tip --style=style1989
1571 1571 M|test
1572 1572 10,test
1573 1573 branch: test
1574 1574
1575 1575 Test new-style inline templating:
1576 1576
1577 1577 $ hg log -R latesttag -r tip --template 'modified files: {file_mods % " {file}\n"}\n'
1578 1578 modified files: .hgtags
1579 1579
1580 1580 Test the sub function of templating for expansion:
1581 1581
1582 1582 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
1583 1583 xx
1584 1584
1585 1585 Test the strip function with chars specified:
1586 1586
1587 1587 $ hg log -R latesttag --template '{desc}\n'
1588 1588 at3
1589 1589 t5
1590 1590 t3
1591 1591 t2
1592 1592 t1
1593 1593 merge
1594 1594 h2e
1595 1595 h2d
1596 1596 h1c
1597 1597 b
1598 1598 a
1599 1599
1600 1600 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
1601 1601 at3
1602 1602 5
1603 1603 3
1604 1604 2
1605 1605 1
1606 1606 merg
1607 1607 h2
1608 1608 h2d
1609 1609 h1c
1610 1610 b
1611 1611 a
1612 1612
1613 1613 Test date format:
1614 1614
1615 1615 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
1616 1616 date: 70 01 01 10 +0000
1617 1617 date: 70 01 01 09 +0000
1618 1618 date: 70 01 01 08 +0000
1619 1619 date: 70 01 01 07 +0000
1620 1620 date: 70 01 01 06 +0000
1621 1621 date: 70 01 01 05 +0100
1622 1622 date: 70 01 01 04 +0000
1623 1623 date: 70 01 01 03 +0000
1624 1624 date: 70 01 01 02 +0000
1625 1625 date: 70 01 01 01 +0000
1626 1626 date: 70 01 01 00 +0000
1627 1627
1628 1628 Test string escaping:
1629 1629
1630 1630 $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
1631 1631 >
1632 1632 <>\n<[>
1633 1633 <>\n<]>
1634 1634 <>\n<
1635 1635
1636 "string-escape"-ed "\x5c\x786e" becomes r"\x6e" (once) or r"n" (twice)
1637
1638 $ hg log -R a -r 0 --template '{if("1", "\x5c\x786e", "NG")}\n'
1639 \x6e
1640 $ hg log -R a -r 0 --template '{if("1", r"\x5c\x786e", "NG")}\n'
1641 \x5c\x786e
1642 $ hg log -R a -r 0 --template '{if("", "NG", "\x5c\x786e")}\n'
1643 \x6e
1644 $ hg log -R a -r 0 --template '{if("", "NG", r"\x5c\x786e")}\n'
1645 \x5c\x786e
1646
1647 $ hg log -R a -r 2 --template '{ifeq("no perso\x6e", desc, "\x5c\x786e", "NG")}\n'
1648 \x6e
1649 $ hg log -R a -r 2 --template '{ifeq(r"no perso\x6e", desc, "NG", r"\x5c\x786e")}\n'
1650 \x5c\x786e
1651 $ hg log -R a -r 2 --template '{ifeq(desc, "no perso\x6e", "\x5c\x786e", "NG")}\n'
1652 \x6e
1653 $ hg log -R a -r 2 --template '{ifeq(desc, r"no perso\x6e", "NG", r"\x5c\x786e")}\n'
1654 \x5c\x786e
1655
1656 $ hg log -R a -r 8 --template '{join(files, "\n")}\n'
1657 fourth
1658 second
1659 third
1660 $ hg log -R a -r 8 --template '{join(files, r"\n")}\n'
1661 fourth\nsecond\nthird
1662
1663 $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", "htm\x6c")}'
1664 <p>
1665 1st
1666 </p>
1667 <p>
1668 2nd
1669 </p>
1670 $ hg log -R a -r 2 --template '{rstdoc(r"1st\n\n2nd", "html")}'
1671 <p>
1672 1st\n\n2nd
1673 </p>
1674 $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", r"htm\x6c")}'
1675 1st
1676
1677 2nd
1678
1679 $ hg log -R a -r 2 --template '{strip(desc, "\x6e")}\n'
1680 o perso
1681 $ hg log -R a -r 2 --template '{strip(desc, r"\x6e")}\n'
1682 no person
1683 $ hg log -R a -r 2 --template '{strip("no perso\x6e", "\x6e")}\n'
1684 o perso
1685 $ hg log -R a -r 2 --template '{strip(r"no perso\x6e", r"\x6e")}\n'
1686 no perso
1687
1688 $ hg log -R a -r 2 --template '{sub("\\x6e", "\x2d", desc)}\n'
1689 -o perso-
1690 $ hg log -R a -r 2 --template '{sub(r"\\x6e", "-", desc)}\n'
1691 no person
1692 $ hg log -R a -r 2 --template '{sub("n", r"\x2d", desc)}\n'
1693 \x2do perso\x2d
1694 $ hg log -R a -r 2 --template '{sub("n", "\x2d", "no perso\x6e")}\n'
1695 -o perso-
1696 $ hg log -R a -r 2 --template '{sub("n", r"\x2d", r"no perso\x6e")}\n'
1697 \x2do perso\x6e
1698
1699 $ hg log -R a -r 8 --template '{files % "{file}\n"}'
1700 fourth
1701 second
1702 third
1703 $ hg log -R a -r 8 --template '{files % r"{file}\n"}\n'
1704 fourth\nsecond\nthird\n
1705
1706 Test string escapeing in nested expression:
1707
1708 $ hg log -R a -r 8 --template '{ifeq(r"\x6e", if("1", "\x5c\x786e"), join(files, "\x5c\x786e"))}\n'
1709 fourth\x6esecond\x6ethird
1710 $ hg log -R a -r 8 --template '{ifeq(if("1", r"\x6e"), "\x5c\x786e", join(files, "\x5c\x786e"))}\n'
1711 fourth\x6esecond\x6ethird
1712
1713 $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", "\x5c\x786e"))}\n'
1714 fourth\x6esecond\x6ethird
1715 $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", r"\x5c\x786e"))}\n'
1716 fourth\x5c\x786esecond\x5c\x786ethird
1717
1718 $ hg log -R a -r 3:4 --template '{rev}:{sub(if("1", "\x6e"), ifeq(branch, "foo", r"\x5c\x786e", "\x5c\x786e"), desc)}\n'
1719 3:\x6eo user, \x6eo domai\x6e
1720 4:\x5c\x786eew bra\x5c\x786ech
1721
1636 1722 Test recursive evaluation:
1637 1723
1638 1724 $ hg init r
1639 1725 $ cd r
1640 1726 $ echo a > a
1641 1727 $ hg ci -Am '{rev}'
1642 1728 adding a
1643 1729 $ hg log -r 0 --template '{if(rev, desc)}\n'
1644 1730 {rev}
1645 1731 $ hg log -r 0 --template '{if(rev, "{author} {rev}")}\n'
1646 1732 test 0
1647 1733
1734 $ hg branch -q 'text.{rev}'
1735 $ echo aa >> aa
1736 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
1737
1738 $ hg log -l1 --template '{fill(desc, "20", author, branch)}'
1739 {node|short}desc to
1740 text.{rev}be wrapped
1741 text.{rev}desc to be
1742 text.{rev}wrapped (no-eol)
1743 $ hg log -l1 --template '{fill(desc, "20", "{node|short}:", "text.{rev}:")}'
1744 bcc7ff960b8e:desc to
1745 text.1:be wrapped
1746 text.1:desc to be
1747 text.1:wrapped (no-eol)
1748
1749 $ hg log -l 1 --template '{sub(r"[0-9]", "-", author)}'
1750 {node|short} (no-eol)
1751 $ hg log -l 1 --template '{sub(r"[0-9]", "-", "{node|short}")}'
1752 bcc-ff---b-e (no-eol)
1753
1754 $ cat >> .hg/hgrc <<EOF
1755 > [extensions]
1756 > color=
1757 > [color]
1758 > mode=ansi
1759 > text.{rev} = red
1760 > text.1 = green
1761 > EOF
1762 $ hg log --color=always -l 1 --template '{label(branch, "text\n")}'
1763 \x1b[0;31mtext\x1b[0m (esc)
1764 $ hg log --color=always -l 1 --template '{label("text.{rev}", "text\n")}'
1765 \x1b[0;32mtext\x1b[0m (esc)
1766
1648 1767 Test branches inside if statement:
1649 1768
1650 1769 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
1651 1770 no
1652 1771
1653 1772 Test shortest(node) function:
1654 1773
1655 1774 $ echo b > b
1656 1775 $ hg ci -qAm b
1657 1776 $ hg log --template '{shortest(node)}\n'
1658 d97c
1777 e777
1778 bcc7
1659 1779 f776
1660 1780 $ hg log --template '{shortest(node, 10)}\n'
1661 d97c383ae3
1781 e777603221
1782 bcc7ff960b
1662 1783 f7769ec2ab
1663 1784
1664 1785 Test pad function
1665 1786
1666 1787 $ hg log --template '{pad(rev, 20)} {author|user}\n'
1667 1 test
1788 2 test
1789 1 {node|short}
1668 1790 0 test
1669 1791
1670 1792 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
1671 1 test
1793 2 test
1794 1 {node|short}
1672 1795 0 test
1673 1796
1674 1797 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
1675 1------------------- test
1798 2------------------- test
1799 1------------------- {node|short}
1676 1800 0------------------- test
1677 1801
1678 1802 Test ifcontains function
1679 1803
1680 1804 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
1805 2 did not add a
1681 1806 1 did not add a
1682 1807 0 added a
1683 1808
1684 1809 Test revset function
1685 1810
1686 1811 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
1687 1 current rev
1812 2 current rev
1813 1 not current rev
1688 1814 0 not current rev
1689 1815
1690 1816 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
1817 2 Parents: 1
1691 1818 1 Parents: 0
1692 1819 0 Parents:
1693 1820
1694 1821 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
1822 Rev: 2
1823 Ancestor: 0
1824 Ancestor: 1
1825 Ancestor: 2
1826
1695 1827 Rev: 1
1696 1828 Ancestor: 0
1697 1829 Ancestor: 1
1698 1830
1699 1831 Rev: 0
1700 1832 Ancestor: 0
1701 1833
1702 1834 Test current bookmark templating
1703 1835
1704 1836 $ hg book foo
1705 1837 $ hg book bar
1706 1838 $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, current, \"*\")} '}\n"
1707 1 bar* foo
1839 2 bar* foo
1840 1
1708 1841 0
1842
1843 Test stringify on sub expressions
1844
1845 $ cd ..
1846 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
1847 fourth, second, third
1848 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
1849 abc
1850
General Comments 0
You need to be logged in to leave comments. Login now