##// END OF EJS Templates
formatter: add support for parts map of [templates] section...
Yuya Nishihara -
r32952:61b60b28 default
parent child Browse files
Show More
@@ -1,518 +1,523 b''
1 1 # formatter.py - generic output formatting for mercurial
2 2 #
3 3 # Copyright 2012 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """Generic output formatting for Mercurial
9 9
10 10 The formatter provides API to show data in various ways. The following
11 11 functions should be used in place of ui.write():
12 12
13 13 - fm.write() for unconditional output
14 14 - fm.condwrite() to show some extra data conditionally in plain output
15 15 - fm.context() to provide changectx to template output
16 16 - fm.data() to provide extra data to JSON or template output
17 17 - fm.plain() to show raw text that isn't provided to JSON or template output
18 18
19 19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
20 20 beforehand so the data is converted to the appropriate data type. Use
21 21 fm.isplain() if you need to convert or format data conditionally which isn't
22 22 supported by the formatter API.
23 23
24 24 To build nested structure (i.e. a list of dicts), use fm.nested().
25 25
26 26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
27 27
28 28 fm.condwrite() vs 'if cond:':
29 29
30 30 In most cases, use fm.condwrite() so users can selectively show the data
31 31 in template output. If it's costly to build data, use plain 'if cond:' with
32 32 fm.write().
33 33
34 34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
35 35
36 36 fm.nested() should be used to form a tree structure (a list of dicts of
37 37 lists of dicts...) which can be accessed through template keywords, e.g.
38 38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
39 39 exports a dict-type object to template, which can be accessed by e.g.
40 40 "{get(foo, key)}" function.
41 41
42 42 Doctest helper:
43 43
44 44 >>> def show(fn, verbose=False, **opts):
45 45 ... import sys
46 46 ... from . import ui as uimod
47 47 ... ui = uimod.ui()
48 48 ... ui.fout = sys.stdout # redirect to doctest
49 49 ... ui.verbose = verbose
50 50 ... return fn(ui, ui.formatter(fn.__name__, opts))
51 51
52 52 Basic example:
53 53
54 54 >>> def files(ui, fm):
55 55 ... files = [('foo', 123, (0, 0)), ('bar', 456, (1, 0))]
56 56 ... for f in files:
57 57 ... fm.startitem()
58 58 ... fm.write('path', '%s', f[0])
59 59 ... fm.condwrite(ui.verbose, 'date', ' %s',
60 60 ... fm.formatdate(f[2], '%Y-%m-%d %H:%M:%S'))
61 61 ... fm.data(size=f[1])
62 62 ... fm.plain('\\n')
63 63 ... fm.end()
64 64 >>> show(files)
65 65 foo
66 66 bar
67 67 >>> show(files, verbose=True)
68 68 foo 1970-01-01 00:00:00
69 69 bar 1970-01-01 00:00:01
70 70 >>> show(files, template='json')
71 71 [
72 72 {
73 73 "date": [0, 0],
74 74 "path": "foo",
75 75 "size": 123
76 76 },
77 77 {
78 78 "date": [1, 0],
79 79 "path": "bar",
80 80 "size": 456
81 81 }
82 82 ]
83 83 >>> show(files, template='path: {path}\\ndate: {date|rfc3339date}\\n')
84 84 path: foo
85 85 date: 1970-01-01T00:00:00+00:00
86 86 path: bar
87 87 date: 1970-01-01T00:00:01+00:00
88 88
89 89 Nested example:
90 90
91 91 >>> def subrepos(ui, fm):
92 92 ... fm.startitem()
93 93 ... fm.write('repo', '[%s]\\n', 'baz')
94 94 ... files(ui, fm.nested('files'))
95 95 ... fm.end()
96 96 >>> show(subrepos)
97 97 [baz]
98 98 foo
99 99 bar
100 100 >>> show(subrepos, template='{repo}: {join(files % "{path}", ", ")}\\n')
101 101 baz: foo, bar
102 102 """
103 103
104 104 from __future__ import absolute_import
105 105
106 106 import collections
107 107 import contextlib
108 108 import itertools
109 109 import os
110 110
111 111 from .i18n import _
112 112 from .node import (
113 113 hex,
114 114 short,
115 115 )
116 116
117 117 from . import (
118 118 error,
119 119 pycompat,
120 120 templatefilters,
121 121 templatekw,
122 122 templater,
123 123 util,
124 124 )
125 125
126 126 pickle = util.pickle
127 127
128 128 class _nullconverter(object):
129 129 '''convert non-primitive data types to be processed by formatter'''
130 130 @staticmethod
131 131 def formatdate(date, fmt):
132 132 '''convert date tuple to appropriate format'''
133 133 return date
134 134 @staticmethod
135 135 def formatdict(data, key, value, fmt, sep):
136 136 '''convert dict or key-value pairs to appropriate dict format'''
137 137 # use plain dict instead of util.sortdict so that data can be
138 138 # serialized as a builtin dict in pickle output
139 139 return dict(data)
140 140 @staticmethod
141 141 def formatlist(data, name, fmt, sep):
142 142 '''convert iterable to appropriate list format'''
143 143 return list(data)
144 144
145 145 class baseformatter(object):
146 146 def __init__(self, ui, topic, opts, converter):
147 147 self._ui = ui
148 148 self._topic = topic
149 149 self._style = opts.get("style")
150 150 self._template = opts.get("template")
151 151 self._converter = converter
152 152 self._item = None
153 153 # function to convert node to string suitable for this output
154 154 self.hexfunc = hex
155 155 def __enter__(self):
156 156 return self
157 157 def __exit__(self, exctype, excvalue, traceback):
158 158 if exctype is None:
159 159 self.end()
160 160 def _showitem(self):
161 161 '''show a formatted item once all data is collected'''
162 162 pass
163 163 def startitem(self):
164 164 '''begin an item in the format list'''
165 165 if self._item is not None:
166 166 self._showitem()
167 167 self._item = {}
168 168 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
169 169 '''convert date tuple to appropriate format'''
170 170 return self._converter.formatdate(date, fmt)
171 171 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
172 172 '''convert dict or key-value pairs to appropriate dict format'''
173 173 return self._converter.formatdict(data, key, value, fmt, sep)
174 174 def formatlist(self, data, name, fmt='%s', sep=' '):
175 175 '''convert iterable to appropriate list format'''
176 176 # name is mandatory argument for now, but it could be optional if
177 177 # we have default template keyword, e.g. {item}
178 178 return self._converter.formatlist(data, name, fmt, sep)
179 179 def context(self, **ctxs):
180 180 '''insert context objects to be used to render template keywords'''
181 181 pass
182 182 def data(self, **data):
183 183 '''insert data into item that's not shown in default output'''
184 184 data = pycompat.byteskwargs(data)
185 185 self._item.update(data)
186 186 def write(self, fields, deftext, *fielddata, **opts):
187 187 '''do default text output while assigning data to item'''
188 188 fieldkeys = fields.split()
189 189 assert len(fieldkeys) == len(fielddata)
190 190 self._item.update(zip(fieldkeys, fielddata))
191 191 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
192 192 '''do conditional write (primarily for plain formatter)'''
193 193 fieldkeys = fields.split()
194 194 assert len(fieldkeys) == len(fielddata)
195 195 self._item.update(zip(fieldkeys, fielddata))
196 196 def plain(self, text, **opts):
197 197 '''show raw text for non-templated mode'''
198 198 pass
199 199 def isplain(self):
200 200 '''check for plain formatter usage'''
201 201 return False
202 202 def nested(self, field):
203 203 '''sub formatter to store nested data in the specified field'''
204 204 self._item[field] = data = []
205 205 return _nestedformatter(self._ui, self._converter, data)
206 206 def end(self):
207 207 '''end output for the formatter'''
208 208 if self._item is not None:
209 209 self._showitem()
210 210
211 211 def nullformatter(ui, topic):
212 212 '''formatter that prints nothing'''
213 213 return baseformatter(ui, topic, opts={}, converter=_nullconverter)
214 214
215 215 class _nestedformatter(baseformatter):
216 216 '''build sub items and store them in the parent formatter'''
217 217 def __init__(self, ui, converter, data):
218 218 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
219 219 self._data = data
220 220 def _showitem(self):
221 221 self._data.append(self._item)
222 222
223 223 def _iteritems(data):
224 224 '''iterate key-value pairs in stable order'''
225 225 if isinstance(data, dict):
226 226 return sorted(data.iteritems())
227 227 return data
228 228
229 229 class _plainconverter(object):
230 230 '''convert non-primitive data types to text'''
231 231 @staticmethod
232 232 def formatdate(date, fmt):
233 233 '''stringify date tuple in the given format'''
234 234 return util.datestr(date, fmt)
235 235 @staticmethod
236 236 def formatdict(data, key, value, fmt, sep):
237 237 '''stringify key-value pairs separated by sep'''
238 238 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
239 239 @staticmethod
240 240 def formatlist(data, name, fmt, sep):
241 241 '''stringify iterable separated by sep'''
242 242 return sep.join(fmt % e for e in data)
243 243
244 244 class plainformatter(baseformatter):
245 245 '''the default text output scheme'''
246 246 def __init__(self, ui, out, topic, opts):
247 247 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
248 248 if ui.debugflag:
249 249 self.hexfunc = hex
250 250 else:
251 251 self.hexfunc = short
252 252 if ui is out:
253 253 self._write = ui.write
254 254 else:
255 255 self._write = lambda s, **opts: out.write(s)
256 256 def startitem(self):
257 257 pass
258 258 def data(self, **data):
259 259 pass
260 260 def write(self, fields, deftext, *fielddata, **opts):
261 261 self._write(deftext % fielddata, **opts)
262 262 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
263 263 '''do conditional write'''
264 264 if cond:
265 265 self._write(deftext % fielddata, **opts)
266 266 def plain(self, text, **opts):
267 267 self._write(text, **opts)
268 268 def isplain(self):
269 269 return True
270 270 def nested(self, field):
271 271 # nested data will be directly written to ui
272 272 return self
273 273 def end(self):
274 274 pass
275 275
276 276 class debugformatter(baseformatter):
277 277 def __init__(self, ui, out, topic, opts):
278 278 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
279 279 self._out = out
280 280 self._out.write("%s = [\n" % self._topic)
281 281 def _showitem(self):
282 282 self._out.write(" " + repr(self._item) + ",\n")
283 283 def end(self):
284 284 baseformatter.end(self)
285 285 self._out.write("]\n")
286 286
287 287 class pickleformatter(baseformatter):
288 288 def __init__(self, ui, out, topic, opts):
289 289 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
290 290 self._out = out
291 291 self._data = []
292 292 def _showitem(self):
293 293 self._data.append(self._item)
294 294 def end(self):
295 295 baseformatter.end(self)
296 296 self._out.write(pickle.dumps(self._data))
297 297
298 298 class jsonformatter(baseformatter):
299 299 def __init__(self, ui, out, topic, opts):
300 300 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
301 301 self._out = out
302 302 self._out.write("[")
303 303 self._first = True
304 304 def _showitem(self):
305 305 if self._first:
306 306 self._first = False
307 307 else:
308 308 self._out.write(",")
309 309
310 310 self._out.write("\n {\n")
311 311 first = True
312 312 for k, v in sorted(self._item.items()):
313 313 if first:
314 314 first = False
315 315 else:
316 316 self._out.write(",\n")
317 317 u = templatefilters.json(v, paranoid=False)
318 318 self._out.write(' "%s": %s' % (k, u))
319 319 self._out.write("\n }")
320 320 def end(self):
321 321 baseformatter.end(self)
322 322 self._out.write("\n]\n")
323 323
324 324 class _templateconverter(object):
325 325 '''convert non-primitive data types to be processed by templater'''
326 326 @staticmethod
327 327 def formatdate(date, fmt):
328 328 '''return date tuple'''
329 329 return date
330 330 @staticmethod
331 331 def formatdict(data, key, value, fmt, sep):
332 332 '''build object that can be evaluated as either plain string or dict'''
333 333 data = util.sortdict(_iteritems(data))
334 334 def f():
335 335 yield _plainconverter.formatdict(data, key, value, fmt, sep)
336 336 return templatekw.hybriddict(data, key=key, value=value, fmt=fmt,
337 337 gen=f())
338 338 @staticmethod
339 339 def formatlist(data, name, fmt, sep):
340 340 '''build object that can be evaluated as either plain string or list'''
341 341 data = list(data)
342 342 def f():
343 343 yield _plainconverter.formatlist(data, name, fmt, sep)
344 344 return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f())
345 345
346 346 class templateformatter(baseformatter):
347 347 def __init__(self, ui, out, topic, opts):
348 348 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
349 349 self._out = out
350 350 spec = lookuptemplate(ui, topic, opts.get('template', ''))
351 351 self._tref = spec.ref
352 352 self._t = loadtemplater(ui, spec, cache=templatekw.defaulttempl)
353 353 self._parts = templatepartsmap(spec, self._t,
354 354 ['docheader', 'docfooter', 'separator'])
355 355 self._counter = itertools.count()
356 356 self._cache = {} # for templatekw/funcs to store reusable data
357 357 self._renderitem('docheader', {})
358 358
359 359 def context(self, **ctxs):
360 360 '''insert context objects to be used to render template keywords'''
361 361 ctxs = pycompat.byteskwargs(ctxs)
362 362 assert all(k == 'ctx' for k in ctxs)
363 363 self._item.update(ctxs)
364 364
365 365 def _showitem(self):
366 366 item = self._item.copy()
367 367 item['index'] = index = next(self._counter)
368 368 if index > 0:
369 369 self._renderitem('separator', {})
370 370 self._renderitem(self._tref, item)
371 371
372 372 def _renderitem(self, part, item):
373 373 if part not in self._parts:
374 374 return
375 375 ref = self._parts[part]
376 376
377 377 # TODO: add support for filectx. probably each template keyword or
378 378 # function will have to declare dependent resources. e.g.
379 379 # @templatekeyword(..., requires=('ctx',))
380 380 props = {}
381 381 if 'ctx' in item:
382 382 props.update(templatekw.keywords)
383 383 # explicitly-defined fields precede templatekw
384 384 props.update(item)
385 385 if 'ctx' in item:
386 386 # but template resources must be always available
387 387 props['templ'] = self._t
388 388 props['repo'] = props['ctx'].repo()
389 389 props['revcache'] = {}
390 390 props = pycompat.strkwargs(props)
391 391 g = self._t(ref, ui=self._ui, cache=self._cache, **props)
392 392 self._out.write(templater.stringify(g))
393 393
394 394 def end(self):
395 395 baseformatter.end(self)
396 396 self._renderitem('docfooter', {})
397 397
398 398 templatespec = collections.namedtuple(r'templatespec',
399 399 r'ref tmpl mapfile')
400 400
401 401 def lookuptemplate(ui, topic, tmpl):
402 402 """Find the template matching the given -T/--template spec 'tmpl'
403 403
404 404 'tmpl' can be any of the following:
405 405
406 406 - a literal template (e.g. '{rev}')
407 407 - a map-file name or path (e.g. 'changelog')
408 408 - a reference to [templates] in config file
409 409 - a path to raw template file
410 410
411 411 A map file defines a stand-alone template environment. If a map file
412 412 selected, all templates defined in the file will be loaded, and the
413 413 template matching the given topic will be rendered. No aliases will be
414 414 loaded from user config.
415 415
416 416 If no map file selected, all templates in [templates] section will be
417 417 available as well as aliases in [templatealias].
418 418 """
419 419
420 420 # looks like a literal template?
421 421 if '{' in tmpl:
422 422 return templatespec('', tmpl, None)
423 423
424 424 # perhaps a stock style?
425 425 if not os.path.split(tmpl)[0]:
426 426 mapname = (templater.templatepath('map-cmdline.' + tmpl)
427 427 or templater.templatepath(tmpl))
428 428 if mapname and os.path.isfile(mapname):
429 429 return templatespec(topic, None, mapname)
430 430
431 431 # perhaps it's a reference to [templates]
432 432 if ui.config('templates', tmpl):
433 433 return templatespec(tmpl, None, None)
434 434
435 435 if tmpl == 'list':
436 436 ui.write(_("available styles: %s\n") % templater.stylelist())
437 437 raise error.Abort(_("specify a template"))
438 438
439 439 # perhaps it's a path to a map or a template
440 440 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
441 441 # is it a mapfile for a style?
442 442 if os.path.basename(tmpl).startswith("map-"):
443 443 return templatespec(topic, None, os.path.realpath(tmpl))
444 444 with util.posixfile(tmpl, 'rb') as f:
445 445 tmpl = f.read()
446 446 return templatespec('', tmpl, None)
447 447
448 448 # constant string?
449 449 return templatespec('', tmpl, None)
450 450
451 451 def templatepartsmap(spec, t, partnames):
452 452 """Create a mapping of {part: ref}"""
453 453 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
454 454 if spec.mapfile:
455 455 partsmap.update((p, p) for p in partnames if p in t)
456 elif spec.ref:
457 for part in partnames:
458 ref = '%s:%s' % (spec.ref, part) # select config sub-section
459 if ref in t:
460 partsmap[part] = ref
456 461 return partsmap
457 462
458 463 def loadtemplater(ui, spec, cache=None):
459 464 """Create a templater from either a literal template or loading from
460 465 a map file"""
461 466 assert not (spec.tmpl and spec.mapfile)
462 467 if spec.mapfile:
463 468 return templater.templater.frommapfile(spec.mapfile, cache=cache)
464 469 return maketemplater(ui, spec.tmpl, cache=cache)
465 470
466 471 def maketemplater(ui, tmpl, cache=None):
467 472 """Create a templater from a string template 'tmpl'"""
468 473 aliases = ui.configitems('templatealias')
469 474 t = templater.templater(cache=cache, aliases=aliases)
470 475 t.cache.update((k, templater.unquotestring(v))
471 476 for k, v in ui.configitems('templates'))
472 477 if tmpl:
473 478 t.cache[''] = tmpl
474 479 return t
475 480
476 481 def formatter(ui, out, topic, opts):
477 482 template = opts.get("template", "")
478 483 if template == "json":
479 484 return jsonformatter(ui, out, topic, opts)
480 485 elif template == "pickle":
481 486 return pickleformatter(ui, out, topic, opts)
482 487 elif template == "debug":
483 488 return debugformatter(ui, out, topic, opts)
484 489 elif template != "":
485 490 return templateformatter(ui, out, topic, opts)
486 491 # developer config: ui.formatdebug
487 492 elif ui.configbool('ui', 'formatdebug'):
488 493 return debugformatter(ui, out, topic, opts)
489 494 # deprecated config: ui.formatjson
490 495 elif ui.configbool('ui', 'formatjson'):
491 496 return jsonformatter(ui, out, topic, opts)
492 497 return plainformatter(ui, out, topic, opts)
493 498
494 499 @contextlib.contextmanager
495 500 def openformatter(ui, filename, topic, opts):
496 501 """Create a formatter that writes outputs to the specified file
497 502
498 503 Must be invoked using the 'with' statement.
499 504 """
500 505 with util.posixfile(filename, 'wb') as out:
501 506 with formatter(ui, out, topic, opts) as fm:
502 507 yield fm
503 508
504 509 @contextlib.contextmanager
505 510 def _neverending(fm):
506 511 yield fm
507 512
508 513 def maybereopen(fm, filename, opts):
509 514 """Create a formatter backed by file if filename specified, else return
510 515 the given formatter
511 516
512 517 Must be invoked using the 'with' statement. This will never call fm.end()
513 518 of the given formatter.
514 519 """
515 520 if filename:
516 521 return openformatter(fm._ui, filename, fm._topic, opts)
517 522 else:
518 523 return _neverending(fm)
@@ -1,200 +1,209 b''
1 1 Mercurial allows you to customize output of commands through
2 2 templates. You can either pass in a template or select an existing
3 3 template-style from the command line, via the --template option.
4 4
5 5 You can customize output for any "log-like" command: log,
6 6 outgoing, incoming, tip, parents, and heads.
7 7
8 8 Some built-in styles are packaged with Mercurial. These can be listed
9 9 with :hg:`log --template list`. Example usage::
10 10
11 11 $ hg log -r1.0::1.1 --template changelog
12 12
13 13 A template is a piece of text, with markup to invoke variable
14 14 expansion::
15 15
16 16 $ hg log -r1 --template "{node}\n"
17 17 b56ce7b07c52de7d5fd79fb89701ea538af65746
18 18
19 19 Keywords
20 20 ========
21 21
22 22 Strings in curly braces are called keywords. The availability of
23 23 keywords depends on the exact context of the templater. These
24 24 keywords are usually available for templating a log-like command:
25 25
26 26 .. keywordsmarker
27 27
28 28 The "date" keyword does not produce human-readable output. If you
29 29 want to use a date in your output, you can use a filter to process
30 30 it. Filters are functions which return a string based on the input
31 31 variable. Be sure to use the stringify filter first when you're
32 32 applying a string-input filter to a list-like input variable.
33 33 You can also use a chain of filters to get the desired output::
34 34
35 35 $ hg tip --template "{date|isodate}\n"
36 36 2008-08-21 18:22 +0000
37 37
38 38 Filters
39 39 =======
40 40
41 41 List of filters:
42 42
43 43 .. filtersmarker
44 44
45 45 Note that a filter is nothing more than a function call, i.e.
46 46 ``expr|filter`` is equivalent to ``filter(expr)``.
47 47
48 48 Functions
49 49 =========
50 50
51 51 In addition to filters, there are some basic built-in functions:
52 52
53 53 .. functionsmarker
54 54
55 55 Operators
56 56 =========
57 57
58 58 We provide a limited set of infix arithmetic operations on integers::
59 59
60 60 + for addition
61 61 - for subtraction
62 62 * for multiplication
63 63 / for floor division (division rounded to integer nearest -infinity)
64 64
65 65 Division fulfills the law x = x / y + mod(x, y).
66 66
67 67 Also, for any expression that returns a list, there is a list operator::
68 68
69 69 expr % "{template}"
70 70
71 71 As seen in the above example, ``{template}`` is interpreted as a template.
72 72 To prevent it from being interpreted, you can use an escape character ``\{``
73 73 or a raw string prefix, ``r'...'``.
74 74
75 75 Aliases
76 76 =======
77 77
78 78 New keywords and functions can be defined in the ``templatealias`` section of
79 79 a Mercurial configuration file::
80 80
81 81 <alias> = <definition>
82 82
83 83 Arguments of the form `a1`, `a2`, etc. are substituted from the alias into
84 84 the definition.
85 85
86 86 For example,
87 87
88 88 ::
89 89
90 90 [templatealias]
91 91 r = rev
92 92 rn = "{r}:{node|short}"
93 93 leftpad(s, w) = pad(s, w, ' ', True)
94 94
95 95 defines two symbol aliases, ``r`` and ``rn``, and a function alias
96 96 ``leftpad()``.
97 97
98 98 It's also possible to specify complete template strings, using the
99 99 ``templates`` section. The syntax used is the general template string syntax.
100 100
101 101 For example,
102 102
103 103 ::
104 104
105 105 [templates]
106 106 nodedate = "{node|short}: {date(date, "%Y-%m-%d")}\n"
107 107
108 108 defines a template, ``nodedate``, which can be called like::
109 109
110 110 $ hg log -r . -Tnodedate
111 111
112 112 A template defined in ``templates`` section can also be referenced from
113 113 another template::
114 114
115 115 $ hg log -r . -T "{rev} {nodedate}"
116 116
117 117 but be aware that the keywords cannot be overridden by templates. For example,
118 118 a template defined as ``templates.rev`` cannot be referenced as ``{rev}``.
119 119
120 A template defined in ``templates`` section may have sub templates which
121 are inserted before/after/between items::
122
123 [templates]
124 myjson = ' {dict(rev, node|short)|json}'
125 myjson:docheader = '\{\n'
126 myjson:docfooter = '\n}\n'
127 myjson:separator = ',\n'
128
120 129 Examples
121 130 ========
122 131
123 132 Some sample command line templates:
124 133
125 134 - Format lists, e.g. files::
126 135
127 136 $ hg log -r 0 --template "files:\n{files % ' {file}\n'}"
128 137
129 138 - Join the list of files with a ", "::
130 139
131 140 $ hg log -r 0 --template "files: {join(files, ', ')}\n"
132 141
133 142 - Join the list of files ending with ".py" with a ", "::
134 143
135 144 $ hg log -r 0 --template "pythonfiles: {join(files('**.py'), ', ')}\n"
136 145
137 146 - Separate non-empty arguments by a " "::
138 147
139 148 $ hg log -r 0 --template "{separate(' ', node, bookmarks, tags}\n"
140 149
141 150 - Modify each line of a commit description::
142 151
143 152 $ hg log --template "{splitlines(desc) % '**** {line}\n'}"
144 153
145 154 - Format date::
146 155
147 156 $ hg log -r 0 --template "{date(date, '%Y')}\n"
148 157
149 158 - Display date in UTC::
150 159
151 160 $ hg log -r 0 --template "{localdate(date, 'UTC')|date}\n"
152 161
153 162 - Output the description set to a fill-width of 30::
154 163
155 164 $ hg log -r 0 --template "{fill(desc, 30)}"
156 165
157 166 - Use a conditional to test for the default branch::
158 167
159 168 $ hg log -r 0 --template "{ifeq(branch, 'default', 'on the main branch',
160 169 'on branch {branch}')}\n"
161 170
162 171 - Append a newline if not empty::
163 172
164 173 $ hg tip --template "{if(author, '{author}\n')}"
165 174
166 175 - Label the output for use with the color extension::
167 176
168 177 $ hg log -r 0 --template "{label('changeset.{phase}', node|short)}\n"
169 178
170 179 - Invert the firstline filter, i.e. everything but the first line::
171 180
172 181 $ hg log -r 0 --template "{sub(r'^.*\n?\n?', '', desc)}\n"
173 182
174 183 - Display the contents of the 'extra' field, one per line::
175 184
176 185 $ hg log -r 0 --template "{join(extras, '\n')}\n"
177 186
178 187 - Mark the active bookmark with '*'::
179 188
180 189 $ hg log --template "{bookmarks % '{bookmark}{ifeq(bookmark, active, '*')} '}\n"
181 190
182 191 - Find the previous release candidate tag, the distance and changes since the tag::
183 192
184 193 $ hg log -r . --template "{latesttag('re:^.*-rc$') % '{tag}, {changes}, {distance}'}\n"
185 194
186 195 - Mark the working copy parent with '@'::
187 196
188 197 $ hg log --template "{ifcontains(rev, revset('.'), '@')}\n"
189 198
190 199 - Show details of parent revisions::
191 200
192 201 $ hg log --template "{revset('parents(%d)', rev) % '{desc|firstline}\n'}"
193 202
194 203 - Show only commit descriptions that start with "template"::
195 204
196 205 $ hg log --template "{startswith('template', firstline(desc))}\n"
197 206
198 207 - Print the first word of each line of a commit message::
199 208
200 209 $ hg log --template "{word(0, desc)}\n"
@@ -1,741 +1,766 b''
1 1 $ hg init a
2 2 $ cd a
3 3
4 4 Verify checking branch of nullrev before the cache is created doesnt crash
5 5 $ hg log -r 'branch(.)' -T '{branch}\n'
6 6
7 7 Basic test
8 8 $ echo 'root' >root
9 9 $ hg add root
10 10 $ hg commit -d '0 0' -m "Adding root node"
11 11
12 12 $ echo 'a' >a
13 13 $ hg add a
14 14 $ hg branch a
15 15 marked working directory as branch a
16 16 (branches are permanent and global, did you want a bookmark?)
17 17 $ hg commit -d '1 0' -m "Adding a branch"
18 18
19 19 $ hg branch q
20 20 marked working directory as branch q
21 21 $ echo 'aa' >a
22 22 $ hg branch -C
23 23 reset working directory to branch a
24 24 $ hg commit -d '2 0' -m "Adding to a branch"
25 25
26 26 $ hg update -C 0
27 27 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
28 28 $ echo 'b' >b
29 29 $ hg add b
30 30 $ hg branch b
31 31 marked working directory as branch b
32 32 $ hg commit -d '2 0' -m "Adding b branch"
33 33
34 34 $ echo 'bh1' >bh1
35 35 $ hg add bh1
36 36 $ hg commit -d '3 0' -m "Adding b branch head 1"
37 37
38 38 $ hg update -C 2
39 39 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
40 40 $ echo 'bh2' >bh2
41 41 $ hg add bh2
42 42 $ hg commit -d '4 0' -m "Adding b branch head 2"
43 43
44 44 $ echo 'c' >c
45 45 $ hg add c
46 46 $ hg branch c
47 47 marked working directory as branch c
48 48 $ hg commit -d '5 0' -m "Adding c branch"
49 49
50 50 reserved names
51 51
52 52 $ hg branch tip
53 53 abort: the name 'tip' is reserved
54 54 [255]
55 55 $ hg branch null
56 56 abort: the name 'null' is reserved
57 57 [255]
58 58 $ hg branch .
59 59 abort: the name '.' is reserved
60 60 [255]
61 61
62 62 invalid characters
63 63
64 64 $ hg branch 'foo:bar'
65 65 abort: ':' cannot be used in a name
66 66 [255]
67 67
68 68 $ hg branch 'foo
69 69 > bar'
70 70 abort: '\n' cannot be used in a name
71 71 [255]
72 72
73 73 trailing or leading spaces should be stripped before testing duplicates
74 74
75 75 $ hg branch 'b '
76 76 abort: a branch of the same name already exists
77 77 (use 'hg update' to switch to it)
78 78 [255]
79 79
80 80 $ hg branch ' b'
81 81 abort: a branch of the same name already exists
82 82 (use 'hg update' to switch to it)
83 83 [255]
84 84
85 85 verify update will accept invalid legacy branch names
86 86
87 87 $ hg init test-invalid-branch-name
88 88 $ cd test-invalid-branch-name
89 89 $ hg pull -u "$TESTDIR"/bundles/test-invalid-branch-name.hg
90 90 pulling from *test-invalid-branch-name.hg (glob)
91 91 requesting all changes
92 92 adding changesets
93 93 adding manifests
94 94 adding file changes
95 95 added 3 changesets with 3 changes to 2 files
96 96 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
97 97
98 98 $ hg update '"colon:test"'
99 99 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 100 $ cd ..
101 101
102 102 $ echo 'd' >d
103 103 $ hg add d
104 104 $ hg branch 'a branch name much longer than the default justification used by branches'
105 105 marked working directory as branch a branch name much longer than the default justification used by branches
106 106 $ hg commit -d '6 0' -m "Adding d branch"
107 107
108 108 $ hg branches
109 109 a branch name much longer than the default justification used by branches 7:10ff5895aa57
110 110 b 4:aee39cd168d0
111 111 c 6:589736a22561 (inactive)
112 112 a 5:d8cbc61dbaa6 (inactive)
113 113 default 0:19709c5a4e75 (inactive)
114 114
115 115 -------
116 116
117 117 $ hg branches -a
118 118 a branch name much longer than the default justification used by branches 7:10ff5895aa57
119 119 b 4:aee39cd168d0
120 120
121 121 --- Branch a
122 122
123 123 $ hg log -b a
124 124 changeset: 5:d8cbc61dbaa6
125 125 branch: a
126 126 parent: 2:881fe2b92ad0
127 127 user: test
128 128 date: Thu Jan 01 00:00:04 1970 +0000
129 129 summary: Adding b branch head 2
130 130
131 131 changeset: 2:881fe2b92ad0
132 132 branch: a
133 133 user: test
134 134 date: Thu Jan 01 00:00:02 1970 +0000
135 135 summary: Adding to a branch
136 136
137 137 changeset: 1:dd6b440dd85a
138 138 branch: a
139 139 user: test
140 140 date: Thu Jan 01 00:00:01 1970 +0000
141 141 summary: Adding a branch
142 142
143 143
144 144 ---- Branch b
145 145
146 146 $ hg log -b b
147 147 changeset: 4:aee39cd168d0
148 148 branch: b
149 149 user: test
150 150 date: Thu Jan 01 00:00:03 1970 +0000
151 151 summary: Adding b branch head 1
152 152
153 153 changeset: 3:ac22033332d1
154 154 branch: b
155 155 parent: 0:19709c5a4e75
156 156 user: test
157 157 date: Thu Jan 01 00:00:02 1970 +0000
158 158 summary: Adding b branch
159 159
160 160
161 161 ---- going to test branch closing
162 162
163 163 $ hg branches
164 164 a branch name much longer than the default justification used by branches 7:10ff5895aa57
165 165 b 4:aee39cd168d0
166 166 c 6:589736a22561 (inactive)
167 167 a 5:d8cbc61dbaa6 (inactive)
168 168 default 0:19709c5a4e75 (inactive)
169 169 $ hg up -C b
170 170 2 files updated, 0 files merged, 4 files removed, 0 files unresolved
171 171 $ echo 'xxx1' >> b
172 172 $ hg commit -d '7 0' -m 'adding cset to branch b'
173 173 $ hg up -C aee39cd168d0
174 174 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
175 175 $ echo 'xxx2' >> b
176 176 $ hg commit -d '8 0' -m 'adding head to branch b'
177 177 created new head
178 178 $ echo 'xxx3' >> b
179 179 $ hg commit -d '9 0' -m 'adding another cset to branch b'
180 180 $ hg branches
181 181 b 10:bfbe841b666e
182 182 a branch name much longer than the default justification used by branches 7:10ff5895aa57
183 183 c 6:589736a22561 (inactive)
184 184 a 5:d8cbc61dbaa6 (inactive)
185 185 default 0:19709c5a4e75 (inactive)
186 186 $ hg heads --closed
187 187 changeset: 10:bfbe841b666e
188 188 branch: b
189 189 tag: tip
190 190 user: test
191 191 date: Thu Jan 01 00:00:09 1970 +0000
192 192 summary: adding another cset to branch b
193 193
194 194 changeset: 8:eebb944467c9
195 195 branch: b
196 196 parent: 4:aee39cd168d0
197 197 user: test
198 198 date: Thu Jan 01 00:00:07 1970 +0000
199 199 summary: adding cset to branch b
200 200
201 201 changeset: 7:10ff5895aa57
202 202 branch: a branch name much longer than the default justification used by branches
203 203 user: test
204 204 date: Thu Jan 01 00:00:06 1970 +0000
205 205 summary: Adding d branch
206 206
207 207 changeset: 6:589736a22561
208 208 branch: c
209 209 user: test
210 210 date: Thu Jan 01 00:00:05 1970 +0000
211 211 summary: Adding c branch
212 212
213 213 changeset: 5:d8cbc61dbaa6
214 214 branch: a
215 215 parent: 2:881fe2b92ad0
216 216 user: test
217 217 date: Thu Jan 01 00:00:04 1970 +0000
218 218 summary: Adding b branch head 2
219 219
220 220 changeset: 0:19709c5a4e75
221 221 user: test
222 222 date: Thu Jan 01 00:00:00 1970 +0000
223 223 summary: Adding root node
224 224
225 225 $ hg heads
226 226 changeset: 10:bfbe841b666e
227 227 branch: b
228 228 tag: tip
229 229 user: test
230 230 date: Thu Jan 01 00:00:09 1970 +0000
231 231 summary: adding another cset to branch b
232 232
233 233 changeset: 8:eebb944467c9
234 234 branch: b
235 235 parent: 4:aee39cd168d0
236 236 user: test
237 237 date: Thu Jan 01 00:00:07 1970 +0000
238 238 summary: adding cset to branch b
239 239
240 240 changeset: 7:10ff5895aa57
241 241 branch: a branch name much longer than the default justification used by branches
242 242 user: test
243 243 date: Thu Jan 01 00:00:06 1970 +0000
244 244 summary: Adding d branch
245 245
246 246 changeset: 6:589736a22561
247 247 branch: c
248 248 user: test
249 249 date: Thu Jan 01 00:00:05 1970 +0000
250 250 summary: Adding c branch
251 251
252 252 changeset: 5:d8cbc61dbaa6
253 253 branch: a
254 254 parent: 2:881fe2b92ad0
255 255 user: test
256 256 date: Thu Jan 01 00:00:04 1970 +0000
257 257 summary: Adding b branch head 2
258 258
259 259 changeset: 0:19709c5a4e75
260 260 user: test
261 261 date: Thu Jan 01 00:00:00 1970 +0000
262 262 summary: Adding root node
263 263
264 264 $ hg commit -d '9 0' --close-branch -m 'prune bad branch'
265 265 $ hg branches -a
266 266 b 8:eebb944467c9
267 267 a branch name much longer than the default justification used by branches 7:10ff5895aa57
268 268 $ hg up -C b
269 269 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
270 270 $ hg commit -d '9 0' --close-branch -m 'close this part branch too'
271 271 $ hg commit -d '9 0' --close-branch -m 're-closing this branch'
272 272 abort: can only close branch heads
273 273 [255]
274 274
275 275 $ hg log -r tip --debug
276 276 changeset: 12:e3d49c0575d8fc2cb1cd6859c747c14f5f6d499f
277 277 branch: b
278 278 tag: tip
279 279 phase: draft
280 280 parent: 8:eebb944467c9fb9651ed232aeaf31b3c0a7fc6c1
281 281 parent: -1:0000000000000000000000000000000000000000
282 282 manifest: 8:6f9ed32d2b310e391a4f107d5f0f071df785bfee
283 283 user: test
284 284 date: Thu Jan 01 00:00:09 1970 +0000
285 285 extra: branch=b
286 286 extra: close=1
287 287 description:
288 288 close this part branch too
289 289
290 290
291 291 --- b branch should be inactive
292 292
293 293 $ hg branches
294 294 a branch name much longer than the default justification used by branches 7:10ff5895aa57
295 295 c 6:589736a22561 (inactive)
296 296 a 5:d8cbc61dbaa6 (inactive)
297 297 default 0:19709c5a4e75 (inactive)
298 298 $ hg branches -c
299 299 a branch name much longer than the default justification used by branches 7:10ff5895aa57
300 300 b 12:e3d49c0575d8 (closed)
301 301 c 6:589736a22561 (inactive)
302 302 a 5:d8cbc61dbaa6 (inactive)
303 303 default 0:19709c5a4e75 (inactive)
304 304 $ hg branches -a
305 305 a branch name much longer than the default justification used by branches 7:10ff5895aa57
306 306 $ hg branches -q
307 307 a branch name much longer than the default justification used by branches
308 308 c
309 309 a
310 310 default
311 311 $ hg heads b
312 312 no open branch heads found on branches b
313 313 [1]
314 314 $ hg heads --closed b
315 315 changeset: 12:e3d49c0575d8
316 316 branch: b
317 317 tag: tip
318 318 parent: 8:eebb944467c9
319 319 user: test
320 320 date: Thu Jan 01 00:00:09 1970 +0000
321 321 summary: close this part branch too
322 322
323 323 changeset: 11:d3f163457ebf
324 324 branch: b
325 325 user: test
326 326 date: Thu Jan 01 00:00:09 1970 +0000
327 327 summary: prune bad branch
328 328
329 329 $ echo 'xxx4' >> b
330 330 $ hg commit -d '9 0' -m 'reopen branch with a change'
331 331 reopening closed branch head 12
332 332
333 333 --- branch b is back in action
334 334
335 335 $ hg branches -a
336 336 b 13:e23b5505d1ad
337 337 a branch name much longer than the default justification used by branches 7:10ff5895aa57
338 338
339 339 ---- test heads listings
340 340
341 341 $ hg heads
342 342 changeset: 13:e23b5505d1ad
343 343 branch: b
344 344 tag: tip
345 345 user: test
346 346 date: Thu Jan 01 00:00:09 1970 +0000
347 347 summary: reopen branch with a change
348 348
349 349 changeset: 7:10ff5895aa57
350 350 branch: a branch name much longer than the default justification used by branches
351 351 user: test
352 352 date: Thu Jan 01 00:00:06 1970 +0000
353 353 summary: Adding d branch
354 354
355 355 changeset: 6:589736a22561
356 356 branch: c
357 357 user: test
358 358 date: Thu Jan 01 00:00:05 1970 +0000
359 359 summary: Adding c branch
360 360
361 361 changeset: 5:d8cbc61dbaa6
362 362 branch: a
363 363 parent: 2:881fe2b92ad0
364 364 user: test
365 365 date: Thu Jan 01 00:00:04 1970 +0000
366 366 summary: Adding b branch head 2
367 367
368 368 changeset: 0:19709c5a4e75
369 369 user: test
370 370 date: Thu Jan 01 00:00:00 1970 +0000
371 371 summary: Adding root node
372 372
373 373
374 374 branch default
375 375
376 376 $ hg heads default
377 377 changeset: 0:19709c5a4e75
378 378 user: test
379 379 date: Thu Jan 01 00:00:00 1970 +0000
380 380 summary: Adding root node
381 381
382 382
383 383 branch a
384 384
385 385 $ hg heads a
386 386 changeset: 5:d8cbc61dbaa6
387 387 branch: a
388 388 parent: 2:881fe2b92ad0
389 389 user: test
390 390 date: Thu Jan 01 00:00:04 1970 +0000
391 391 summary: Adding b branch head 2
392 392
393 393 $ hg heads --active a
394 394 no open branch heads found on branches a
395 395 [1]
396 396
397 397 branch b
398 398
399 399 $ hg heads b
400 400 changeset: 13:e23b5505d1ad
401 401 branch: b
402 402 tag: tip
403 403 user: test
404 404 date: Thu Jan 01 00:00:09 1970 +0000
405 405 summary: reopen branch with a change
406 406
407 407 $ hg heads --closed b
408 408 changeset: 13:e23b5505d1ad
409 409 branch: b
410 410 tag: tip
411 411 user: test
412 412 date: Thu Jan 01 00:00:09 1970 +0000
413 413 summary: reopen branch with a change
414 414
415 415 changeset: 11:d3f163457ebf
416 416 branch: b
417 417 user: test
418 418 date: Thu Jan 01 00:00:09 1970 +0000
419 419 summary: prune bad branch
420 420
421 421 default branch colors:
422 422
423 423 $ cat <<EOF >> $HGRCPATH
424 424 > [extensions]
425 425 > color =
426 426 > [color]
427 427 > mode = ansi
428 428 > EOF
429 429
430 430 $ hg up -C c
431 431 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
432 432 $ hg commit -d '9 0' --close-branch -m 'reclosing this branch'
433 433 $ hg up -C b
434 434 2 files updated, 0 files merged, 3 files removed, 0 files unresolved
435 435 $ hg branches --color=always
436 436 \x1b[0;32mb\x1b[0m\x1b[0;33m 13:e23b5505d1ad\x1b[0m (esc)
437 437 \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;33m 7:10ff5895aa57\x1b[0m (esc)
438 438 \x1b[0;0ma\x1b[0m\x1b[0;33m 5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
439 439 \x1b[0;0mdefault\x1b[0m\x1b[0;33m 0:19709c5a4e75\x1b[0m (inactive) (esc)
440 440
441 441 default closed branch color:
442 442
443 443 $ hg branches --color=always --closed
444 444 \x1b[0;32mb\x1b[0m\x1b[0;33m 13:e23b5505d1ad\x1b[0m (esc)
445 445 \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;33m 7:10ff5895aa57\x1b[0m (esc)
446 446 \x1b[0;30;1mc\x1b[0m\x1b[0;33m 14:f894c25619d3\x1b[0m (closed) (esc)
447 447 \x1b[0;0ma\x1b[0m\x1b[0;33m 5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
448 448 \x1b[0;0mdefault\x1b[0m\x1b[0;33m 0:19709c5a4e75\x1b[0m (inactive) (esc)
449 449
450 450 $ cat <<EOF >> $HGRCPATH
451 451 > [extensions]
452 452 > color =
453 453 > [color]
454 454 > branches.active = green
455 455 > branches.closed = blue
456 456 > branches.current = red
457 457 > branches.inactive = magenta
458 458 > log.changeset = cyan
459 459 > EOF
460 460
461 461 custom branch colors:
462 462
463 463 $ hg branches --color=always
464 464 \x1b[0;31mb\x1b[0m\x1b[0;36m 13:e23b5505d1ad\x1b[0m (esc)
465 465 \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;36m 7:10ff5895aa57\x1b[0m (esc)
466 466 \x1b[0;35ma\x1b[0m\x1b[0;36m 5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
467 467 \x1b[0;35mdefault\x1b[0m\x1b[0;36m 0:19709c5a4e75\x1b[0m (inactive) (esc)
468 468
469 469 custom closed branch color:
470 470
471 471 $ hg branches --color=always --closed
472 472 \x1b[0;31mb\x1b[0m\x1b[0;36m 13:e23b5505d1ad\x1b[0m (esc)
473 473 \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;36m 7:10ff5895aa57\x1b[0m (esc)
474 474 \x1b[0;34mc\x1b[0m\x1b[0;36m 14:f894c25619d3\x1b[0m (closed) (esc)
475 475 \x1b[0;35ma\x1b[0m\x1b[0;36m 5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
476 476 \x1b[0;35mdefault\x1b[0m\x1b[0;36m 0:19709c5a4e75\x1b[0m (inactive) (esc)
477 477
478 478 template output:
479 479
480 480 $ hg branches -Tjson --closed
481 481 [
482 482 {
483 483 "active": true,
484 484 "branch": "b",
485 485 "closed": false,
486 486 "current": true,
487 487 "node": "e23b5505d1ad24aab6f84fd8c7cb8cd8e5e93be0",
488 488 "rev": 13
489 489 },
490 490 {
491 491 "active": true,
492 492 "branch": "a branch name much longer than the default justification used by branches",
493 493 "closed": false,
494 494 "current": false,
495 495 "node": "10ff5895aa5793bd378da574af8cec8ea408d831",
496 496 "rev": 7
497 497 },
498 498 {
499 499 "active": false,
500 500 "branch": "c",
501 501 "closed": true,
502 502 "current": false,
503 503 "node": "f894c25619d3f1484639d81be950e0a07bc6f1f6",
504 504 "rev": 14
505 505 },
506 506 {
507 507 "active": false,
508 508 "branch": "a",
509 509 "closed": false,
510 510 "current": false,
511 511 "node": "d8cbc61dbaa6dc817175d1e301eecb863f280832",
512 512 "rev": 5
513 513 },
514 514 {
515 515 "active": false,
516 516 "branch": "default",
517 517 "closed": false,
518 518 "current": false,
519 519 "node": "19709c5a4e75bf938f8e349aff97438539bb729e",
520 520 "rev": 0
521 521 }
522 522 ]
523 523
524 524 $ hg branches --closed -T '{if(closed, "{branch}\n")}'
525 525 c
526 526
527 527 $ hg branches -T '{word(0, branch)}: {desc|firstline}\n'
528 528 b: reopen branch with a change
529 529 a: Adding d branch
530 530 a: Adding b branch head 2
531 531 default: Adding root node
532 532
533 533 $ cat <<'EOF' > "$TESTTMP/map-myjson"
534 534 > docheader = '\{\n'
535 535 > docfooter = '\n}\n'
536 536 > separator = ',\n'
537 537 > branches = ' {dict(branch, node|short)|json}'
538 538 > EOF
539 539 $ hg branches -T "$TESTTMP/map-myjson"
540 540 {
541 541 {"branch": "b", "node": "e23b5505d1ad"},
542 542 {"branch": "a branch *", "node": "10ff5895aa57"}, (glob)
543 543 {"branch": "a", "node": "d8cbc61dbaa6"},
544 544 {"branch": "default", "node": "19709c5a4e75"}
545 545 }
546 546
547 $ cat <<'EOF' >> .hg/hgrc
548 > [templates]
549 > myjson = ' {dict(branch, node|short)|json}'
550 > myjson:docheader = '\{\n'
551 > myjson:docfooter = '\n}\n'
552 > myjson:separator = ',\n'
553 > EOF
554 $ hg branches -T myjson
555 {
556 {"branch": "b", "node": "e23b5505d1ad"},
557 {"branch": "a branch *", "node": "10ff5895aa57"}, (glob)
558 {"branch": "a", "node": "d8cbc61dbaa6"},
559 {"branch": "default", "node": "19709c5a4e75"}
560 }
561
562 $ cat <<'EOF' >> .hg/hgrc
563 > [templates]
564 > :docheader = 'should not be selected as a docheader for literal templates\n'
565 > EOF
566 $ hg branches -T '{branch}\n'
567 b
568 a branch name much longer than the default justification used by branches
569 a
570 default
571
547 572 Tests of revision branch name caching
548 573
549 574 We rev branch cache is updated automatically. In these tests we use a trick to
550 575 trigger rebuilds. We remove the branch head cache and run 'hg head' to cause a
551 576 rebuild that also will populate the rev branch cache.
552 577
553 578 revision branch cache is created when building the branch head cache
554 579 $ rm -rf .hg/cache; hg head a -T '{rev}\n'
555 580 5
556 581 $ f --hexdump --size .hg/cache/rbc-*
557 582 .hg/cache/rbc-names-v1: size=87
558 583 0000: 64 65 66 61 75 6c 74 00 61 00 62 00 63 00 61 20 |default.a.b.c.a |
559 584 0010: 62 72 61 6e 63 68 20 6e 61 6d 65 20 6d 75 63 68 |branch name much|
560 585 0020: 20 6c 6f 6e 67 65 72 20 74 68 61 6e 20 74 68 65 | longer than the|
561 586 0030: 20 64 65 66 61 75 6c 74 20 6a 75 73 74 69 66 69 | default justifi|
562 587 0040: 63 61 74 69 6f 6e 20 75 73 65 64 20 62 79 20 62 |cation used by b|
563 588 0050: 72 61 6e 63 68 65 73 |ranches|
564 589 .hg/cache/rbc-revs-v1: size=120
565 590 0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
566 591 0010: 88 1f e2 b9 00 00 00 01 ac 22 03 33 00 00 00 02 |.........".3....|
567 592 0020: ae e3 9c d1 00 00 00 02 d8 cb c6 1d 00 00 00 01 |................|
568 593 0030: 58 97 36 a2 00 00 00 03 10 ff 58 95 00 00 00 04 |X.6.......X.....|
569 594 0040: ee bb 94 44 00 00 00 02 5f 40 61 bb 00 00 00 02 |...D...._@a.....|
570 595 0050: bf be 84 1b 00 00 00 02 d3 f1 63 45 80 00 00 02 |..........cE....|
571 596 0060: e3 d4 9c 05 80 00 00 02 e2 3b 55 05 00 00 00 02 |.........;U.....|
572 597 0070: f8 94 c2 56 80 00 00 03 |...V....|
573 598
574 599 no errors when revbranchcache is not writable
575 600
576 601 $ echo >> .hg/cache/rbc-revs-v1
577 602 $ mv .hg/cache/rbc-revs-v1 .hg/cache/rbc-revs-v1_
578 603 $ mkdir .hg/cache/rbc-revs-v1
579 604 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n'
580 605 5
581 606 $ rmdir .hg/cache/rbc-revs-v1
582 607 $ mv .hg/cache/rbc-revs-v1_ .hg/cache/rbc-revs-v1
583 608
584 609 no errors when wlock cannot be acquired
585 610
586 611 #if unix-permissions
587 612 $ mv .hg/cache/rbc-revs-v1 .hg/cache/rbc-revs-v1_
588 613 $ rm -f .hg/cache/branch*
589 614 $ chmod 555 .hg
590 615 $ hg head a -T '{rev}\n'
591 616 5
592 617 $ chmod 755 .hg
593 618 $ mv .hg/cache/rbc-revs-v1_ .hg/cache/rbc-revs-v1
594 619 #endif
595 620
596 621 recovery from invalid cache revs file with trailing data
597 622 $ echo >> .hg/cache/rbc-revs-v1
598 623 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
599 624 5
600 625 truncating cache/rbc-revs-v1 to 120
601 626 $ f --size .hg/cache/rbc-revs*
602 627 .hg/cache/rbc-revs-v1: size=120
603 628 recovery from invalid cache file with partial last record
604 629 $ mv .hg/cache/rbc-revs-v1 .
605 630 $ f -qDB 119 rbc-revs-v1 > .hg/cache/rbc-revs-v1
606 631 $ f --size .hg/cache/rbc-revs*
607 632 .hg/cache/rbc-revs-v1: size=119
608 633 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
609 634 5
610 635 truncating cache/rbc-revs-v1 to 112
611 636 $ f --size .hg/cache/rbc-revs*
612 637 .hg/cache/rbc-revs-v1: size=120
613 638 recovery from invalid cache file with missing record - no truncation
614 639 $ mv .hg/cache/rbc-revs-v1 .
615 640 $ f -qDB 112 rbc-revs-v1 > .hg/cache/rbc-revs-v1
616 641 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
617 642 5
618 643 $ f --size .hg/cache/rbc-revs*
619 644 .hg/cache/rbc-revs-v1: size=120
620 645 recovery from invalid cache file with some bad records
621 646 $ mv .hg/cache/rbc-revs-v1 .
622 647 $ f -qDB 8 rbc-revs-v1 > .hg/cache/rbc-revs-v1
623 648 $ f --size .hg/cache/rbc-revs*
624 649 .hg/cache/rbc-revs-v1: size=8
625 650 $ f -qDB 112 rbc-revs-v1 >> .hg/cache/rbc-revs-v1
626 651 $ f --size .hg/cache/rbc-revs*
627 652 .hg/cache/rbc-revs-v1: size=120
628 653 $ hg log -r 'branch(.)' -T '{rev} ' --debug
629 654 history modification detected - truncating revision branch cache to revision 13
630 655 history modification detected - truncating revision branch cache to revision 1
631 656 3 4 8 9 10 11 12 13 truncating cache/rbc-revs-v1 to 8
632 657 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
633 658 5
634 659 truncating cache/rbc-revs-v1 to 104
635 660 $ f --size --hexdump --bytes=16 .hg/cache/rbc-revs*
636 661 .hg/cache/rbc-revs-v1: size=120
637 662 0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
638 663 cache is updated when committing
639 664 $ hg branch i-will-regret-this
640 665 marked working directory as branch i-will-regret-this
641 666 $ hg ci -m regrets
642 667 $ f --size .hg/cache/rbc-*
643 668 .hg/cache/rbc-names-v1: size=106
644 669 .hg/cache/rbc-revs-v1: size=128
645 670 update after rollback - the cache will be correct but rbc-names will will still
646 671 contain the branch name even though it no longer is used
647 672 $ hg up -qr '.^'
648 673 $ hg rollback -qf
649 674 $ f --size --hexdump .hg/cache/rbc-*
650 675 .hg/cache/rbc-names-v1: size=106
651 676 0000: 64 65 66 61 75 6c 74 00 61 00 62 00 63 00 61 20 |default.a.b.c.a |
652 677 0010: 62 72 61 6e 63 68 20 6e 61 6d 65 20 6d 75 63 68 |branch name much|
653 678 0020: 20 6c 6f 6e 67 65 72 20 74 68 61 6e 20 74 68 65 | longer than the|
654 679 0030: 20 64 65 66 61 75 6c 74 20 6a 75 73 74 69 66 69 | default justifi|
655 680 0040: 63 61 74 69 6f 6e 20 75 73 65 64 20 62 79 20 62 |cation used by b|
656 681 0050: 72 61 6e 63 68 65 73 00 69 2d 77 69 6c 6c 2d 72 |ranches.i-will-r|
657 682 0060: 65 67 72 65 74 2d 74 68 69 73 |egret-this|
658 683 .hg/cache/rbc-revs-v1: size=120
659 684 0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
660 685 0010: 88 1f e2 b9 00 00 00 01 ac 22 03 33 00 00 00 02 |.........".3....|
661 686 0020: ae e3 9c d1 00 00 00 02 d8 cb c6 1d 00 00 00 01 |................|
662 687 0030: 58 97 36 a2 00 00 00 03 10 ff 58 95 00 00 00 04 |X.6.......X.....|
663 688 0040: ee bb 94 44 00 00 00 02 5f 40 61 bb 00 00 00 02 |...D...._@a.....|
664 689 0050: bf be 84 1b 00 00 00 02 d3 f1 63 45 80 00 00 02 |..........cE....|
665 690 0060: e3 d4 9c 05 80 00 00 02 e2 3b 55 05 00 00 00 02 |.........;U.....|
666 691 0070: f8 94 c2 56 80 00 00 03 |...V....|
667 692 cache is updated/truncated when stripping - it is thus very hard to get in a
668 693 situation where the cache is out of sync and the hash check detects it
669 694 $ hg --config extensions.strip= strip -r tip --nob
670 695 $ f --size .hg/cache/rbc-revs*
671 696 .hg/cache/rbc-revs-v1: size=112
672 697
673 698 cache is rebuilt when corruption is detected
674 699 $ echo > .hg/cache/rbc-names-v1
675 700 $ hg log -r '5:&branch(.)' -T '{rev} ' --debug
676 701 referenced branch names not found - rebuilding revision branch cache from scratch
677 702 8 9 10 11 12 13 truncating cache/rbc-revs-v1 to 40
678 703 $ f --size --hexdump .hg/cache/rbc-*
679 704 .hg/cache/rbc-names-v1: size=79
680 705 0000: 62 00 61 00 63 00 61 20 62 72 61 6e 63 68 20 6e |b.a.c.a branch n|
681 706 0010: 61 6d 65 20 6d 75 63 68 20 6c 6f 6e 67 65 72 20 |ame much longer |
682 707 0020: 74 68 61 6e 20 74 68 65 20 64 65 66 61 75 6c 74 |than the default|
683 708 0030: 20 6a 75 73 74 69 66 69 63 61 74 69 6f 6e 20 75 | justification u|
684 709 0040: 73 65 64 20 62 79 20 62 72 61 6e 63 68 65 73 |sed by branches|
685 710 .hg/cache/rbc-revs-v1: size=112
686 711 0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
687 712 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
688 713 0020: 00 00 00 00 00 00 00 00 d8 cb c6 1d 00 00 00 01 |................|
689 714 0030: 58 97 36 a2 00 00 00 02 10 ff 58 95 00 00 00 03 |X.6.......X.....|
690 715 0040: ee bb 94 44 00 00 00 00 5f 40 61 bb 00 00 00 00 |...D...._@a.....|
691 716 0050: bf be 84 1b 00 00 00 00 d3 f1 63 45 80 00 00 00 |..........cE....|
692 717 0060: e3 d4 9c 05 80 00 00 00 e2 3b 55 05 00 00 00 00 |.........;U.....|
693 718
694 719 Test that cache files are created and grows correctly:
695 720
696 721 $ rm .hg/cache/rbc*
697 722 $ hg log -r "5 & branch(5)" -T "{rev}\n"
698 723 5
699 724 $ f --size --hexdump .hg/cache/rbc-*
700 725 .hg/cache/rbc-names-v1: size=1
701 726 0000: 61 |a|
702 727 .hg/cache/rbc-revs-v1: size=112
703 728 0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
704 729 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
705 730 0020: 00 00 00 00 00 00 00 00 d8 cb c6 1d 00 00 00 00 |................|
706 731 0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
707 732 0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
708 733 0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
709 734 0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
710 735
711 736 $ cd ..
712 737
713 738 Test for multiple incorrect branch cache entries:
714 739
715 740 $ hg init b
716 741 $ cd b
717 742 $ touch f
718 743 $ hg ci -Aqmf
719 744 $ echo >> f
720 745 $ hg ci -Amf
721 746 $ hg branch -q branch
722 747 $ hg ci -Amf
723 748
724 749 $ f --size --hexdump .hg/cache/rbc-*
725 750 .hg/cache/rbc-names-v1: size=14
726 751 0000: 64 65 66 61 75 6c 74 00 62 72 61 6e 63 68 |default.branch|
727 752 .hg/cache/rbc-revs-v1: size=24
728 753 0000: 66 e5 f5 aa 00 00 00 00 fa 4c 04 e5 00 00 00 00 |f........L......|
729 754 0010: 56 46 78 69 00 00 00 01 |VFxi....|
730 755 $ : > .hg/cache/rbc-revs-v1
731 756
732 757 No superfluous rebuilding of cache:
733 758 $ hg log -r "branch(null)&branch(branch)" --debug
734 759 $ f --size --hexdump .hg/cache/rbc-*
735 760 .hg/cache/rbc-names-v1: size=14
736 761 0000: 64 65 66 61 75 6c 74 00 62 72 61 6e 63 68 |default.branch|
737 762 .hg/cache/rbc-revs-v1: size=24
738 763 0000: 66 e5 f5 aa 00 00 00 00 fa 4c 04 e5 00 00 00 00 |f........L......|
739 764 0010: 56 46 78 69 00 00 00 01 |VFxi....|
740 765
741 766 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now