##// END OF EJS Templates
formatter: add support for docheader and docfooter templates...
Yuya Nishihara -
r32949:13eebc18 default
parent child Browse files
Show More
@@ -1,497 +1,516 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 self._parts = templatepartsmap(spec, self._t,
354 ['docheader', 'docfooter'])
353 355 self._counter = itertools.count()
354 356 self._cache = {} # for templatekw/funcs to store reusable data
357 self._renderitem('docheader', {})
358
355 359 def context(self, **ctxs):
356 360 '''insert context objects to be used to render template keywords'''
357 361 ctxs = pycompat.byteskwargs(ctxs)
358 362 assert all(k == 'ctx' for k in ctxs)
359 363 self._item.update(ctxs)
360 364
361 365 def _showitem(self):
362 366 item = self._item.copy()
363 367 item['index'] = next(self._counter)
364 368 self._renderitem(self._tref, item)
365 369
366 def _renderitem(self, ref, item):
370 def _renderitem(self, part, item):
371 if part not in self._parts:
372 return
373 ref = self._parts[part]
374
367 375 # TODO: add support for filectx. probably each template keyword or
368 376 # function will have to declare dependent resources. e.g.
369 377 # @templatekeyword(..., requires=('ctx',))
370 378 props = {}
371 379 if 'ctx' in item:
372 380 props.update(templatekw.keywords)
373 381 # explicitly-defined fields precede templatekw
374 382 props.update(item)
375 383 if 'ctx' in item:
376 384 # but template resources must be always available
377 385 props['templ'] = self._t
378 386 props['repo'] = props['ctx'].repo()
379 387 props['revcache'] = {}
380 388 props = pycompat.strkwargs(props)
381 389 g = self._t(ref, ui=self._ui, cache=self._cache, **props)
382 390 self._out.write(templater.stringify(g))
383 391
392 def end(self):
393 baseformatter.end(self)
394 self._renderitem('docfooter', {})
395
384 396 templatespec = collections.namedtuple(r'templatespec',
385 397 r'ref tmpl mapfile')
386 398
387 399 def lookuptemplate(ui, topic, tmpl):
388 400 """Find the template matching the given -T/--template spec 'tmpl'
389 401
390 402 'tmpl' can be any of the following:
391 403
392 404 - a literal template (e.g. '{rev}')
393 405 - a map-file name or path (e.g. 'changelog')
394 406 - a reference to [templates] in config file
395 407 - a path to raw template file
396 408
397 409 A map file defines a stand-alone template environment. If a map file
398 410 selected, all templates defined in the file will be loaded, and the
399 411 template matching the given topic will be rendered. No aliases will be
400 412 loaded from user config.
401 413
402 414 If no map file selected, all templates in [templates] section will be
403 415 available as well as aliases in [templatealias].
404 416 """
405 417
406 418 # looks like a literal template?
407 419 if '{' in tmpl:
408 420 return templatespec('', tmpl, None)
409 421
410 422 # perhaps a stock style?
411 423 if not os.path.split(tmpl)[0]:
412 424 mapname = (templater.templatepath('map-cmdline.' + tmpl)
413 425 or templater.templatepath(tmpl))
414 426 if mapname and os.path.isfile(mapname):
415 427 return templatespec(topic, None, mapname)
416 428
417 429 # perhaps it's a reference to [templates]
418 430 if ui.config('templates', tmpl):
419 431 return templatespec(tmpl, None, None)
420 432
421 433 if tmpl == 'list':
422 434 ui.write(_("available styles: %s\n") % templater.stylelist())
423 435 raise error.Abort(_("specify a template"))
424 436
425 437 # perhaps it's a path to a map or a template
426 438 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
427 439 # is it a mapfile for a style?
428 440 if os.path.basename(tmpl).startswith("map-"):
429 441 return templatespec(topic, None, os.path.realpath(tmpl))
430 442 with util.posixfile(tmpl, 'rb') as f:
431 443 tmpl = f.read()
432 444 return templatespec('', tmpl, None)
433 445
434 446 # constant string?
435 447 return templatespec('', tmpl, None)
436 448
449 def templatepartsmap(spec, t, partnames):
450 """Create a mapping of {part: ref}"""
451 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
452 if spec.mapfile:
453 partsmap.update((p, p) for p in partnames if p in t)
454 return partsmap
455
437 456 def loadtemplater(ui, spec, cache=None):
438 457 """Create a templater from either a literal template or loading from
439 458 a map file"""
440 459 assert not (spec.tmpl and spec.mapfile)
441 460 if spec.mapfile:
442 461 return templater.templater.frommapfile(spec.mapfile, cache=cache)
443 462 return maketemplater(ui, spec.tmpl, cache=cache)
444 463
445 464 def maketemplater(ui, tmpl, cache=None):
446 465 """Create a templater from a string template 'tmpl'"""
447 466 aliases = ui.configitems('templatealias')
448 467 t = templater.templater(cache=cache, aliases=aliases)
449 468 t.cache.update((k, templater.unquotestring(v))
450 469 for k, v in ui.configitems('templates'))
451 470 if tmpl:
452 471 t.cache[''] = tmpl
453 472 return t
454 473
455 474 def formatter(ui, out, topic, opts):
456 475 template = opts.get("template", "")
457 476 if template == "json":
458 477 return jsonformatter(ui, out, topic, opts)
459 478 elif template == "pickle":
460 479 return pickleformatter(ui, out, topic, opts)
461 480 elif template == "debug":
462 481 return debugformatter(ui, out, topic, opts)
463 482 elif template != "":
464 483 return templateformatter(ui, out, topic, opts)
465 484 # developer config: ui.formatdebug
466 485 elif ui.configbool('ui', 'formatdebug'):
467 486 return debugformatter(ui, out, topic, opts)
468 487 # deprecated config: ui.formatjson
469 488 elif ui.configbool('ui', 'formatjson'):
470 489 return jsonformatter(ui, out, topic, opts)
471 490 return plainformatter(ui, out, topic, opts)
472 491
473 492 @contextlib.contextmanager
474 493 def openformatter(ui, filename, topic, opts):
475 494 """Create a formatter that writes outputs to the specified file
476 495
477 496 Must be invoked using the 'with' statement.
478 497 """
479 498 with util.posixfile(filename, 'wb') as out:
480 499 with formatter(ui, out, topic, opts) as fm:
481 500 yield fm
482 501
483 502 @contextlib.contextmanager
484 503 def _neverending(fm):
485 504 yield fm
486 505
487 506 def maybereopen(fm, filename, opts):
488 507 """Create a formatter backed by file if filename specified, else return
489 508 the given formatter
490 509
491 510 Must be invoked using the 'with' statement. This will never call fm.end()
492 511 of the given formatter.
493 512 """
494 513 if filename:
495 514 return openformatter(fm._ui, filename, fm._topic, opts)
496 515 else:
497 516 return _neverending(fm)
@@ -1,727 +1,740 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 $ cat <<'EOF' > "$TESTTMP/map-myjson"
534 > docheader = '\{\n'
535 > docfooter = '\n}\n'
536 > branches = '{ifeq(index, 0, "", ",\n")} {dict(branch, node|short)|json}'
537 > EOF
538 $ hg branches -T "$TESTTMP/map-myjson"
539 {
540 {"branch": "b", "node": "e23b5505d1ad"},
541 {"branch": "a branch *", "node": "10ff5895aa57"}, (glob)
542 {"branch": "a", "node": "d8cbc61dbaa6"},
543 {"branch": "default", "node": "19709c5a4e75"}
544 }
545
533 546 Tests of revision branch name caching
534 547
535 548 We rev branch cache is updated automatically. In these tests we use a trick to
536 549 trigger rebuilds. We remove the branch head cache and run 'hg head' to cause a
537 550 rebuild that also will populate the rev branch cache.
538 551
539 552 revision branch cache is created when building the branch head cache
540 553 $ rm -rf .hg/cache; hg head a -T '{rev}\n'
541 554 5
542 555 $ f --hexdump --size .hg/cache/rbc-*
543 556 .hg/cache/rbc-names-v1: size=87
544 557 0000: 64 65 66 61 75 6c 74 00 61 00 62 00 63 00 61 20 |default.a.b.c.a |
545 558 0010: 62 72 61 6e 63 68 20 6e 61 6d 65 20 6d 75 63 68 |branch name much|
546 559 0020: 20 6c 6f 6e 67 65 72 20 74 68 61 6e 20 74 68 65 | longer than the|
547 560 0030: 20 64 65 66 61 75 6c 74 20 6a 75 73 74 69 66 69 | default justifi|
548 561 0040: 63 61 74 69 6f 6e 20 75 73 65 64 20 62 79 20 62 |cation used by b|
549 562 0050: 72 61 6e 63 68 65 73 |ranches|
550 563 .hg/cache/rbc-revs-v1: size=120
551 564 0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
552 565 0010: 88 1f e2 b9 00 00 00 01 ac 22 03 33 00 00 00 02 |.........".3....|
553 566 0020: ae e3 9c d1 00 00 00 02 d8 cb c6 1d 00 00 00 01 |................|
554 567 0030: 58 97 36 a2 00 00 00 03 10 ff 58 95 00 00 00 04 |X.6.......X.....|
555 568 0040: ee bb 94 44 00 00 00 02 5f 40 61 bb 00 00 00 02 |...D...._@a.....|
556 569 0050: bf be 84 1b 00 00 00 02 d3 f1 63 45 80 00 00 02 |..........cE....|
557 570 0060: e3 d4 9c 05 80 00 00 02 e2 3b 55 05 00 00 00 02 |.........;U.....|
558 571 0070: f8 94 c2 56 80 00 00 03 |...V....|
559 572
560 573 no errors when revbranchcache is not writable
561 574
562 575 $ echo >> .hg/cache/rbc-revs-v1
563 576 $ mv .hg/cache/rbc-revs-v1 .hg/cache/rbc-revs-v1_
564 577 $ mkdir .hg/cache/rbc-revs-v1
565 578 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n'
566 579 5
567 580 $ rmdir .hg/cache/rbc-revs-v1
568 581 $ mv .hg/cache/rbc-revs-v1_ .hg/cache/rbc-revs-v1
569 582
570 583 no errors when wlock cannot be acquired
571 584
572 585 #if unix-permissions
573 586 $ mv .hg/cache/rbc-revs-v1 .hg/cache/rbc-revs-v1_
574 587 $ rm -f .hg/cache/branch*
575 588 $ chmod 555 .hg
576 589 $ hg head a -T '{rev}\n'
577 590 5
578 591 $ chmod 755 .hg
579 592 $ mv .hg/cache/rbc-revs-v1_ .hg/cache/rbc-revs-v1
580 593 #endif
581 594
582 595 recovery from invalid cache revs file with trailing data
583 596 $ echo >> .hg/cache/rbc-revs-v1
584 597 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
585 598 5
586 599 truncating cache/rbc-revs-v1 to 120
587 600 $ f --size .hg/cache/rbc-revs*
588 601 .hg/cache/rbc-revs-v1: size=120
589 602 recovery from invalid cache file with partial last record
590 603 $ mv .hg/cache/rbc-revs-v1 .
591 604 $ f -qDB 119 rbc-revs-v1 > .hg/cache/rbc-revs-v1
592 605 $ f --size .hg/cache/rbc-revs*
593 606 .hg/cache/rbc-revs-v1: size=119
594 607 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
595 608 5
596 609 truncating cache/rbc-revs-v1 to 112
597 610 $ f --size .hg/cache/rbc-revs*
598 611 .hg/cache/rbc-revs-v1: size=120
599 612 recovery from invalid cache file with missing record - no truncation
600 613 $ mv .hg/cache/rbc-revs-v1 .
601 614 $ f -qDB 112 rbc-revs-v1 > .hg/cache/rbc-revs-v1
602 615 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
603 616 5
604 617 $ f --size .hg/cache/rbc-revs*
605 618 .hg/cache/rbc-revs-v1: size=120
606 619 recovery from invalid cache file with some bad records
607 620 $ mv .hg/cache/rbc-revs-v1 .
608 621 $ f -qDB 8 rbc-revs-v1 > .hg/cache/rbc-revs-v1
609 622 $ f --size .hg/cache/rbc-revs*
610 623 .hg/cache/rbc-revs-v1: size=8
611 624 $ f -qDB 112 rbc-revs-v1 >> .hg/cache/rbc-revs-v1
612 625 $ f --size .hg/cache/rbc-revs*
613 626 .hg/cache/rbc-revs-v1: size=120
614 627 $ hg log -r 'branch(.)' -T '{rev} ' --debug
615 628 history modification detected - truncating revision branch cache to revision 13
616 629 history modification detected - truncating revision branch cache to revision 1
617 630 3 4 8 9 10 11 12 13 truncating cache/rbc-revs-v1 to 8
618 631 $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
619 632 5
620 633 truncating cache/rbc-revs-v1 to 104
621 634 $ f --size --hexdump --bytes=16 .hg/cache/rbc-revs*
622 635 .hg/cache/rbc-revs-v1: size=120
623 636 0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
624 637 cache is updated when committing
625 638 $ hg branch i-will-regret-this
626 639 marked working directory as branch i-will-regret-this
627 640 $ hg ci -m regrets
628 641 $ f --size .hg/cache/rbc-*
629 642 .hg/cache/rbc-names-v1: size=106
630 643 .hg/cache/rbc-revs-v1: size=128
631 644 update after rollback - the cache will be correct but rbc-names will will still
632 645 contain the branch name even though it no longer is used
633 646 $ hg up -qr '.^'
634 647 $ hg rollback -qf
635 648 $ f --size --hexdump .hg/cache/rbc-*
636 649 .hg/cache/rbc-names-v1: size=106
637 650 0000: 64 65 66 61 75 6c 74 00 61 00 62 00 63 00 61 20 |default.a.b.c.a |
638 651 0010: 62 72 61 6e 63 68 20 6e 61 6d 65 20 6d 75 63 68 |branch name much|
639 652 0020: 20 6c 6f 6e 67 65 72 20 74 68 61 6e 20 74 68 65 | longer than the|
640 653 0030: 20 64 65 66 61 75 6c 74 20 6a 75 73 74 69 66 69 | default justifi|
641 654 0040: 63 61 74 69 6f 6e 20 75 73 65 64 20 62 79 20 62 |cation used by b|
642 655 0050: 72 61 6e 63 68 65 73 00 69 2d 77 69 6c 6c 2d 72 |ranches.i-will-r|
643 656 0060: 65 67 72 65 74 2d 74 68 69 73 |egret-this|
644 657 .hg/cache/rbc-revs-v1: size=120
645 658 0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
646 659 0010: 88 1f e2 b9 00 00 00 01 ac 22 03 33 00 00 00 02 |.........".3....|
647 660 0020: ae e3 9c d1 00 00 00 02 d8 cb c6 1d 00 00 00 01 |................|
648 661 0030: 58 97 36 a2 00 00 00 03 10 ff 58 95 00 00 00 04 |X.6.......X.....|
649 662 0040: ee bb 94 44 00 00 00 02 5f 40 61 bb 00 00 00 02 |...D...._@a.....|
650 663 0050: bf be 84 1b 00 00 00 02 d3 f1 63 45 80 00 00 02 |..........cE....|
651 664 0060: e3 d4 9c 05 80 00 00 02 e2 3b 55 05 00 00 00 02 |.........;U.....|
652 665 0070: f8 94 c2 56 80 00 00 03 |...V....|
653 666 cache is updated/truncated when stripping - it is thus very hard to get in a
654 667 situation where the cache is out of sync and the hash check detects it
655 668 $ hg --config extensions.strip= strip -r tip --nob
656 669 $ f --size .hg/cache/rbc-revs*
657 670 .hg/cache/rbc-revs-v1: size=112
658 671
659 672 cache is rebuilt when corruption is detected
660 673 $ echo > .hg/cache/rbc-names-v1
661 674 $ hg log -r '5:&branch(.)' -T '{rev} ' --debug
662 675 referenced branch names not found - rebuilding revision branch cache from scratch
663 676 8 9 10 11 12 13 truncating cache/rbc-revs-v1 to 40
664 677 $ f --size --hexdump .hg/cache/rbc-*
665 678 .hg/cache/rbc-names-v1: size=79
666 679 0000: 62 00 61 00 63 00 61 20 62 72 61 6e 63 68 20 6e |b.a.c.a branch n|
667 680 0010: 61 6d 65 20 6d 75 63 68 20 6c 6f 6e 67 65 72 20 |ame much longer |
668 681 0020: 74 68 61 6e 20 74 68 65 20 64 65 66 61 75 6c 74 |than the default|
669 682 0030: 20 6a 75 73 74 69 66 69 63 61 74 69 6f 6e 20 75 | justification u|
670 683 0040: 73 65 64 20 62 79 20 62 72 61 6e 63 68 65 73 |sed by branches|
671 684 .hg/cache/rbc-revs-v1: size=112
672 685 0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
673 686 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
674 687 0020: 00 00 00 00 00 00 00 00 d8 cb c6 1d 00 00 00 01 |................|
675 688 0030: 58 97 36 a2 00 00 00 02 10 ff 58 95 00 00 00 03 |X.6.......X.....|
676 689 0040: ee bb 94 44 00 00 00 00 5f 40 61 bb 00 00 00 00 |...D...._@a.....|
677 690 0050: bf be 84 1b 00 00 00 00 d3 f1 63 45 80 00 00 00 |..........cE....|
678 691 0060: e3 d4 9c 05 80 00 00 00 e2 3b 55 05 00 00 00 00 |.........;U.....|
679 692
680 693 Test that cache files are created and grows correctly:
681 694
682 695 $ rm .hg/cache/rbc*
683 696 $ hg log -r "5 & branch(5)" -T "{rev}\n"
684 697 5
685 698 $ f --size --hexdump .hg/cache/rbc-*
686 699 .hg/cache/rbc-names-v1: size=1
687 700 0000: 61 |a|
688 701 .hg/cache/rbc-revs-v1: size=112
689 702 0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
690 703 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
691 704 0020: 00 00 00 00 00 00 00 00 d8 cb c6 1d 00 00 00 00 |................|
692 705 0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
693 706 0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
694 707 0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
695 708 0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
696 709
697 710 $ cd ..
698 711
699 712 Test for multiple incorrect branch cache entries:
700 713
701 714 $ hg init b
702 715 $ cd b
703 716 $ touch f
704 717 $ hg ci -Aqmf
705 718 $ echo >> f
706 719 $ hg ci -Amf
707 720 $ hg branch -q branch
708 721 $ hg ci -Amf
709 722
710 723 $ f --size --hexdump .hg/cache/rbc-*
711 724 .hg/cache/rbc-names-v1: size=14
712 725 0000: 64 65 66 61 75 6c 74 00 62 72 61 6e 63 68 |default.branch|
713 726 .hg/cache/rbc-revs-v1: size=24
714 727 0000: 66 e5 f5 aa 00 00 00 00 fa 4c 04 e5 00 00 00 00 |f........L......|
715 728 0010: 56 46 78 69 00 00 00 01 |VFxi....|
716 729 $ : > .hg/cache/rbc-revs-v1
717 730
718 731 No superfluous rebuilding of cache:
719 732 $ hg log -r "branch(null)&branch(branch)" --debug
720 733 $ f --size --hexdump .hg/cache/rbc-*
721 734 .hg/cache/rbc-names-v1: size=14
722 735 0000: 64 65 66 61 75 6c 74 00 62 72 61 6e 63 68 |default.branch|
723 736 .hg/cache/rbc-revs-v1: size=24
724 737 0000: 66 e5 f5 aa 00 00 00 00 fa 4c 04 e5 00 00 00 00 |f........L......|
725 738 0010: 56 46 78 69 00 00 00 01 |VFxi....|
726 739
727 740 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now