##// END OF EJS Templates
templatekw: alias {name} of file copies dict to {path}...
Yuya Nishihara -
r39404:5b1d406b default
parent child Browse files
Show More
@@ -1,820 +1,815 b''
1 1 # templatekw.py - common changeset template keywords
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 from .i18n import _
11 11 from .node import (
12 12 hex,
13 13 nullid,
14 14 )
15 15
16 16 from . import (
17 17 diffutil,
18 18 encoding,
19 19 error,
20 20 hbisect,
21 21 i18n,
22 22 obsutil,
23 23 patch,
24 24 pycompat,
25 25 registrar,
26 26 scmutil,
27 27 templateutil,
28 28 util,
29 29 )
30 30 from .utils import (
31 31 stringutil,
32 32 )
33 33
34 34 _hybrid = templateutil.hybrid
35 35 hybriddict = templateutil.hybriddict
36 36 hybridlist = templateutil.hybridlist
37 37 compatdict = templateutil.compatdict
38 38 compatlist = templateutil.compatlist
39 39 _showcompatlist = templateutil._showcompatlist
40 40
41 41 def getlatesttags(context, mapping, pattern=None):
42 42 '''return date, distance and name for the latest tag of rev'''
43 43 repo = context.resource(mapping, 'repo')
44 44 ctx = context.resource(mapping, 'ctx')
45 45 cache = context.resource(mapping, 'cache')
46 46
47 47 cachename = 'latesttags'
48 48 if pattern is not None:
49 49 cachename += '-' + pattern
50 50 match = stringutil.stringmatcher(pattern)[2]
51 51 else:
52 52 match = util.always
53 53
54 54 if cachename not in cache:
55 55 # Cache mapping from rev to a tuple with tag date, tag
56 56 # distance and tag name
57 57 cache[cachename] = {-1: (0, 0, ['null'])}
58 58 latesttags = cache[cachename]
59 59
60 60 rev = ctx.rev()
61 61 todo = [rev]
62 62 while todo:
63 63 rev = todo.pop()
64 64 if rev in latesttags:
65 65 continue
66 66 ctx = repo[rev]
67 67 tags = [t for t in ctx.tags()
68 68 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
69 69 and match(t))]
70 70 if tags:
71 71 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
72 72 continue
73 73 try:
74 74 ptags = [latesttags[p.rev()] for p in ctx.parents()]
75 75 if len(ptags) > 1:
76 76 if ptags[0][2] == ptags[1][2]:
77 77 # The tuples are laid out so the right one can be found by
78 78 # comparison in this case.
79 79 pdate, pdist, ptag = max(ptags)
80 80 else:
81 81 def key(x):
82 82 changessincetag = len(repo.revs('only(%d, %s)',
83 83 ctx.rev(), x[2][0]))
84 84 # Smallest number of changes since tag wins. Date is
85 85 # used as tiebreaker.
86 86 return [-changessincetag, x[0]]
87 87 pdate, pdist, ptag = max(ptags, key=key)
88 88 else:
89 89 pdate, pdist, ptag = ptags[0]
90 90 except KeyError:
91 91 # Cache miss - recurse
92 92 todo.append(rev)
93 93 todo.extend(p.rev() for p in ctx.parents())
94 94 continue
95 95 latesttags[rev] = pdate, pdist + 1, ptag
96 96 return latesttags[rev]
97 97
98 98 def getrenamedfn(repo, endrev=None):
99 99 rcache = {}
100 100 if endrev is None:
101 101 endrev = len(repo)
102 102
103 103 def getrenamed(fn, rev):
104 104 '''looks up all renames for a file (up to endrev) the first
105 105 time the file is given. It indexes on the changerev and only
106 106 parses the manifest if linkrev != changerev.
107 107 Returns rename info for fn at changerev rev.'''
108 108 if fn not in rcache:
109 109 rcache[fn] = {}
110 110 fl = repo.file(fn)
111 111 for i in fl:
112 112 lr = fl.linkrev(i)
113 113 renamed = fl.renamed(fl.node(i))
114 114 rcache[fn][lr] = renamed and renamed[0]
115 115 if lr >= endrev:
116 116 break
117 117 if rev in rcache[fn]:
118 118 return rcache[fn][rev]
119 119
120 120 # If linkrev != rev (i.e. rev not found in rcache) fallback to
121 121 # filectx logic.
122 122 try:
123 123 renamed = repo[rev][fn].renamed()
124 124 return renamed and renamed[0]
125 125 except error.LookupError:
126 126 return None
127 127
128 128 return getrenamed
129 129
130 130 def getlogcolumns():
131 131 """Return a dict of log column labels"""
132 132 _ = pycompat.identity # temporarily disable gettext
133 133 # i18n: column positioning for "hg log"
134 134 columns = _('bookmark: %s\n'
135 135 'branch: %s\n'
136 136 'changeset: %s\n'
137 137 'copies: %s\n'
138 138 'date: %s\n'
139 139 'extra: %s=%s\n'
140 140 'files+: %s\n'
141 141 'files-: %s\n'
142 142 'files: %s\n'
143 143 'instability: %s\n'
144 144 'manifest: %s\n'
145 145 'obsolete: %s\n'
146 146 'parent: %s\n'
147 147 'phase: %s\n'
148 148 'summary: %s\n'
149 149 'tag: %s\n'
150 150 'user: %s\n')
151 151 return dict(zip([s.split(':', 1)[0] for s in columns.splitlines()],
152 152 i18n._(columns).splitlines(True)))
153 153
154 154 # default templates internally used for rendering of lists
155 155 defaulttempl = {
156 156 'parent': '{rev}:{node|formatnode} ',
157 157 'manifest': '{rev}:{node|formatnode}',
158 158 'file_copy': '{name} ({source})',
159 159 'envvar': '{key}={value}',
160 160 'extra': '{key}={value|stringescape}'
161 161 }
162 162 # filecopy is preserved for compatibility reasons
163 163 defaulttempl['filecopy'] = defaulttempl['file_copy']
164 164
165 165 # keywords are callables (see registrar.templatekeyword for details)
166 166 keywords = {}
167 167 templatekeyword = registrar.templatekeyword(keywords)
168 168
169 169 @templatekeyword('author', requires={'ctx'})
170 170 def showauthor(context, mapping):
171 171 """Alias for ``{user}``"""
172 172 return showuser(context, mapping)
173 173
174 174 @templatekeyword('bisect', requires={'repo', 'ctx'})
175 175 def showbisect(context, mapping):
176 176 """String. The changeset bisection status."""
177 177 repo = context.resource(mapping, 'repo')
178 178 ctx = context.resource(mapping, 'ctx')
179 179 return hbisect.label(repo, ctx.node())
180 180
181 181 @templatekeyword('branch', requires={'ctx'})
182 182 def showbranch(context, mapping):
183 183 """String. The name of the branch on which the changeset was
184 184 committed.
185 185 """
186 186 ctx = context.resource(mapping, 'ctx')
187 187 return ctx.branch()
188 188
189 189 @templatekeyword('branches', requires={'ctx'})
190 190 def showbranches(context, mapping):
191 191 """List of strings. The name of the branch on which the
192 192 changeset was committed. Will be empty if the branch name was
193 193 default. (DEPRECATED)
194 194 """
195 195 ctx = context.resource(mapping, 'ctx')
196 196 branch = ctx.branch()
197 197 if branch != 'default':
198 198 return compatlist(context, mapping, 'branch', [branch],
199 199 plural='branches')
200 200 return compatlist(context, mapping, 'branch', [], plural='branches')
201 201
202 202 @templatekeyword('bookmarks', requires={'repo', 'ctx'})
203 203 def showbookmarks(context, mapping):
204 204 """List of strings. Any bookmarks associated with the
205 205 changeset. Also sets 'active', the name of the active bookmark.
206 206 """
207 207 repo = context.resource(mapping, 'repo')
208 208 ctx = context.resource(mapping, 'ctx')
209 209 bookmarks = ctx.bookmarks()
210 210 active = repo._activebookmark
211 211 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
212 212 f = _showcompatlist(context, mapping, 'bookmark', bookmarks)
213 213 return _hybrid(f, bookmarks, makemap, pycompat.identity)
214 214
215 215 @templatekeyword('children', requires={'ctx'})
216 216 def showchildren(context, mapping):
217 217 """List of strings. The children of the changeset."""
218 218 ctx = context.resource(mapping, 'ctx')
219 219 childrevs = ['%d:%s' % (cctx.rev(), cctx) for cctx in ctx.children()]
220 220 return compatlist(context, mapping, 'children', childrevs, element='child')
221 221
222 222 # Deprecated, but kept alive for help generation a purpose.
223 223 @templatekeyword('currentbookmark', requires={'repo', 'ctx'})
224 224 def showcurrentbookmark(context, mapping):
225 225 """String. The active bookmark, if it is associated with the changeset.
226 226 (DEPRECATED)"""
227 227 return showactivebookmark(context, mapping)
228 228
229 229 @templatekeyword('activebookmark', requires={'repo', 'ctx'})
230 230 def showactivebookmark(context, mapping):
231 231 """String. The active bookmark, if it is associated with the changeset."""
232 232 repo = context.resource(mapping, 'repo')
233 233 ctx = context.resource(mapping, 'ctx')
234 234 active = repo._activebookmark
235 235 if active and active in ctx.bookmarks():
236 236 return active
237 237 return ''
238 238
239 239 @templatekeyword('date', requires={'ctx'})
240 240 def showdate(context, mapping):
241 241 """Date information. The date when the changeset was committed."""
242 242 ctx = context.resource(mapping, 'ctx')
243 243 # the default string format is '<float(unixtime)><tzoffset>' because
244 244 # python-hglib splits date at decimal separator.
245 245 return templateutil.date(ctx.date(), showfmt='%d.0%d')
246 246
247 247 @templatekeyword('desc', requires={'ctx'})
248 248 def showdescription(context, mapping):
249 249 """String. The text of the changeset description."""
250 250 ctx = context.resource(mapping, 'ctx')
251 251 s = ctx.description()
252 252 if isinstance(s, encoding.localstr):
253 253 # try hard to preserve utf-8 bytes
254 254 return encoding.tolocal(encoding.fromlocal(s).strip())
255 255 elif isinstance(s, encoding.safelocalstr):
256 256 return encoding.safelocalstr(s.strip())
257 257 else:
258 258 return s.strip()
259 259
260 260 @templatekeyword('diffstat', requires={'ui', 'ctx'})
261 261 def showdiffstat(context, mapping):
262 262 """String. Statistics of changes with the following format:
263 263 "modified files: +added/-removed lines"
264 264 """
265 265 ui = context.resource(mapping, 'ui')
266 266 ctx = context.resource(mapping, 'ctx')
267 267 diffopts = diffutil.diffallopts(ui, {'noprefix': False})
268 268 diff = ctx.diff(opts=diffopts)
269 269 stats = patch.diffstatdata(util.iterlines(diff))
270 270 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
271 271 return '%d: +%d/-%d' % (len(stats), adds, removes)
272 272
273 273 @templatekeyword('envvars', requires={'ui'})
274 274 def showenvvars(context, mapping):
275 275 """A dictionary of environment variables. (EXPERIMENTAL)"""
276 276 ui = context.resource(mapping, 'ui')
277 277 env = ui.exportableenviron()
278 278 env = util.sortdict((k, env[k]) for k in sorted(env))
279 279 return compatdict(context, mapping, 'envvar', env, plural='envvars')
280 280
281 281 @templatekeyword('extras', requires={'ctx'})
282 282 def showextras(context, mapping):
283 283 """List of dicts with key, value entries of the 'extras'
284 284 field of this changeset."""
285 285 ctx = context.resource(mapping, 'ctx')
286 286 extras = ctx.extra()
287 287 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
288 288 makemap = lambda k: {'key': k, 'value': extras[k]}
289 289 c = [makemap(k) for k in extras]
290 290 f = _showcompatlist(context, mapping, 'extra', c, plural='extras')
291 291 return _hybrid(f, extras, makemap,
292 292 lambda k: '%s=%s' % (k, stringutil.escapestr(extras[k])))
293 293
294 294 def _showfilesbystat(context, mapping, name, index):
295 295 ctx = context.resource(mapping, 'ctx')
296 296 revcache = context.resource(mapping, 'revcache')
297 297 if 'files' not in revcache:
298 298 revcache['files'] = ctx.p1().status(ctx)[:3]
299 299 files = revcache['files'][index]
300 300 return templateutil.compatfileslist(context, mapping, name, files)
301 301
302 302 @templatekeyword('file_adds', requires={'ctx', 'revcache'})
303 303 def showfileadds(context, mapping):
304 304 """List of strings. Files added by this changeset."""
305 305 return _showfilesbystat(context, mapping, 'file_add', 1)
306 306
307 307 @templatekeyword('file_copies',
308 308 requires={'repo', 'ctx', 'cache', 'revcache'})
309 309 def showfilecopies(context, mapping):
310 310 """List of strings. Files copied in this changeset with
311 311 their sources.
312 312 """
313 313 repo = context.resource(mapping, 'repo')
314 314 ctx = context.resource(mapping, 'ctx')
315 315 cache = context.resource(mapping, 'cache')
316 316 copies = context.resource(mapping, 'revcache').get('copies')
317 317 if copies is None:
318 318 if 'getrenamed' not in cache:
319 319 cache['getrenamed'] = getrenamedfn(repo)
320 320 copies = []
321 321 getrenamed = cache['getrenamed']
322 322 for fn in ctx.files():
323 323 rename = getrenamed(fn, ctx.rev())
324 324 if rename:
325 325 copies.append((fn, rename))
326
327 copies = util.sortdict(copies)
328 return compatdict(context, mapping, 'file_copy', copies,
329 key='name', value='source', fmt='%s (%s)',
330 plural='file_copies')
326 return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
327 copies)
331 328
332 329 # showfilecopiesswitch() displays file copies only if copy records are
333 330 # provided before calling the templater, usually with a --copies
334 331 # command line switch.
335 332 @templatekeyword('file_copies_switch', requires={'revcache'})
336 333 def showfilecopiesswitch(context, mapping):
337 334 """List of strings. Like "file_copies" but displayed
338 335 only if the --copied switch is set.
339 336 """
340 337 copies = context.resource(mapping, 'revcache').get('copies') or []
341 copies = util.sortdict(copies)
342 return compatdict(context, mapping, 'file_copy', copies,
343 key='name', value='source', fmt='%s (%s)',
344 plural='file_copies')
338 return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
339 copies)
345 340
346 341 @templatekeyword('file_dels', requires={'ctx', 'revcache'})
347 342 def showfiledels(context, mapping):
348 343 """List of strings. Files removed by this changeset."""
349 344 return _showfilesbystat(context, mapping, 'file_del', 2)
350 345
351 346 @templatekeyword('file_mods', requires={'ctx', 'revcache'})
352 347 def showfilemods(context, mapping):
353 348 """List of strings. Files modified by this changeset."""
354 349 return _showfilesbystat(context, mapping, 'file_mod', 0)
355 350
356 351 @templatekeyword('files', requires={'ctx'})
357 352 def showfiles(context, mapping):
358 353 """List of strings. All files modified, added, or removed by this
359 354 changeset.
360 355 """
361 356 ctx = context.resource(mapping, 'ctx')
362 357 return templateutil.compatfileslist(context, mapping, 'file', ctx.files())
363 358
364 359 @templatekeyword('graphnode', requires={'repo', 'ctx'})
365 360 def showgraphnode(context, mapping):
366 361 """String. The character representing the changeset node in an ASCII
367 362 revision graph."""
368 363 repo = context.resource(mapping, 'repo')
369 364 ctx = context.resource(mapping, 'ctx')
370 365 return getgraphnode(repo, ctx)
371 366
372 367 def getgraphnode(repo, ctx):
373 368 return getgraphnodecurrent(repo, ctx) or getgraphnodesymbol(ctx)
374 369
375 370 def getgraphnodecurrent(repo, ctx):
376 371 wpnodes = repo.dirstate.parents()
377 372 if wpnodes[1] == nullid:
378 373 wpnodes = wpnodes[:1]
379 374 if ctx.node() in wpnodes:
380 375 return '@'
381 376 else:
382 377 return ''
383 378
384 379 def getgraphnodesymbol(ctx):
385 380 if ctx.obsolete():
386 381 return 'x'
387 382 elif ctx.isunstable():
388 383 return '*'
389 384 elif ctx.closesbranch():
390 385 return '_'
391 386 else:
392 387 return 'o'
393 388
394 389 @templatekeyword('graphwidth', requires=())
395 390 def showgraphwidth(context, mapping):
396 391 """Integer. The width of the graph drawn by 'log --graph' or zero."""
397 392 # just hosts documentation; should be overridden by template mapping
398 393 return 0
399 394
400 395 @templatekeyword('index', requires=())
401 396 def showindex(context, mapping):
402 397 """Integer. The current iteration of the loop. (0 indexed)"""
403 398 # just hosts documentation; should be overridden by template mapping
404 399 raise error.Abort(_("can't use index in this context"))
405 400
406 401 @templatekeyword('latesttag', requires={'repo', 'ctx', 'cache'})
407 402 def showlatesttag(context, mapping):
408 403 """List of strings. The global tags on the most recent globally
409 404 tagged ancestor of this changeset. If no such tags exist, the list
410 405 consists of the single string "null".
411 406 """
412 407 return showlatesttags(context, mapping, None)
413 408
414 409 def showlatesttags(context, mapping, pattern):
415 410 """helper method for the latesttag keyword and function"""
416 411 latesttags = getlatesttags(context, mapping, pattern)
417 412
418 413 # latesttag[0] is an implementation detail for sorting csets on different
419 414 # branches in a stable manner- it is the date the tagged cset was created,
420 415 # not the date the tag was created. Therefore it isn't made visible here.
421 416 makemap = lambda v: {
422 417 'changes': _showchangessincetag,
423 418 'distance': latesttags[1],
424 419 'latesttag': v, # BC with {latesttag % '{latesttag}'}
425 420 'tag': v
426 421 }
427 422
428 423 tags = latesttags[2]
429 424 f = _showcompatlist(context, mapping, 'latesttag', tags, separator=':')
430 425 return _hybrid(f, tags, makemap, pycompat.identity)
431 426
432 427 @templatekeyword('latesttagdistance', requires={'repo', 'ctx', 'cache'})
433 428 def showlatesttagdistance(context, mapping):
434 429 """Integer. Longest path to the latest tag."""
435 430 return getlatesttags(context, mapping)[1]
436 431
437 432 @templatekeyword('changessincelatesttag', requires={'repo', 'ctx', 'cache'})
438 433 def showchangessincelatesttag(context, mapping):
439 434 """Integer. All ancestors not in the latest tag."""
440 435 tag = getlatesttags(context, mapping)[2][0]
441 436 mapping = context.overlaymap(mapping, {'tag': tag})
442 437 return _showchangessincetag(context, mapping)
443 438
444 439 def _showchangessincetag(context, mapping):
445 440 repo = context.resource(mapping, 'repo')
446 441 ctx = context.resource(mapping, 'ctx')
447 442 offset = 0
448 443 revs = [ctx.rev()]
449 444 tag = context.symbol(mapping, 'tag')
450 445
451 446 # The only() revset doesn't currently support wdir()
452 447 if ctx.rev() is None:
453 448 offset = 1
454 449 revs = [p.rev() for p in ctx.parents()]
455 450
456 451 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
457 452
458 453 # teach templater latesttags.changes is switched to (context, mapping) API
459 454 _showchangessincetag._requires = {'repo', 'ctx'}
460 455
461 456 @templatekeyword('manifest', requires={'repo', 'ctx'})
462 457 def showmanifest(context, mapping):
463 458 repo = context.resource(mapping, 'repo')
464 459 ctx = context.resource(mapping, 'ctx')
465 460 mnode = ctx.manifestnode()
466 461 if mnode is None:
467 462 # just avoid crash, we might want to use the 'ff...' hash in future
468 463 return
469 464 mrev = repo.manifestlog.rev(mnode)
470 465 mhex = hex(mnode)
471 466 mapping = context.overlaymap(mapping, {'rev': mrev, 'node': mhex})
472 467 f = context.process('manifest', mapping)
473 468 # TODO: perhaps 'ctx' should be dropped from mapping because manifest
474 469 # rev and node are completely different from changeset's.
475 470 return templateutil.hybriditem(f, None, f,
476 471 lambda x: {'rev': mrev, 'node': mhex})
477 472
478 473 @templatekeyword('obsfate', requires={'ui', 'repo', 'ctx'})
479 474 def showobsfate(context, mapping):
480 475 # this function returns a list containing pre-formatted obsfate strings.
481 476 #
482 477 # This function will be replaced by templates fragments when we will have
483 478 # the verbosity templatekw available.
484 479 succsandmarkers = showsuccsandmarkers(context, mapping)
485 480
486 481 ui = context.resource(mapping, 'ui')
487 482 repo = context.resource(mapping, 'repo')
488 483 values = []
489 484
490 485 for x in succsandmarkers.tovalue(context, mapping):
491 486 v = obsutil.obsfateprinter(ui, repo, x['successors'], x['markers'],
492 487 scmutil.formatchangeid)
493 488 values.append(v)
494 489
495 490 return compatlist(context, mapping, "fate", values)
496 491
497 492 def shownames(context, mapping, namespace):
498 493 """helper method to generate a template keyword for a namespace"""
499 494 repo = context.resource(mapping, 'repo')
500 495 ctx = context.resource(mapping, 'ctx')
501 496 ns = repo.names[namespace]
502 497 names = ns.names(repo, ctx.node())
503 498 return compatlist(context, mapping, ns.templatename, names,
504 499 plural=namespace)
505 500
506 501 @templatekeyword('namespaces', requires={'repo', 'ctx'})
507 502 def shownamespaces(context, mapping):
508 503 """Dict of lists. Names attached to this changeset per
509 504 namespace."""
510 505 repo = context.resource(mapping, 'repo')
511 506 ctx = context.resource(mapping, 'ctx')
512 507
513 508 namespaces = util.sortdict()
514 509 def makensmapfn(ns):
515 510 # 'name' for iterating over namespaces, templatename for local reference
516 511 return lambda v: {'name': v, ns.templatename: v}
517 512
518 513 for k, ns in repo.names.iteritems():
519 514 names = ns.names(repo, ctx.node())
520 515 f = _showcompatlist(context, mapping, 'name', names)
521 516 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
522 517
523 518 f = _showcompatlist(context, mapping, 'namespace', list(namespaces))
524 519
525 520 def makemap(ns):
526 521 return {
527 522 'namespace': ns,
528 523 'names': namespaces[ns],
529 524 'builtin': repo.names[ns].builtin,
530 525 'colorname': repo.names[ns].colorname,
531 526 }
532 527
533 528 return _hybrid(f, namespaces, makemap, pycompat.identity)
534 529
535 530 @templatekeyword('node', requires={'ctx'})
536 531 def shownode(context, mapping):
537 532 """String. The changeset identification hash, as a 40 hexadecimal
538 533 digit string.
539 534 """
540 535 ctx = context.resource(mapping, 'ctx')
541 536 return ctx.hex()
542 537
543 538 @templatekeyword('obsolete', requires={'ctx'})
544 539 def showobsolete(context, mapping):
545 540 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
546 541 ctx = context.resource(mapping, 'ctx')
547 542 if ctx.obsolete():
548 543 return 'obsolete'
549 544 return ''
550 545
551 546 @templatekeyword('peerurls', requires={'repo'})
552 547 def showpeerurls(context, mapping):
553 548 """A dictionary of repository locations defined in the [paths] section
554 549 of your configuration file."""
555 550 repo = context.resource(mapping, 'repo')
556 551 # see commands.paths() for naming of dictionary keys
557 552 paths = repo.ui.paths
558 553 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
559 554 def makemap(k):
560 555 p = paths[k]
561 556 d = {'name': k, 'url': p.rawloc}
562 557 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
563 558 return d
564 559 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
565 560
566 561 @templatekeyword("predecessors", requires={'repo', 'ctx'})
567 562 def showpredecessors(context, mapping):
568 563 """Returns the list if the closest visible successors. (EXPERIMENTAL)"""
569 564 repo = context.resource(mapping, 'repo')
570 565 ctx = context.resource(mapping, 'ctx')
571 566 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
572 567 predecessors = pycompat.maplist(hex, predecessors)
573 568
574 569 return _hybrid(None, predecessors,
575 570 lambda x: {'ctx': repo[x]},
576 571 lambda x: scmutil.formatchangeid(repo[x]))
577 572
578 573 @templatekeyword('reporoot', requires={'repo'})
579 574 def showreporoot(context, mapping):
580 575 """String. The root directory of the current repository."""
581 576 repo = context.resource(mapping, 'repo')
582 577 return repo.root
583 578
584 579 @templatekeyword("successorssets", requires={'repo', 'ctx'})
585 580 def showsuccessorssets(context, mapping):
586 581 """Returns a string of sets of successors for a changectx. Format used
587 582 is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
588 583 while also diverged into ctx3. (EXPERIMENTAL)"""
589 584 repo = context.resource(mapping, 'repo')
590 585 ctx = context.resource(mapping, 'ctx')
591 586 if not ctx.obsolete():
592 587 return ''
593 588
594 589 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
595 590 ssets = [[hex(n) for n in ss] for ss in ssets]
596 591
597 592 data = []
598 593 for ss in ssets:
599 594 h = _hybrid(None, ss, lambda x: {'ctx': repo[x]},
600 595 lambda x: scmutil.formatchangeid(repo[x]))
601 596 data.append(h)
602 597
603 598 # Format the successorssets
604 599 def render(d):
605 600 return templateutil.stringify(context, mapping, d)
606 601
607 602 def gen(data):
608 603 yield "; ".join(render(d) for d in data)
609 604
610 605 return _hybrid(gen(data), data, lambda x: {'successorset': x},
611 606 pycompat.identity)
612 607
613 608 @templatekeyword("succsandmarkers", requires={'repo', 'ctx'})
614 609 def showsuccsandmarkers(context, mapping):
615 610 """Returns a list of dict for each final successor of ctx. The dict
616 611 contains successors node id in "successors" keys and the list of
617 612 obs-markers from ctx to the set of successors in "markers".
618 613 (EXPERIMENTAL)
619 614 """
620 615 repo = context.resource(mapping, 'repo')
621 616 ctx = context.resource(mapping, 'ctx')
622 617
623 618 values = obsutil.successorsandmarkers(repo, ctx)
624 619
625 620 if values is None:
626 621 values = []
627 622
628 623 # Format successors and markers to avoid exposing binary to templates
629 624 data = []
630 625 for i in values:
631 626 # Format successors
632 627 successors = i['successors']
633 628
634 629 successors = [hex(n) for n in successors]
635 630 successors = _hybrid(None, successors,
636 631 lambda x: {'ctx': repo[x]},
637 632 lambda x: scmutil.formatchangeid(repo[x]))
638 633
639 634 # Format markers
640 635 finalmarkers = []
641 636 for m in i['markers']:
642 637 hexprec = hex(m[0])
643 638 hexsucs = tuple(hex(n) for n in m[1])
644 639 hexparents = None
645 640 if m[5] is not None:
646 641 hexparents = tuple(hex(n) for n in m[5])
647 642 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
648 643 finalmarkers.append(newmarker)
649 644
650 645 data.append({'successors': successors, 'markers': finalmarkers})
651 646
652 647 return templateutil.mappinglist(data)
653 648
654 649 @templatekeyword('p1rev', requires={'ctx'})
655 650 def showp1rev(context, mapping):
656 651 """Integer. The repository-local revision number of the changeset's
657 652 first parent, or -1 if the changeset has no parents."""
658 653 ctx = context.resource(mapping, 'ctx')
659 654 return ctx.p1().rev()
660 655
661 656 @templatekeyword('p2rev', requires={'ctx'})
662 657 def showp2rev(context, mapping):
663 658 """Integer. The repository-local revision number of the changeset's
664 659 second parent, or -1 if the changeset has no second parent."""
665 660 ctx = context.resource(mapping, 'ctx')
666 661 return ctx.p2().rev()
667 662
668 663 @templatekeyword('p1node', requires={'ctx'})
669 664 def showp1node(context, mapping):
670 665 """String. The identification hash of the changeset's first parent,
671 666 as a 40 digit hexadecimal string. If the changeset has no parents, all
672 667 digits are 0."""
673 668 ctx = context.resource(mapping, 'ctx')
674 669 return ctx.p1().hex()
675 670
676 671 @templatekeyword('p2node', requires={'ctx'})
677 672 def showp2node(context, mapping):
678 673 """String. The identification hash of the changeset's second
679 674 parent, as a 40 digit hexadecimal string. If the changeset has no second
680 675 parent, all digits are 0."""
681 676 ctx = context.resource(mapping, 'ctx')
682 677 return ctx.p2().hex()
683 678
684 679 @templatekeyword('parents', requires={'repo', 'ctx'})
685 680 def showparents(context, mapping):
686 681 """List of strings. The parents of the changeset in "rev:node"
687 682 format. If the changeset has only one "natural" parent (the predecessor
688 683 revision) nothing is shown."""
689 684 repo = context.resource(mapping, 'repo')
690 685 ctx = context.resource(mapping, 'ctx')
691 686 pctxs = scmutil.meaningfulparents(repo, ctx)
692 687 prevs = [p.rev() for p in pctxs]
693 688 parents = [[('rev', p.rev()),
694 689 ('node', p.hex()),
695 690 ('phase', p.phasestr())]
696 691 for p in pctxs]
697 692 f = _showcompatlist(context, mapping, 'parent', parents)
698 693 return _hybrid(f, prevs, lambda x: {'ctx': repo[x]},
699 694 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
700 695
701 696 @templatekeyword('phase', requires={'ctx'})
702 697 def showphase(context, mapping):
703 698 """String. The changeset phase name."""
704 699 ctx = context.resource(mapping, 'ctx')
705 700 return ctx.phasestr()
706 701
707 702 @templatekeyword('phaseidx', requires={'ctx'})
708 703 def showphaseidx(context, mapping):
709 704 """Integer. The changeset phase index. (ADVANCED)"""
710 705 ctx = context.resource(mapping, 'ctx')
711 706 return ctx.phase()
712 707
713 708 @templatekeyword('rev', requires={'ctx'})
714 709 def showrev(context, mapping):
715 710 """Integer. The repository-local changeset revision number."""
716 711 ctx = context.resource(mapping, 'ctx')
717 712 return scmutil.intrev(ctx)
718 713
719 714 def showrevslist(context, mapping, name, revs):
720 715 """helper to generate a list of revisions in which a mapped template will
721 716 be evaluated"""
722 717 repo = context.resource(mapping, 'repo')
723 718 f = _showcompatlist(context, mapping, name, ['%d' % r for r in revs])
724 719 return _hybrid(f, revs,
725 720 lambda x: {name: x, 'ctx': repo[x]},
726 721 pycompat.identity, keytype=int)
727 722
728 723 @templatekeyword('subrepos', requires={'ctx'})
729 724 def showsubrepos(context, mapping):
730 725 """List of strings. Updated subrepositories in the changeset."""
731 726 ctx = context.resource(mapping, 'ctx')
732 727 substate = ctx.substate
733 728 if not substate:
734 729 return compatlist(context, mapping, 'subrepo', [])
735 730 psubstate = ctx.parents()[0].substate or {}
736 731 subrepos = []
737 732 for sub in substate:
738 733 if sub not in psubstate or substate[sub] != psubstate[sub]:
739 734 subrepos.append(sub) # modified or newly added in ctx
740 735 for sub in psubstate:
741 736 if sub not in substate:
742 737 subrepos.append(sub) # removed in ctx
743 738 return compatlist(context, mapping, 'subrepo', sorted(subrepos))
744 739
745 740 # don't remove "showtags" definition, even though namespaces will put
746 741 # a helper function for "tags" keyword into "keywords" map automatically,
747 742 # because online help text is built without namespaces initialization
748 743 @templatekeyword('tags', requires={'repo', 'ctx'})
749 744 def showtags(context, mapping):
750 745 """List of strings. Any tags associated with the changeset."""
751 746 return shownames(context, mapping, 'tags')
752 747
753 748 @templatekeyword('termwidth', requires={'ui'})
754 749 def showtermwidth(context, mapping):
755 750 """Integer. The width of the current terminal."""
756 751 ui = context.resource(mapping, 'ui')
757 752 return ui.termwidth()
758 753
759 754 @templatekeyword('user', requires={'ctx'})
760 755 def showuser(context, mapping):
761 756 """String. The unmodified author of the changeset."""
762 757 ctx = context.resource(mapping, 'ctx')
763 758 return ctx.user()
764 759
765 760 @templatekeyword('instabilities', requires={'ctx'})
766 761 def showinstabilities(context, mapping):
767 762 """List of strings. Evolution instabilities affecting the changeset.
768 763 (EXPERIMENTAL)
769 764 """
770 765 ctx = context.resource(mapping, 'ctx')
771 766 return compatlist(context, mapping, 'instability', ctx.instabilities(),
772 767 plural='instabilities')
773 768
774 769 @templatekeyword('verbosity', requires={'ui'})
775 770 def showverbosity(context, mapping):
776 771 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
777 772 or ''."""
778 773 ui = context.resource(mapping, 'ui')
779 774 # see logcmdutil.changesettemplater for priority of these flags
780 775 if ui.debugflag:
781 776 return 'debug'
782 777 elif ui.quiet:
783 778 return 'quiet'
784 779 elif ui.verbose:
785 780 return 'verbose'
786 781 return ''
787 782
788 783 @templatekeyword('whyunstable', requires={'repo', 'ctx'})
789 784 def showwhyunstable(context, mapping):
790 785 """List of dicts explaining all instabilities of a changeset.
791 786 (EXPERIMENTAL)
792 787 """
793 788 repo = context.resource(mapping, 'repo')
794 789 ctx = context.resource(mapping, 'ctx')
795 790
796 791 def formatnode(ctx):
797 792 return '%s (%s)' % (scmutil.formatchangeid(ctx), ctx.phasestr())
798 793
799 794 entries = obsutil.whyunstable(repo, ctx)
800 795
801 796 for entry in entries:
802 797 if entry.get('divergentnodes'):
803 798 dnodes = entry['divergentnodes']
804 799 dnhybrid = _hybrid(None, [dnode.hex() for dnode in dnodes],
805 800 lambda x: {'ctx': repo[x]},
806 801 lambda x: formatnode(repo[x]))
807 802 entry['divergentnodes'] = dnhybrid
808 803
809 804 tmpl = ('{instability}:{if(divergentnodes, " ")}{divergentnodes} '
810 805 '{reason} {node|short}')
811 806 return templateutil.mappinglist(entries, tmpl=tmpl, sep='\n')
812 807
813 808 def loadkeyword(ui, extname, registrarobj):
814 809 """Load template keyword from specified registrarobj
815 810 """
816 811 for name, func in registrarobj._table.iteritems():
817 812 keywords[name] = func
818 813
819 814 # tell hggettext to extract docstrings from these functions:
820 815 i18nfunctions = keywords.values()
@@ -1,948 +1,963 b''
1 1 # templateutil.py - utility for template evaluation
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import abc
11 11 import types
12 12
13 13 from .i18n import _
14 14 from . import (
15 15 error,
16 16 pycompat,
17 17 util,
18 18 )
19 19 from .utils import (
20 20 dateutil,
21 21 stringutil,
22 22 )
23 23
24 24 class ResourceUnavailable(error.Abort):
25 25 pass
26 26
27 27 class TemplateNotFound(error.Abort):
28 28 pass
29 29
30 30 class wrapped(object):
31 31 """Object requiring extra conversion prior to displaying or processing
32 32 as value
33 33
34 34 Use unwrapvalue() or unwrapastype() to obtain the inner object.
35 35 """
36 36
37 37 __metaclass__ = abc.ABCMeta
38 38
39 39 @abc.abstractmethod
40 40 def contains(self, context, mapping, item):
41 41 """Test if the specified item is in self
42 42
43 43 The item argument may be a wrapped object.
44 44 """
45 45
46 46 @abc.abstractmethod
47 47 def getmember(self, context, mapping, key):
48 48 """Return a member item for the specified key
49 49
50 50 The key argument may be a wrapped object.
51 51 A returned object may be either a wrapped object or a pure value
52 52 depending on the self type.
53 53 """
54 54
55 55 @abc.abstractmethod
56 56 def getmin(self, context, mapping):
57 57 """Return the smallest item, which may be either a wrapped or a pure
58 58 value depending on the self type"""
59 59
60 60 @abc.abstractmethod
61 61 def getmax(self, context, mapping):
62 62 """Return the largest item, which may be either a wrapped or a pure
63 63 value depending on the self type"""
64 64
65 65 @abc.abstractmethod
66 66 def filter(self, context, mapping, select):
67 67 """Return new container of the same type which includes only the
68 68 selected elements
69 69
70 70 select() takes each item as a wrapped object and returns True/False.
71 71 """
72 72
73 73 @abc.abstractmethod
74 74 def itermaps(self, context):
75 75 """Yield each template mapping"""
76 76
77 77 @abc.abstractmethod
78 78 def join(self, context, mapping, sep):
79 79 """Join items with the separator; Returns a bytes or (possibly nested)
80 80 generator of bytes
81 81
82 82 A pre-configured template may be rendered per item if this container
83 83 holds unprintable items.
84 84 """
85 85
86 86 @abc.abstractmethod
87 87 def show(self, context, mapping):
88 88 """Return a bytes or (possibly nested) generator of bytes representing
89 89 the underlying object
90 90
91 91 A pre-configured template may be rendered if the underlying object is
92 92 not printable.
93 93 """
94 94
95 95 @abc.abstractmethod
96 96 def tobool(self, context, mapping):
97 97 """Return a boolean representation of the inner value"""
98 98
99 99 @abc.abstractmethod
100 100 def tovalue(self, context, mapping):
101 101 """Move the inner value object out or create a value representation
102 102
103 103 A returned value must be serializable by templaterfilters.json().
104 104 """
105 105
106 106 class mappable(object):
107 107 """Object which can be converted to a single template mapping"""
108 108
109 109 def itermaps(self, context):
110 110 yield self.tomap(context)
111 111
112 112 @abc.abstractmethod
113 113 def tomap(self, context):
114 114 """Create a single template mapping representing this"""
115 115
116 116 class wrappedbytes(wrapped):
117 117 """Wrapper for byte string"""
118 118
119 119 def __init__(self, value):
120 120 self._value = value
121 121
122 122 def contains(self, context, mapping, item):
123 123 item = stringify(context, mapping, item)
124 124 return item in self._value
125 125
126 126 def getmember(self, context, mapping, key):
127 127 raise error.ParseError(_('%r is not a dictionary')
128 128 % pycompat.bytestr(self._value))
129 129
130 130 def getmin(self, context, mapping):
131 131 return self._getby(context, mapping, min)
132 132
133 133 def getmax(self, context, mapping):
134 134 return self._getby(context, mapping, max)
135 135
136 136 def _getby(self, context, mapping, func):
137 137 if not self._value:
138 138 raise error.ParseError(_('empty string'))
139 139 return func(pycompat.iterbytestr(self._value))
140 140
141 141 def filter(self, context, mapping, select):
142 142 raise error.ParseError(_('%r is not filterable')
143 143 % pycompat.bytestr(self._value))
144 144
145 145 def itermaps(self, context):
146 146 raise error.ParseError(_('%r is not iterable of mappings')
147 147 % pycompat.bytestr(self._value))
148 148
149 149 def join(self, context, mapping, sep):
150 150 return joinitems(pycompat.iterbytestr(self._value), sep)
151 151
152 152 def show(self, context, mapping):
153 153 return self._value
154 154
155 155 def tobool(self, context, mapping):
156 156 return bool(self._value)
157 157
158 158 def tovalue(self, context, mapping):
159 159 return self._value
160 160
161 161 class wrappedvalue(wrapped):
162 162 """Generic wrapper for pure non-list/dict/bytes value"""
163 163
164 164 def __init__(self, value):
165 165 self._value = value
166 166
167 167 def contains(self, context, mapping, item):
168 168 raise error.ParseError(_("%r is not iterable") % self._value)
169 169
170 170 def getmember(self, context, mapping, key):
171 171 raise error.ParseError(_('%r is not a dictionary') % self._value)
172 172
173 173 def getmin(self, context, mapping):
174 174 raise error.ParseError(_("%r is not iterable") % self._value)
175 175
176 176 def getmax(self, context, mapping):
177 177 raise error.ParseError(_("%r is not iterable") % self._value)
178 178
179 179 def filter(self, context, mapping, select):
180 180 raise error.ParseError(_("%r is not iterable") % self._value)
181 181
182 182 def itermaps(self, context):
183 183 raise error.ParseError(_('%r is not iterable of mappings')
184 184 % self._value)
185 185
186 186 def join(self, context, mapping, sep):
187 187 raise error.ParseError(_('%r is not iterable') % self._value)
188 188
189 189 def show(self, context, mapping):
190 190 if self._value is None:
191 191 return b''
192 192 return pycompat.bytestr(self._value)
193 193
194 194 def tobool(self, context, mapping):
195 195 if self._value is None:
196 196 return False
197 197 if isinstance(self._value, bool):
198 198 return self._value
199 199 # otherwise evaluate as string, which means 0 is True
200 200 return bool(pycompat.bytestr(self._value))
201 201
202 202 def tovalue(self, context, mapping):
203 203 return self._value
204 204
205 205 class date(mappable, wrapped):
206 206 """Wrapper for date tuple"""
207 207
208 208 def __init__(self, value, showfmt='%d %d'):
209 209 # value may be (float, int), but public interface shouldn't support
210 210 # floating-point timestamp
211 211 self._unixtime, self._tzoffset = map(int, value)
212 212 self._showfmt = showfmt
213 213
214 214 def contains(self, context, mapping, item):
215 215 raise error.ParseError(_('date is not iterable'))
216 216
217 217 def getmember(self, context, mapping, key):
218 218 raise error.ParseError(_('date is not a dictionary'))
219 219
220 220 def getmin(self, context, mapping):
221 221 raise error.ParseError(_('date is not iterable'))
222 222
223 223 def getmax(self, context, mapping):
224 224 raise error.ParseError(_('date is not iterable'))
225 225
226 226 def filter(self, context, mapping, select):
227 227 raise error.ParseError(_('date is not iterable'))
228 228
229 229 def join(self, context, mapping, sep):
230 230 raise error.ParseError(_("date is not iterable"))
231 231
232 232 def show(self, context, mapping):
233 233 return self._showfmt % (self._unixtime, self._tzoffset)
234 234
235 235 def tomap(self, context):
236 236 return {'unixtime': self._unixtime, 'tzoffset': self._tzoffset}
237 237
238 238 def tobool(self, context, mapping):
239 239 return True
240 240
241 241 def tovalue(self, context, mapping):
242 242 return (self._unixtime, self._tzoffset)
243 243
244 244 class hybrid(wrapped):
245 245 """Wrapper for list or dict to support legacy template
246 246
247 247 This class allows us to handle both:
248 248 - "{files}" (legacy command-line-specific list hack) and
249 249 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
250 250 and to access raw values:
251 251 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
252 252 - "{get(extras, key)}"
253 253 - "{files|json}"
254 254 """
255 255
256 256 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
257 257 self._gen = gen # generator or function returning generator
258 258 self._values = values
259 259 self._makemap = makemap
260 260 self._joinfmt = joinfmt
261 261 self._keytype = keytype # hint for 'x in y' where type(x) is unresolved
262 262
263 263 def contains(self, context, mapping, item):
264 264 item = unwrapastype(context, mapping, item, self._keytype)
265 265 return item in self._values
266 266
267 267 def getmember(self, context, mapping, key):
268 268 # TODO: maybe split hybrid list/dict types?
269 269 if not util.safehasattr(self._values, 'get'):
270 270 raise error.ParseError(_('not a dictionary'))
271 271 key = unwrapastype(context, mapping, key, self._keytype)
272 272 return self._wrapvalue(key, self._values.get(key))
273 273
274 274 def getmin(self, context, mapping):
275 275 return self._getby(context, mapping, min)
276 276
277 277 def getmax(self, context, mapping):
278 278 return self._getby(context, mapping, max)
279 279
280 280 def _getby(self, context, mapping, func):
281 281 if not self._values:
282 282 raise error.ParseError(_('empty sequence'))
283 283 val = func(self._values)
284 284 return self._wrapvalue(val, val)
285 285
286 286 def _wrapvalue(self, key, val):
287 287 if val is None:
288 288 return
289 289 if util.safehasattr(val, '_makemap'):
290 290 # a nested hybrid list/dict, which has its own way of map operation
291 291 return val
292 292 return hybriditem(None, key, val, self._makemap)
293 293
294 294 def filter(self, context, mapping, select):
295 295 if util.safehasattr(self._values, 'get'):
296 296 values = {k: v for k, v in self._values.iteritems()
297 297 if select(self._wrapvalue(k, v))}
298 298 else:
299 299 values = [v for v in self._values if select(self._wrapvalue(v, v))]
300 300 return hybrid(None, values, self._makemap, self._joinfmt, self._keytype)
301 301
302 302 def itermaps(self, context):
303 303 makemap = self._makemap
304 304 for x in self._values:
305 305 yield makemap(x)
306 306
307 307 def join(self, context, mapping, sep):
308 308 # TODO: switch gen to (context, mapping) API?
309 309 return joinitems((self._joinfmt(x) for x in self._values), sep)
310 310
311 311 def show(self, context, mapping):
312 312 # TODO: switch gen to (context, mapping) API?
313 313 gen = self._gen
314 314 if gen is None:
315 315 return self.join(context, mapping, ' ')
316 316 if callable(gen):
317 317 return gen()
318 318 return gen
319 319
320 320 def tobool(self, context, mapping):
321 321 return bool(self._values)
322 322
323 323 def tovalue(self, context, mapping):
324 324 # TODO: make it non-recursive for trivial lists/dicts
325 325 xs = self._values
326 326 if util.safehasattr(xs, 'get'):
327 327 return {k: unwrapvalue(context, mapping, v)
328 328 for k, v in xs.iteritems()}
329 329 return [unwrapvalue(context, mapping, x) for x in xs]
330 330
331 331 class hybriditem(mappable, wrapped):
332 332 """Wrapper for non-list/dict object to support map operation
333 333
334 334 This class allows us to handle both:
335 335 - "{manifest}"
336 336 - "{manifest % '{rev}:{node}'}"
337 337 - "{manifest.rev}"
338 338 """
339 339
340 340 def __init__(self, gen, key, value, makemap):
341 341 self._gen = gen # generator or function returning generator
342 342 self._key = key
343 343 self._value = value # may be generator of strings
344 344 self._makemap = makemap
345 345
346 346 def tomap(self, context):
347 347 return self._makemap(self._key)
348 348
349 349 def contains(self, context, mapping, item):
350 350 w = makewrapped(context, mapping, self._value)
351 351 return w.contains(context, mapping, item)
352 352
353 353 def getmember(self, context, mapping, key):
354 354 w = makewrapped(context, mapping, self._value)
355 355 return w.getmember(context, mapping, key)
356 356
357 357 def getmin(self, context, mapping):
358 358 w = makewrapped(context, mapping, self._value)
359 359 return w.getmin(context, mapping)
360 360
361 361 def getmax(self, context, mapping):
362 362 w = makewrapped(context, mapping, self._value)
363 363 return w.getmax(context, mapping)
364 364
365 365 def filter(self, context, mapping, select):
366 366 w = makewrapped(context, mapping, self._value)
367 367 return w.filter(context, mapping, select)
368 368
369 369 def join(self, context, mapping, sep):
370 370 w = makewrapped(context, mapping, self._value)
371 371 return w.join(context, mapping, sep)
372 372
373 373 def show(self, context, mapping):
374 374 # TODO: switch gen to (context, mapping) API?
375 375 gen = self._gen
376 376 if gen is None:
377 377 return pycompat.bytestr(self._value)
378 378 if callable(gen):
379 379 return gen()
380 380 return gen
381 381
382 382 def tobool(self, context, mapping):
383 383 w = makewrapped(context, mapping, self._value)
384 384 return w.tobool(context, mapping)
385 385
386 386 def tovalue(self, context, mapping):
387 387 return _unthunk(context, mapping, self._value)
388 388
389 389 class _mappingsequence(wrapped):
390 390 """Wrapper for sequence of template mappings
391 391
392 392 This represents an inner template structure (i.e. a list of dicts),
393 393 which can also be rendered by the specified named/literal template.
394 394
395 395 Template mappings may be nested.
396 396 """
397 397
398 398 def __init__(self, name=None, tmpl=None, sep=''):
399 399 if name is not None and tmpl is not None:
400 400 raise error.ProgrammingError('name and tmpl are mutually exclusive')
401 401 self._name = name
402 402 self._tmpl = tmpl
403 403 self._defaultsep = sep
404 404
405 405 def contains(self, context, mapping, item):
406 406 raise error.ParseError(_('not comparable'))
407 407
408 408 def getmember(self, context, mapping, key):
409 409 raise error.ParseError(_('not a dictionary'))
410 410
411 411 def getmin(self, context, mapping):
412 412 raise error.ParseError(_('not comparable'))
413 413
414 414 def getmax(self, context, mapping):
415 415 raise error.ParseError(_('not comparable'))
416 416
417 417 def filter(self, context, mapping, select):
418 418 # implement if necessary; we'll need a wrapped type for a mapping dict
419 419 raise error.ParseError(_('not filterable without template'))
420 420
421 421 def join(self, context, mapping, sep):
422 422 mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context))
423 423 if self._name:
424 424 itemiter = (context.process(self._name, m) for m in mapsiter)
425 425 elif self._tmpl:
426 426 itemiter = (context.expand(self._tmpl, m) for m in mapsiter)
427 427 else:
428 428 raise error.ParseError(_('not displayable without template'))
429 429 return joinitems(itemiter, sep)
430 430
431 431 def show(self, context, mapping):
432 432 return self.join(context, mapping, self._defaultsep)
433 433
434 434 def tovalue(self, context, mapping):
435 435 knownres = context.knownresourcekeys()
436 436 items = []
437 437 for nm in self.itermaps(context):
438 438 # drop internal resources (recursively) which shouldn't be displayed
439 439 lm = context.overlaymap(mapping, nm)
440 440 items.append({k: unwrapvalue(context, lm, v)
441 441 for k, v in nm.iteritems() if k not in knownres})
442 442 return items
443 443
444 444 class mappinggenerator(_mappingsequence):
445 445 """Wrapper for generator of template mappings
446 446
447 447 The function ``make(context, *args)`` should return a generator of
448 448 mapping dicts.
449 449 """
450 450
451 451 def __init__(self, make, args=(), name=None, tmpl=None, sep=''):
452 452 super(mappinggenerator, self).__init__(name, tmpl, sep)
453 453 self._make = make
454 454 self._args = args
455 455
456 456 def itermaps(self, context):
457 457 return self._make(context, *self._args)
458 458
459 459 def tobool(self, context, mapping):
460 460 return _nonempty(self.itermaps(context))
461 461
462 462 class mappinglist(_mappingsequence):
463 463 """Wrapper for list of template mappings"""
464 464
465 465 def __init__(self, mappings, name=None, tmpl=None, sep=''):
466 466 super(mappinglist, self).__init__(name, tmpl, sep)
467 467 self._mappings = mappings
468 468
469 469 def itermaps(self, context):
470 470 return iter(self._mappings)
471 471
472 472 def tobool(self, context, mapping):
473 473 return bool(self._mappings)
474 474
475 475 class mappedgenerator(wrapped):
476 476 """Wrapper for generator of strings which acts as a list
477 477
478 478 The function ``make(context, *args)`` should return a generator of
479 479 byte strings, or a generator of (possibly nested) generators of byte
480 480 strings (i.e. a generator for a list of byte strings.)
481 481 """
482 482
483 483 def __init__(self, make, args=()):
484 484 self._make = make
485 485 self._args = args
486 486
487 487 def contains(self, context, mapping, item):
488 488 item = stringify(context, mapping, item)
489 489 return item in self.tovalue(context, mapping)
490 490
491 491 def _gen(self, context):
492 492 return self._make(context, *self._args)
493 493
494 494 def getmember(self, context, mapping, key):
495 495 raise error.ParseError(_('not a dictionary'))
496 496
497 497 def getmin(self, context, mapping):
498 498 return self._getby(context, mapping, min)
499 499
500 500 def getmax(self, context, mapping):
501 501 return self._getby(context, mapping, max)
502 502
503 503 def _getby(self, context, mapping, func):
504 504 xs = self.tovalue(context, mapping)
505 505 if not xs:
506 506 raise error.ParseError(_('empty sequence'))
507 507 return func(xs)
508 508
509 509 @staticmethod
510 510 def _filteredgen(context, mapping, make, args, select):
511 511 for x in make(context, *args):
512 512 s = stringify(context, mapping, x)
513 513 if select(wrappedbytes(s)):
514 514 yield s
515 515
516 516 def filter(self, context, mapping, select):
517 517 args = (mapping, self._make, self._args, select)
518 518 return mappedgenerator(self._filteredgen, args)
519 519
520 520 def itermaps(self, context):
521 521 raise error.ParseError(_('list of strings is not mappable'))
522 522
523 523 def join(self, context, mapping, sep):
524 524 return joinitems(self._gen(context), sep)
525 525
526 526 def show(self, context, mapping):
527 527 return self.join(context, mapping, '')
528 528
529 529 def tobool(self, context, mapping):
530 530 return _nonempty(self._gen(context))
531 531
532 532 def tovalue(self, context, mapping):
533 533 return [stringify(context, mapping, x) for x in self._gen(context)]
534 534
535 535 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
536 536 """Wrap data to support both dict-like and string-like operations"""
537 537 prefmt = pycompat.identity
538 538 if fmt is None:
539 539 fmt = '%s=%s'
540 540 prefmt = pycompat.bytestr
541 541 return hybrid(gen, data, lambda k: {key: k, value: data[k]},
542 542 lambda k: fmt % (prefmt(k), prefmt(data[k])))
543 543
544 544 def hybridlist(data, name, fmt=None, gen=None):
545 545 """Wrap data to support both list-like and string-like operations"""
546 546 prefmt = pycompat.identity
547 547 if fmt is None:
548 548 fmt = '%s'
549 549 prefmt = pycompat.bytestr
550 550 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
551 551
552 552 def compatdict(context, mapping, name, data, key='key', value='value',
553 553 fmt=None, plural=None, separator=' '):
554 554 """Wrap data like hybriddict(), but also supports old-style list template
555 555
556 556 This exists for backward compatibility with the old-style template. Use
557 557 hybriddict() for new template keywords.
558 558 """
559 559 c = [{key: k, value: v} for k, v in data.iteritems()]
560 560 f = _showcompatlist(context, mapping, name, c, plural, separator)
561 561 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
562 562
563 563 def compatlist(context, mapping, name, data, element=None, fmt=None,
564 564 plural=None, separator=' '):
565 565 """Wrap data like hybridlist(), but also supports old-style list template
566 566
567 567 This exists for backward compatibility with the old-style template. Use
568 568 hybridlist() for new template keywords.
569 569 """
570 570 f = _showcompatlist(context, mapping, name, data, plural, separator)
571 571 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
572 572
573 def compatfilecopiesdict(context, mapping, name, copies):
574 """Wrap list of (dest, source) file names to support old-style list
575 template and field names
576
577 This exists for backward compatibility. Use hybriddict for new template
578 keywords.
579 """
580 # no need to provide {path} to old-style list template
581 c = [{'name': k, 'source': v} for k, v in copies]
582 f = _showcompatlist(context, mapping, name, c, plural='file_copies')
583 copies = util.sortdict(copies)
584 return hybrid(f, copies,
585 lambda k: {'name': k, 'path': k, 'source': copies[k]},
586 lambda k: '%s (%s)' % (k, copies[k]))
587
573 588 def compatfileslist(context, mapping, name, files):
574 589 """Wrap list of file names to support old-style list template and field
575 590 names
576 591
577 592 This exists for backward compatibility. Use hybridlist for new template
578 593 keywords.
579 594 """
580 595 f = _showcompatlist(context, mapping, name, files)
581 596 return hybrid(f, files, lambda x: {'file': x, 'path': x},
582 597 pycompat.identity)
583 598
584 599 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
585 600 """Return a generator that renders old-style list template
586 601
587 602 name is name of key in template map.
588 603 values is list of strings or dicts.
589 604 plural is plural of name, if not simply name + 's'.
590 605 separator is used to join values as a string
591 606
592 607 expansion works like this, given name 'foo'.
593 608
594 609 if values is empty, expand 'no_foos'.
595 610
596 611 if 'foo' not in template map, return values as a string,
597 612 joined by 'separator'.
598 613
599 614 expand 'start_foos'.
600 615
601 616 for each value, expand 'foo'. if 'last_foo' in template
602 617 map, expand it instead of 'foo' for last key.
603 618
604 619 expand 'end_foos'.
605 620 """
606 621 if not plural:
607 622 plural = name + 's'
608 623 if not values:
609 624 noname = 'no_' + plural
610 625 if context.preload(noname):
611 626 yield context.process(noname, mapping)
612 627 return
613 628 if not context.preload(name):
614 629 if isinstance(values[0], bytes):
615 630 yield separator.join(values)
616 631 else:
617 632 for v in values:
618 633 r = dict(v)
619 634 r.update(mapping)
620 635 yield r
621 636 return
622 637 startname = 'start_' + plural
623 638 if context.preload(startname):
624 639 yield context.process(startname, mapping)
625 640 def one(v, tag=name):
626 641 vmapping = {}
627 642 try:
628 643 vmapping.update(v)
629 644 # Python 2 raises ValueError if the type of v is wrong. Python
630 645 # 3 raises TypeError.
631 646 except (AttributeError, TypeError, ValueError):
632 647 try:
633 648 # Python 2 raises ValueError trying to destructure an e.g.
634 649 # bytes. Python 3 raises TypeError.
635 650 for a, b in v:
636 651 vmapping[a] = b
637 652 except (TypeError, ValueError):
638 653 vmapping[name] = v
639 654 vmapping = context.overlaymap(mapping, vmapping)
640 655 return context.process(tag, vmapping)
641 656 lastname = 'last_' + name
642 657 if context.preload(lastname):
643 658 last = values.pop()
644 659 else:
645 660 last = None
646 661 for v in values:
647 662 yield one(v)
648 663 if last is not None:
649 664 yield one(last, tag=lastname)
650 665 endname = 'end_' + plural
651 666 if context.preload(endname):
652 667 yield context.process(endname, mapping)
653 668
654 669 def flatten(context, mapping, thing):
655 670 """Yield a single stream from a possibly nested set of iterators"""
656 671 if isinstance(thing, wrapped):
657 672 thing = thing.show(context, mapping)
658 673 if isinstance(thing, bytes):
659 674 yield thing
660 675 elif isinstance(thing, str):
661 676 # We can only hit this on Python 3, and it's here to guard
662 677 # against infinite recursion.
663 678 raise error.ProgrammingError('Mercurial IO including templates is done'
664 679 ' with bytes, not strings, got %r' % thing)
665 680 elif thing is None:
666 681 pass
667 682 elif not util.safehasattr(thing, '__iter__'):
668 683 yield pycompat.bytestr(thing)
669 684 else:
670 685 for i in thing:
671 686 if isinstance(i, wrapped):
672 687 i = i.show(context, mapping)
673 688 if isinstance(i, bytes):
674 689 yield i
675 690 elif i is None:
676 691 pass
677 692 elif not util.safehasattr(i, '__iter__'):
678 693 yield pycompat.bytestr(i)
679 694 else:
680 695 for j in flatten(context, mapping, i):
681 696 yield j
682 697
683 698 def stringify(context, mapping, thing):
684 699 """Turn values into bytes by converting into text and concatenating them"""
685 700 if isinstance(thing, bytes):
686 701 return thing # retain localstr to be round-tripped
687 702 return b''.join(flatten(context, mapping, thing))
688 703
689 704 def findsymbolicname(arg):
690 705 """Find symbolic name for the given compiled expression; returns None
691 706 if nothing found reliably"""
692 707 while True:
693 708 func, data = arg
694 709 if func is runsymbol:
695 710 return data
696 711 elif func is runfilter:
697 712 arg = data[0]
698 713 else:
699 714 return None
700 715
701 716 def _nonempty(xiter):
702 717 try:
703 718 next(xiter)
704 719 return True
705 720 except StopIteration:
706 721 return False
707 722
708 723 def _unthunk(context, mapping, thing):
709 724 """Evaluate a lazy byte string into value"""
710 725 if not isinstance(thing, types.GeneratorType):
711 726 return thing
712 727 return stringify(context, mapping, thing)
713 728
714 729 def evalrawexp(context, mapping, arg):
715 730 """Evaluate given argument as a bare template object which may require
716 731 further processing (such as folding generator of strings)"""
717 732 func, data = arg
718 733 return func(context, mapping, data)
719 734
720 735 def evalwrapped(context, mapping, arg):
721 736 """Evaluate given argument to wrapped object"""
722 737 thing = evalrawexp(context, mapping, arg)
723 738 return makewrapped(context, mapping, thing)
724 739
725 740 def makewrapped(context, mapping, thing):
726 741 """Lift object to a wrapped type"""
727 742 if isinstance(thing, wrapped):
728 743 return thing
729 744 thing = _unthunk(context, mapping, thing)
730 745 if isinstance(thing, bytes):
731 746 return wrappedbytes(thing)
732 747 return wrappedvalue(thing)
733 748
734 749 def evalfuncarg(context, mapping, arg):
735 750 """Evaluate given argument as value type"""
736 751 return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
737 752
738 753 def unwrapvalue(context, mapping, thing):
739 754 """Move the inner value object out of the wrapper"""
740 755 if isinstance(thing, wrapped):
741 756 return thing.tovalue(context, mapping)
742 757 # evalrawexp() may return string, generator of strings or arbitrary object
743 758 # such as date tuple, but filter does not want generator.
744 759 return _unthunk(context, mapping, thing)
745 760
746 761 def evalboolean(context, mapping, arg):
747 762 """Evaluate given argument as boolean, but also takes boolean literals"""
748 763 func, data = arg
749 764 if func is runsymbol:
750 765 thing = func(context, mapping, data, default=None)
751 766 if thing is None:
752 767 # not a template keyword, takes as a boolean literal
753 768 thing = stringutil.parsebool(data)
754 769 else:
755 770 thing = func(context, mapping, data)
756 771 return makewrapped(context, mapping, thing).tobool(context, mapping)
757 772
758 773 def evaldate(context, mapping, arg, err=None):
759 774 """Evaluate given argument as a date tuple or a date string; returns
760 775 a (unixtime, offset) tuple"""
761 776 thing = evalrawexp(context, mapping, arg)
762 777 return unwrapdate(context, mapping, thing, err)
763 778
764 779 def unwrapdate(context, mapping, thing, err=None):
765 780 if isinstance(thing, date):
766 781 return thing.tovalue(context, mapping)
767 782 # TODO: update hgweb to not return bare tuple; then just stringify 'thing'
768 783 thing = unwrapvalue(context, mapping, thing)
769 784 try:
770 785 return dateutil.parsedate(thing)
771 786 except AttributeError:
772 787 raise error.ParseError(err or _('not a date tuple nor a string'))
773 788 except error.ParseError:
774 789 if not err:
775 790 raise
776 791 raise error.ParseError(err)
777 792
778 793 def evalinteger(context, mapping, arg, err=None):
779 794 thing = evalrawexp(context, mapping, arg)
780 795 return unwrapinteger(context, mapping, thing, err)
781 796
782 797 def unwrapinteger(context, mapping, thing, err=None):
783 798 thing = unwrapvalue(context, mapping, thing)
784 799 try:
785 800 return int(thing)
786 801 except (TypeError, ValueError):
787 802 raise error.ParseError(err or _('not an integer'))
788 803
789 804 def evalstring(context, mapping, arg):
790 805 return stringify(context, mapping, evalrawexp(context, mapping, arg))
791 806
792 807 def evalstringliteral(context, mapping, arg):
793 808 """Evaluate given argument as string template, but returns symbol name
794 809 if it is unknown"""
795 810 func, data = arg
796 811 if func is runsymbol:
797 812 thing = func(context, mapping, data, default=data)
798 813 else:
799 814 thing = func(context, mapping, data)
800 815 return stringify(context, mapping, thing)
801 816
802 817 _unwrapfuncbytype = {
803 818 None: unwrapvalue,
804 819 bytes: stringify,
805 820 date: unwrapdate,
806 821 int: unwrapinteger,
807 822 }
808 823
809 824 def unwrapastype(context, mapping, thing, typ):
810 825 """Move the inner value object out of the wrapper and coerce its type"""
811 826 try:
812 827 f = _unwrapfuncbytype[typ]
813 828 except KeyError:
814 829 raise error.ProgrammingError('invalid type specified: %r' % typ)
815 830 return f(context, mapping, thing)
816 831
817 832 def runinteger(context, mapping, data):
818 833 return int(data)
819 834
820 835 def runstring(context, mapping, data):
821 836 return data
822 837
823 838 def _recursivesymbolblocker(key):
824 839 def showrecursion(context, mapping):
825 840 raise error.Abort(_("recursive reference '%s' in template") % key)
826 841 showrecursion._requires = () # mark as new-style templatekw
827 842 return showrecursion
828 843
829 844 def runsymbol(context, mapping, key, default=''):
830 845 v = context.symbol(mapping, key)
831 846 if v is None:
832 847 # put poison to cut recursion. we can't move this to parsing phase
833 848 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
834 849 safemapping = mapping.copy()
835 850 safemapping[key] = _recursivesymbolblocker(key)
836 851 try:
837 852 v = context.process(key, safemapping)
838 853 except TemplateNotFound:
839 854 v = default
840 855 if callable(v) and getattr(v, '_requires', None) is None:
841 856 # old templatekw: expand all keywords and resources
842 857 # (TODO: drop support for old-style functions. 'f._requires = ()'
843 858 # can be removed.)
844 859 props = {k: context._resources.lookup(context, mapping, k)
845 860 for k in context._resources.knownkeys()}
846 861 # pass context to _showcompatlist() through templatekw._showlist()
847 862 props['templ'] = context
848 863 props.update(mapping)
849 864 ui = props.get('ui')
850 865 if ui:
851 866 ui.deprecwarn("old-style template keyword '%s'" % key, '4.8')
852 867 return v(**pycompat.strkwargs(props))
853 868 if callable(v):
854 869 # new templatekw
855 870 try:
856 871 return v(context, mapping)
857 872 except ResourceUnavailable:
858 873 # unsupported keyword is mapped to empty just like unknown keyword
859 874 return None
860 875 return v
861 876
862 877 def runtemplate(context, mapping, template):
863 878 for arg in template:
864 879 yield evalrawexp(context, mapping, arg)
865 880
866 881 def runfilter(context, mapping, data):
867 882 arg, filt = data
868 883 thing = evalrawexp(context, mapping, arg)
869 884 intype = getattr(filt, '_intype', None)
870 885 try:
871 886 thing = unwrapastype(context, mapping, thing, intype)
872 887 return filt(thing)
873 888 except error.ParseError as e:
874 889 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
875 890
876 891 def _formatfiltererror(arg, filt):
877 892 fn = pycompat.sysbytes(filt.__name__)
878 893 sym = findsymbolicname(arg)
879 894 if not sym:
880 895 return _("incompatible use of template filter '%s'") % fn
881 896 return (_("template filter '%s' is not compatible with keyword '%s'")
882 897 % (fn, sym))
883 898
884 899 def _iteroverlaymaps(context, origmapping, newmappings):
885 900 """Generate combined mappings from the original mapping and an iterable
886 901 of partial mappings to override the original"""
887 902 for i, nm in enumerate(newmappings):
888 903 lm = context.overlaymap(origmapping, nm)
889 904 lm['index'] = i
890 905 yield lm
891 906
892 907 def _applymap(context, mapping, d, darg, targ):
893 908 try:
894 909 diter = d.itermaps(context)
895 910 except error.ParseError as err:
896 911 sym = findsymbolicname(darg)
897 912 if not sym:
898 913 raise
899 914 hint = _("keyword '%s' does not support map operation") % sym
900 915 raise error.ParseError(bytes(err), hint=hint)
901 916 for lm in _iteroverlaymaps(context, mapping, diter):
902 917 yield evalrawexp(context, lm, targ)
903 918
904 919 def runmap(context, mapping, data):
905 920 darg, targ = data
906 921 d = evalwrapped(context, mapping, darg)
907 922 return mappedgenerator(_applymap, args=(mapping, d, darg, targ))
908 923
909 924 def runmember(context, mapping, data):
910 925 darg, memb = data
911 926 d = evalwrapped(context, mapping, darg)
912 927 if isinstance(d, mappable):
913 928 lm = context.overlaymap(mapping, d.tomap(context))
914 929 return runsymbol(context, lm, memb)
915 930 try:
916 931 return d.getmember(context, mapping, memb)
917 932 except error.ParseError as err:
918 933 sym = findsymbolicname(darg)
919 934 if not sym:
920 935 raise
921 936 hint = _("keyword '%s' does not support member operation") % sym
922 937 raise error.ParseError(bytes(err), hint=hint)
923 938
924 939 def runnegate(context, mapping, data):
925 940 data = evalinteger(context, mapping, data,
926 941 _('negation needs an integer argument'))
927 942 return -data
928 943
929 944 def runarithmetic(context, mapping, data):
930 945 func, left, right = data
931 946 left = evalinteger(context, mapping, left,
932 947 _('arithmetic only defined on integers'))
933 948 right = evalinteger(context, mapping, right,
934 949 _('arithmetic only defined on integers'))
935 950 try:
936 951 return func(left, right)
937 952 except ZeroDivisionError:
938 953 raise error.Abort(_('division by zero is not defined'))
939 954
940 955 def joinitems(itemiter, sep):
941 956 """Join items with the separator; Returns generator of bytes"""
942 957 first = True
943 958 for x in itemiter:
944 959 if first:
945 960 first = False
946 961 elif sep:
947 962 yield sep
948 963 yield x
@@ -1,1246 +1,1264 b''
1 1 Test template keywords
2 2 ======================
3 3
4 4 $ hg init a
5 5 $ cd a
6 6 $ echo a > a
7 7 $ hg add a
8 8 $ echo line 1 > b
9 9 $ echo line 2 >> b
10 10 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
11 11
12 12 $ hg add b
13 13 $ echo other 1 > c
14 14 $ echo other 2 >> c
15 15 $ echo >> c
16 16 $ echo other 3 >> c
17 17 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
18 18
19 19 $ hg add c
20 20 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
21 21 $ echo c >> c
22 22 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
23 23
24 24 $ echo foo > .hg/branch
25 25 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
26 26
27 27 $ hg co -q 3
28 28 $ echo other 4 >> d
29 29 $ hg add d
30 30 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
31 31
32 32 $ hg merge -q foo
33 33 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
34 34
35 35 Second branch starting at nullrev:
36 36
37 37 $ hg update null
38 38 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
39 39 $ echo second > second
40 40 $ hg add second
41 41 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
42 42 created new head
43 43
44 44 $ echo third > third
45 45 $ hg add third
46 46 $ hg mv second fourth
47 47 $ hg commit -m third -d "2020-01-01 10:01"
48 48
49 49 Working-directory revision has special identifiers, though they are still
50 50 experimental:
51 51
52 52 $ hg log -r 'wdir()' -T '{rev}:{node}\n'
53 53 2147483647:ffffffffffffffffffffffffffffffffffffffff
54 54
55 55 Some keywords are invalid for working-directory revision, but they should
56 56 never cause crash:
57 57
58 58 $ hg log -r 'wdir()' -T '{manifest}\n'
59 59
60 60
61 61 Check that {phase} works correctly on parents:
62 62
63 63 $ cat << EOF > parentphase
64 64 > changeset_debug = '{rev} ({phase}):{parents}\n'
65 65 > parent = ' {rev} ({phase})'
66 66 > EOF
67 67 $ hg phase -r 5 --public
68 68 $ hg phase -r 7 --secret --force
69 69 $ hg log --debug -G --style ./parentphase
70 70 @ 8 (secret): 7 (secret) -1 (public)
71 71 |
72 72 o 7 (secret): -1 (public) -1 (public)
73 73
74 74 o 6 (draft): 5 (public) 4 (draft)
75 75 |\
76 76 | o 5 (public): 3 (public) -1 (public)
77 77 | |
78 78 o | 4 (draft): 3 (public) -1 (public)
79 79 |/
80 80 o 3 (public): 2 (public) -1 (public)
81 81 |
82 82 o 2 (public): 1 (public) -1 (public)
83 83 |
84 84 o 1 (public): 0 (public) -1 (public)
85 85 |
86 86 o 0 (public): -1 (public) -1 (public)
87 87
88 88
89 89 Keys work:
90 90
91 91 $ for key in author branch branches date desc file_adds file_dels file_mods \
92 92 > file_copies file_copies_switch files \
93 93 > manifest node parents rev tags diffstat extras \
94 94 > p1rev p2rev p1node p2node user; do
95 95 > for mode in '' --verbose --debug; do
96 96 > hg log $mode --template "$key$mode: {$key}\n"
97 97 > done
98 98 > done
99 99 author: test
100 100 author: User Name <user@hostname>
101 101 author: person
102 102 author: person
103 103 author: person
104 104 author: person
105 105 author: other@place
106 106 author: A. N. Other <other@place>
107 107 author: User Name <user@hostname>
108 108 author--verbose: test
109 109 author--verbose: User Name <user@hostname>
110 110 author--verbose: person
111 111 author--verbose: person
112 112 author--verbose: person
113 113 author--verbose: person
114 114 author--verbose: other@place
115 115 author--verbose: A. N. Other <other@place>
116 116 author--verbose: User Name <user@hostname>
117 117 author--debug: test
118 118 author--debug: User Name <user@hostname>
119 119 author--debug: person
120 120 author--debug: person
121 121 author--debug: person
122 122 author--debug: person
123 123 author--debug: other@place
124 124 author--debug: A. N. Other <other@place>
125 125 author--debug: User Name <user@hostname>
126 126 branch: default
127 127 branch: default
128 128 branch: default
129 129 branch: default
130 130 branch: foo
131 131 branch: default
132 132 branch: default
133 133 branch: default
134 134 branch: default
135 135 branch--verbose: default
136 136 branch--verbose: default
137 137 branch--verbose: default
138 138 branch--verbose: default
139 139 branch--verbose: foo
140 140 branch--verbose: default
141 141 branch--verbose: default
142 142 branch--verbose: default
143 143 branch--verbose: default
144 144 branch--debug: default
145 145 branch--debug: default
146 146 branch--debug: default
147 147 branch--debug: default
148 148 branch--debug: foo
149 149 branch--debug: default
150 150 branch--debug: default
151 151 branch--debug: default
152 152 branch--debug: default
153 153 branches:
154 154 branches:
155 155 branches:
156 156 branches:
157 157 branches: foo
158 158 branches:
159 159 branches:
160 160 branches:
161 161 branches:
162 162 branches--verbose:
163 163 branches--verbose:
164 164 branches--verbose:
165 165 branches--verbose:
166 166 branches--verbose: foo
167 167 branches--verbose:
168 168 branches--verbose:
169 169 branches--verbose:
170 170 branches--verbose:
171 171 branches--debug:
172 172 branches--debug:
173 173 branches--debug:
174 174 branches--debug:
175 175 branches--debug: foo
176 176 branches--debug:
177 177 branches--debug:
178 178 branches--debug:
179 179 branches--debug:
180 180 date: 1577872860.00
181 181 date: 1000000.00
182 182 date: 1500001.00
183 183 date: 1500000.00
184 184 date: 1400000.00
185 185 date: 1300000.00
186 186 date: 1200000.00
187 187 date: 1100000.00
188 188 date: 1000000.00
189 189 date--verbose: 1577872860.00
190 190 date--verbose: 1000000.00
191 191 date--verbose: 1500001.00
192 192 date--verbose: 1500000.00
193 193 date--verbose: 1400000.00
194 194 date--verbose: 1300000.00
195 195 date--verbose: 1200000.00
196 196 date--verbose: 1100000.00
197 197 date--verbose: 1000000.00
198 198 date--debug: 1577872860.00
199 199 date--debug: 1000000.00
200 200 date--debug: 1500001.00
201 201 date--debug: 1500000.00
202 202 date--debug: 1400000.00
203 203 date--debug: 1300000.00
204 204 date--debug: 1200000.00
205 205 date--debug: 1100000.00
206 206 date--debug: 1000000.00
207 207 desc: third
208 208 desc: second
209 209 desc: merge
210 210 desc: new head
211 211 desc: new branch
212 212 desc: no user, no domain
213 213 desc: no person
214 214 desc: other 1
215 215 other 2
216 216
217 217 other 3
218 218 desc: line 1
219 219 line 2
220 220 desc--verbose: third
221 221 desc--verbose: second
222 222 desc--verbose: merge
223 223 desc--verbose: new head
224 224 desc--verbose: new branch
225 225 desc--verbose: no user, no domain
226 226 desc--verbose: no person
227 227 desc--verbose: other 1
228 228 other 2
229 229
230 230 other 3
231 231 desc--verbose: line 1
232 232 line 2
233 233 desc--debug: third
234 234 desc--debug: second
235 235 desc--debug: merge
236 236 desc--debug: new head
237 237 desc--debug: new branch
238 238 desc--debug: no user, no domain
239 239 desc--debug: no person
240 240 desc--debug: other 1
241 241 other 2
242 242
243 243 other 3
244 244 desc--debug: line 1
245 245 line 2
246 246 file_adds: fourth third
247 247 file_adds: second
248 248 file_adds:
249 249 file_adds: d
250 250 file_adds:
251 251 file_adds:
252 252 file_adds: c
253 253 file_adds: b
254 254 file_adds: a
255 255 file_adds--verbose: fourth third
256 256 file_adds--verbose: second
257 257 file_adds--verbose:
258 258 file_adds--verbose: d
259 259 file_adds--verbose:
260 260 file_adds--verbose:
261 261 file_adds--verbose: c
262 262 file_adds--verbose: b
263 263 file_adds--verbose: a
264 264 file_adds--debug: fourth third
265 265 file_adds--debug: second
266 266 file_adds--debug:
267 267 file_adds--debug: d
268 268 file_adds--debug:
269 269 file_adds--debug:
270 270 file_adds--debug: c
271 271 file_adds--debug: b
272 272 file_adds--debug: a
273 273 file_dels: second
274 274 file_dels:
275 275 file_dels:
276 276 file_dels:
277 277 file_dels:
278 278 file_dels:
279 279 file_dels:
280 280 file_dels:
281 281 file_dels:
282 282 file_dels--verbose: second
283 283 file_dels--verbose:
284 284 file_dels--verbose:
285 285 file_dels--verbose:
286 286 file_dels--verbose:
287 287 file_dels--verbose:
288 288 file_dels--verbose:
289 289 file_dels--verbose:
290 290 file_dels--verbose:
291 291 file_dels--debug: second
292 292 file_dels--debug:
293 293 file_dels--debug:
294 294 file_dels--debug:
295 295 file_dels--debug:
296 296 file_dels--debug:
297 297 file_dels--debug:
298 298 file_dels--debug:
299 299 file_dels--debug:
300 300 file_mods:
301 301 file_mods:
302 302 file_mods:
303 303 file_mods:
304 304 file_mods:
305 305 file_mods: c
306 306 file_mods:
307 307 file_mods:
308 308 file_mods:
309 309 file_mods--verbose:
310 310 file_mods--verbose:
311 311 file_mods--verbose:
312 312 file_mods--verbose:
313 313 file_mods--verbose:
314 314 file_mods--verbose: c
315 315 file_mods--verbose:
316 316 file_mods--verbose:
317 317 file_mods--verbose:
318 318 file_mods--debug:
319 319 file_mods--debug:
320 320 file_mods--debug:
321 321 file_mods--debug:
322 322 file_mods--debug:
323 323 file_mods--debug: c
324 324 file_mods--debug:
325 325 file_mods--debug:
326 326 file_mods--debug:
327 327 file_copies: fourth (second)
328 328 file_copies:
329 329 file_copies:
330 330 file_copies:
331 331 file_copies:
332 332 file_copies:
333 333 file_copies:
334 334 file_copies:
335 335 file_copies:
336 336 file_copies--verbose: fourth (second)
337 337 file_copies--verbose:
338 338 file_copies--verbose:
339 339 file_copies--verbose:
340 340 file_copies--verbose:
341 341 file_copies--verbose:
342 342 file_copies--verbose:
343 343 file_copies--verbose:
344 344 file_copies--verbose:
345 345 file_copies--debug: fourth (second)
346 346 file_copies--debug:
347 347 file_copies--debug:
348 348 file_copies--debug:
349 349 file_copies--debug:
350 350 file_copies--debug:
351 351 file_copies--debug:
352 352 file_copies--debug:
353 353 file_copies--debug:
354 354 file_copies_switch:
355 355 file_copies_switch:
356 356 file_copies_switch:
357 357 file_copies_switch:
358 358 file_copies_switch:
359 359 file_copies_switch:
360 360 file_copies_switch:
361 361 file_copies_switch:
362 362 file_copies_switch:
363 363 file_copies_switch--verbose:
364 364 file_copies_switch--verbose:
365 365 file_copies_switch--verbose:
366 366 file_copies_switch--verbose:
367 367 file_copies_switch--verbose:
368 368 file_copies_switch--verbose:
369 369 file_copies_switch--verbose:
370 370 file_copies_switch--verbose:
371 371 file_copies_switch--verbose:
372 372 file_copies_switch--debug:
373 373 file_copies_switch--debug:
374 374 file_copies_switch--debug:
375 375 file_copies_switch--debug:
376 376 file_copies_switch--debug:
377 377 file_copies_switch--debug:
378 378 file_copies_switch--debug:
379 379 file_copies_switch--debug:
380 380 file_copies_switch--debug:
381 381 files: fourth second third
382 382 files: second
383 383 files:
384 384 files: d
385 385 files:
386 386 files: c
387 387 files: c
388 388 files: b
389 389 files: a
390 390 files--verbose: fourth second third
391 391 files--verbose: second
392 392 files--verbose:
393 393 files--verbose: d
394 394 files--verbose:
395 395 files--verbose: c
396 396 files--verbose: c
397 397 files--verbose: b
398 398 files--verbose: a
399 399 files--debug: fourth second third
400 400 files--debug: second
401 401 files--debug:
402 402 files--debug: d
403 403 files--debug:
404 404 files--debug: c
405 405 files--debug: c
406 406 files--debug: b
407 407 files--debug: a
408 408 manifest: 6:94961b75a2da
409 409 manifest: 5:f2dbc354b94e
410 410 manifest: 4:4dc3def4f9b4
411 411 manifest: 4:4dc3def4f9b4
412 412 manifest: 3:cb5a1327723b
413 413 manifest: 3:cb5a1327723b
414 414 manifest: 2:6e0e82995c35
415 415 manifest: 1:4e8d705b1e53
416 416 manifest: 0:a0c8bcbbb45c
417 417 manifest--verbose: 6:94961b75a2da
418 418 manifest--verbose: 5:f2dbc354b94e
419 419 manifest--verbose: 4:4dc3def4f9b4
420 420 manifest--verbose: 4:4dc3def4f9b4
421 421 manifest--verbose: 3:cb5a1327723b
422 422 manifest--verbose: 3:cb5a1327723b
423 423 manifest--verbose: 2:6e0e82995c35
424 424 manifest--verbose: 1:4e8d705b1e53
425 425 manifest--verbose: 0:a0c8bcbbb45c
426 426 manifest--debug: 6:94961b75a2da554b4df6fb599e5bfc7d48de0c64
427 427 manifest--debug: 5:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
428 428 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
429 429 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
430 430 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
431 431 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
432 432 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
433 433 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
434 434 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
435 435 node: 95c24699272ef57d062b8bccc32c878bf841784a
436 436 node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
437 437 node: d41e714fe50d9e4a5f11b4d595d543481b5f980b
438 438 node: 13207e5a10d9fd28ec424934298e176197f2c67f
439 439 node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
440 440 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
441 441 node: 97054abb4ab824450e9164180baf491ae0078465
442 442 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
443 443 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
444 444 node--verbose: 95c24699272ef57d062b8bccc32c878bf841784a
445 445 node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
446 446 node--verbose: d41e714fe50d9e4a5f11b4d595d543481b5f980b
447 447 node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
448 448 node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
449 449 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
450 450 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
451 451 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
452 452 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
453 453 node--debug: 95c24699272ef57d062b8bccc32c878bf841784a
454 454 node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
455 455 node--debug: d41e714fe50d9e4a5f11b4d595d543481b5f980b
456 456 node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
457 457 node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
458 458 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
459 459 node--debug: 97054abb4ab824450e9164180baf491ae0078465
460 460 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
461 461 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
462 462 parents:
463 463 parents: -1:000000000000
464 464 parents: 5:13207e5a10d9 4:bbe44766e73d
465 465 parents: 3:10e46f2dcbf4
466 466 parents:
467 467 parents:
468 468 parents:
469 469 parents:
470 470 parents:
471 471 parents--verbose:
472 472 parents--verbose: -1:000000000000
473 473 parents--verbose: 5:13207e5a10d9 4:bbe44766e73d
474 474 parents--verbose: 3:10e46f2dcbf4
475 475 parents--verbose:
476 476 parents--verbose:
477 477 parents--verbose:
478 478 parents--verbose:
479 479 parents--verbose:
480 480 parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000
481 481 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
482 482 parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
483 483 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
484 484 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
485 485 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
486 486 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
487 487 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
488 488 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
489 489 rev: 8
490 490 rev: 7
491 491 rev: 6
492 492 rev: 5
493 493 rev: 4
494 494 rev: 3
495 495 rev: 2
496 496 rev: 1
497 497 rev: 0
498 498 rev--verbose: 8
499 499 rev--verbose: 7
500 500 rev--verbose: 6
501 501 rev--verbose: 5
502 502 rev--verbose: 4
503 503 rev--verbose: 3
504 504 rev--verbose: 2
505 505 rev--verbose: 1
506 506 rev--verbose: 0
507 507 rev--debug: 8
508 508 rev--debug: 7
509 509 rev--debug: 6
510 510 rev--debug: 5
511 511 rev--debug: 4
512 512 rev--debug: 3
513 513 rev--debug: 2
514 514 rev--debug: 1
515 515 rev--debug: 0
516 516 tags: tip
517 517 tags:
518 518 tags:
519 519 tags:
520 520 tags:
521 521 tags:
522 522 tags:
523 523 tags:
524 524 tags:
525 525 tags--verbose: tip
526 526 tags--verbose:
527 527 tags--verbose:
528 528 tags--verbose:
529 529 tags--verbose:
530 530 tags--verbose:
531 531 tags--verbose:
532 532 tags--verbose:
533 533 tags--verbose:
534 534 tags--debug: tip
535 535 tags--debug:
536 536 tags--debug:
537 537 tags--debug:
538 538 tags--debug:
539 539 tags--debug:
540 540 tags--debug:
541 541 tags--debug:
542 542 tags--debug:
543 543 diffstat: 3: +2/-1
544 544 diffstat: 1: +1/-0
545 545 diffstat: 0: +0/-0
546 546 diffstat: 1: +1/-0
547 547 diffstat: 0: +0/-0
548 548 diffstat: 1: +1/-0
549 549 diffstat: 1: +4/-0
550 550 diffstat: 1: +2/-0
551 551 diffstat: 1: +1/-0
552 552 diffstat--verbose: 3: +2/-1
553 553 diffstat--verbose: 1: +1/-0
554 554 diffstat--verbose: 0: +0/-0
555 555 diffstat--verbose: 1: +1/-0
556 556 diffstat--verbose: 0: +0/-0
557 557 diffstat--verbose: 1: +1/-0
558 558 diffstat--verbose: 1: +4/-0
559 559 diffstat--verbose: 1: +2/-0
560 560 diffstat--verbose: 1: +1/-0
561 561 diffstat--debug: 3: +2/-1
562 562 diffstat--debug: 1: +1/-0
563 563 diffstat--debug: 0: +0/-0
564 564 diffstat--debug: 1: +1/-0
565 565 diffstat--debug: 0: +0/-0
566 566 diffstat--debug: 1: +1/-0
567 567 diffstat--debug: 1: +4/-0
568 568 diffstat--debug: 1: +2/-0
569 569 diffstat--debug: 1: +1/-0
570 570 extras: branch=default
571 571 extras: branch=default
572 572 extras: branch=default
573 573 extras: branch=default
574 574 extras: branch=foo
575 575 extras: branch=default
576 576 extras: branch=default
577 577 extras: branch=default
578 578 extras: branch=default
579 579 extras--verbose: branch=default
580 580 extras--verbose: branch=default
581 581 extras--verbose: branch=default
582 582 extras--verbose: branch=default
583 583 extras--verbose: branch=foo
584 584 extras--verbose: branch=default
585 585 extras--verbose: branch=default
586 586 extras--verbose: branch=default
587 587 extras--verbose: branch=default
588 588 extras--debug: branch=default
589 589 extras--debug: branch=default
590 590 extras--debug: branch=default
591 591 extras--debug: branch=default
592 592 extras--debug: branch=foo
593 593 extras--debug: branch=default
594 594 extras--debug: branch=default
595 595 extras--debug: branch=default
596 596 extras--debug: branch=default
597 597 p1rev: 7
598 598 p1rev: -1
599 599 p1rev: 5
600 600 p1rev: 3
601 601 p1rev: 3
602 602 p1rev: 2
603 603 p1rev: 1
604 604 p1rev: 0
605 605 p1rev: -1
606 606 p1rev--verbose: 7
607 607 p1rev--verbose: -1
608 608 p1rev--verbose: 5
609 609 p1rev--verbose: 3
610 610 p1rev--verbose: 3
611 611 p1rev--verbose: 2
612 612 p1rev--verbose: 1
613 613 p1rev--verbose: 0
614 614 p1rev--verbose: -1
615 615 p1rev--debug: 7
616 616 p1rev--debug: -1
617 617 p1rev--debug: 5
618 618 p1rev--debug: 3
619 619 p1rev--debug: 3
620 620 p1rev--debug: 2
621 621 p1rev--debug: 1
622 622 p1rev--debug: 0
623 623 p1rev--debug: -1
624 624 p2rev: -1
625 625 p2rev: -1
626 626 p2rev: 4
627 627 p2rev: -1
628 628 p2rev: -1
629 629 p2rev: -1
630 630 p2rev: -1
631 631 p2rev: -1
632 632 p2rev: -1
633 633 p2rev--verbose: -1
634 634 p2rev--verbose: -1
635 635 p2rev--verbose: 4
636 636 p2rev--verbose: -1
637 637 p2rev--verbose: -1
638 638 p2rev--verbose: -1
639 639 p2rev--verbose: -1
640 640 p2rev--verbose: -1
641 641 p2rev--verbose: -1
642 642 p2rev--debug: -1
643 643 p2rev--debug: -1
644 644 p2rev--debug: 4
645 645 p2rev--debug: -1
646 646 p2rev--debug: -1
647 647 p2rev--debug: -1
648 648 p2rev--debug: -1
649 649 p2rev--debug: -1
650 650 p2rev--debug: -1
651 651 p1node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
652 652 p1node: 0000000000000000000000000000000000000000
653 653 p1node: 13207e5a10d9fd28ec424934298e176197f2c67f
654 654 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
655 655 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
656 656 p1node: 97054abb4ab824450e9164180baf491ae0078465
657 657 p1node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
658 658 p1node: 1e4e1b8f71e05681d422154f5421e385fec3454f
659 659 p1node: 0000000000000000000000000000000000000000
660 660 p1node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
661 661 p1node--verbose: 0000000000000000000000000000000000000000
662 662 p1node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
663 663 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
664 664 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
665 665 p1node--verbose: 97054abb4ab824450e9164180baf491ae0078465
666 666 p1node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
667 667 p1node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
668 668 p1node--verbose: 0000000000000000000000000000000000000000
669 669 p1node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
670 670 p1node--debug: 0000000000000000000000000000000000000000
671 671 p1node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
672 672 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
673 673 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
674 674 p1node--debug: 97054abb4ab824450e9164180baf491ae0078465
675 675 p1node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
676 676 p1node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
677 677 p1node--debug: 0000000000000000000000000000000000000000
678 678 p2node: 0000000000000000000000000000000000000000
679 679 p2node: 0000000000000000000000000000000000000000
680 680 p2node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
681 681 p2node: 0000000000000000000000000000000000000000
682 682 p2node: 0000000000000000000000000000000000000000
683 683 p2node: 0000000000000000000000000000000000000000
684 684 p2node: 0000000000000000000000000000000000000000
685 685 p2node: 0000000000000000000000000000000000000000
686 686 p2node: 0000000000000000000000000000000000000000
687 687 p2node--verbose: 0000000000000000000000000000000000000000
688 688 p2node--verbose: 0000000000000000000000000000000000000000
689 689 p2node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
690 690 p2node--verbose: 0000000000000000000000000000000000000000
691 691 p2node--verbose: 0000000000000000000000000000000000000000
692 692 p2node--verbose: 0000000000000000000000000000000000000000
693 693 p2node--verbose: 0000000000000000000000000000000000000000
694 694 p2node--verbose: 0000000000000000000000000000000000000000
695 695 p2node--verbose: 0000000000000000000000000000000000000000
696 696 p2node--debug: 0000000000000000000000000000000000000000
697 697 p2node--debug: 0000000000000000000000000000000000000000
698 698 p2node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
699 699 p2node--debug: 0000000000000000000000000000000000000000
700 700 p2node--debug: 0000000000000000000000000000000000000000
701 701 p2node--debug: 0000000000000000000000000000000000000000
702 702 p2node--debug: 0000000000000000000000000000000000000000
703 703 p2node--debug: 0000000000000000000000000000000000000000
704 704 p2node--debug: 0000000000000000000000000000000000000000
705 705 user: test
706 706 user: User Name <user@hostname>
707 707 user: person
708 708 user: person
709 709 user: person
710 710 user: person
711 711 user: other@place
712 712 user: A. N. Other <other@place>
713 713 user: User Name <user@hostname>
714 714 user--verbose: test
715 715 user--verbose: User Name <user@hostname>
716 716 user--verbose: person
717 717 user--verbose: person
718 718 user--verbose: person
719 719 user--verbose: person
720 720 user--verbose: other@place
721 721 user--verbose: A. N. Other <other@place>
722 722 user--verbose: User Name <user@hostname>
723 723 user--debug: test
724 724 user--debug: User Name <user@hostname>
725 725 user--debug: person
726 726 user--debug: person
727 727 user--debug: person
728 728 user--debug: person
729 729 user--debug: other@place
730 730 user--debug: A. N. Other <other@place>
731 731 user--debug: User Name <user@hostname>
732 732
733 733 Add a dummy commit to make up for the instability of the above:
734 734
735 735 $ echo a > a
736 736 $ hg add a
737 737 $ hg ci -m future
738 738
739 739 Add a commit that does all possible modifications at once
740 740
741 741 $ echo modify >> third
742 742 $ touch b
743 743 $ hg add b
744 744 $ hg mv fourth fifth
745 745 $ hg rm a
746 746 $ hg ci -m "Modify, add, remove, rename"
747 747
748 748 Test files list:
749 749
750 750 $ hg log -l1 -T '{join(file_mods, " ")}\n'
751 751 third
752 752 $ hg log -l1 -T '{file_mods % "{file}\n"}'
753 753 third
754 754 $ hg log -l1 -T '{file_mods % "{path}\n"}'
755 755 third
756 756
757 757 $ hg log -l1 -T '{join(files, " ")}\n'
758 758 a b fifth fourth third
759 759 $ hg log -l1 -T '{files % "{file}\n"}'
760 760 a
761 761 b
762 762 fifth
763 763 fourth
764 764 third
765 765 $ hg log -l1 -T '{files % "{path}\n"}'
766 766 a
767 767 b
768 768 fifth
769 769 fourth
770 770 third
771 771
772 Test file copies dict:
773
774 $ hg log -r8 -T '{join(file_copies, " ")}\n'
775 fourth (second)
776 $ hg log -r8 -T '{file_copies % "{name} <- {source}\n"}'
777 fourth <- second
778 $ hg log -r8 -T '{file_copies % "{path} <- {source}\n"}'
779 fourth <- second
780
781 $ hg log -r8 -T '{join(file_copies_switch, " ")}\n'
782
783 $ hg log -r8 -C -T '{join(file_copies_switch, " ")}\n'
784 fourth (second)
785 $ hg log -r8 -C -T '{file_copies_switch % "{name} <- {source}\n"}'
786 fourth <- second
787 $ hg log -r8 -C -T '{file_copies_switch % "{path} <- {source}\n"}'
788 fourth <- second
789
772 790 Test index keyword:
773 791
774 792 $ hg log -l 2 -T '{index + 10}{files % " {index}:{file}"}\n'
775 793 10 0:a 1:b 2:fifth 3:fourth 4:third
776 794 11 0:a
777 795
778 796 $ hg branches -T '{index} {branch}\n'
779 797 0 default
780 798 1 foo
781 799
782 800 ui verbosity:
783 801
784 802 $ hg log -l1 -T '{verbosity}\n'
785 803
786 804 $ hg log -l1 -T '{verbosity}\n' --debug
787 805 debug
788 806 $ hg log -l1 -T '{verbosity}\n' --quiet
789 807 quiet
790 808 $ hg log -l1 -T '{verbosity}\n' --verbose
791 809 verbose
792 810
793 811 $ cd ..
794 812
795 813 latesttag:
796 814
797 815 $ hg init latesttag
798 816 $ cd latesttag
799 817
800 818 $ echo a > file
801 819 $ hg ci -Am a -d '0 0'
802 820 adding file
803 821
804 822 $ echo b >> file
805 823 $ hg ci -m b -d '1 0'
806 824
807 825 $ echo c >> head1
808 826 $ hg ci -Am h1c -d '2 0'
809 827 adding head1
810 828
811 829 $ hg update -q 1
812 830 $ echo d >> head2
813 831 $ hg ci -Am h2d -d '3 0'
814 832 adding head2
815 833 created new head
816 834
817 835 $ echo e >> head2
818 836 $ hg ci -m h2e -d '4 0'
819 837
820 838 $ hg merge -q
821 839 $ hg ci -m merge -d '5 -3600'
822 840
823 841 No tag set:
824 842
825 843 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
826 844 @ 5: null+5
827 845 |\
828 846 | o 4: null+4
829 847 | |
830 848 | o 3: null+3
831 849 | |
832 850 o | 2: null+3
833 851 |/
834 852 o 1: null+2
835 853 |
836 854 o 0: null+1
837 855
838 856
839 857 One common tag: longest path wins for {latesttagdistance}:
840 858
841 859 $ hg tag -r 1 -m t1 -d '6 0' t1
842 860 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
843 861 @ 6: t1+4
844 862 |
845 863 o 5: t1+3
846 864 |\
847 865 | o 4: t1+2
848 866 | |
849 867 | o 3: t1+1
850 868 | |
851 869 o | 2: t1+1
852 870 |/
853 871 o 1: t1+0
854 872 |
855 873 o 0: null+1
856 874
857 875
858 876 One ancestor tag: closest wins:
859 877
860 878 $ hg tag -r 2 -m t2 -d '7 0' t2
861 879 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
862 880 @ 7: t2+3
863 881 |
864 882 o 6: t2+2
865 883 |
866 884 o 5: t2+1
867 885 |\
868 886 | o 4: t1+2
869 887 | |
870 888 | o 3: t1+1
871 889 | |
872 890 o | 2: t2+0
873 891 |/
874 892 o 1: t1+0
875 893 |
876 894 o 0: null+1
877 895
878 896
879 897 Two branch tags: more recent wins if same number of changes:
880 898
881 899 $ hg tag -r 3 -m t3 -d '8 0' t3
882 900 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
883 901 @ 8: t3+5
884 902 |
885 903 o 7: t3+4
886 904 |
887 905 o 6: t3+3
888 906 |
889 907 o 5: t3+2
890 908 |\
891 909 | o 4: t3+1
892 910 | |
893 911 | o 3: t3+0
894 912 | |
895 913 o | 2: t2+0
896 914 |/
897 915 o 1: t1+0
898 916 |
899 917 o 0: null+1
900 918
901 919
902 920 Two branch tags: fewest changes wins:
903 921
904 922 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
905 923 $ hg log -G --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
906 924 @ 9: t4+5,6
907 925 |
908 926 o 8: t4+4,5
909 927 |
910 928 o 7: t4+3,4
911 929 |
912 930 o 6: t4+2,3
913 931 |
914 932 o 5: t4+1,2
915 933 |\
916 934 | o 4: t4+0,0
917 935 | |
918 936 | o 3: t3+0,0
919 937 | |
920 938 o | 2: t2+0,0
921 939 |/
922 940 o 1: t1+0,0
923 941 |
924 942 o 0: null+1,1
925 943
926 944
927 945 Merged tag overrides:
928 946
929 947 $ hg tag -r 5 -m t5 -d '9 0' t5
930 948 $ hg tag -r 3 -m at3 -d '10 0' at3
931 949 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
932 950 @ 11: t5+6
933 951 |
934 952 o 10: t5+5
935 953 |
936 954 o 9: t5+4
937 955 |
938 956 o 8: t5+3
939 957 |
940 958 o 7: t5+2
941 959 |
942 960 o 6: t5+1
943 961 |
944 962 o 5: t5+0
945 963 |\
946 964 | o 4: t4+0
947 965 | |
948 966 | o 3: at3:t3+0
949 967 | |
950 968 o | 2: t2+0
951 969 |/
952 970 o 1: t1+0
953 971 |
954 972 o 0: null+1
955 973
956 974
957 975 $ hg log -G --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
958 976 @ 11: t5+6,6
959 977 |
960 978 o 10: t5+5,5
961 979 |
962 980 o 9: t5+4,4
963 981 |
964 982 o 8: t5+3,3
965 983 |
966 984 o 7: t5+2,2
967 985 |
968 986 o 6: t5+1,1
969 987 |
970 988 o 5: t5+0,0
971 989 |\
972 990 | o 4: t4+0,0
973 991 | |
974 992 | o 3: at3+0,0 t3+0,0
975 993 | |
976 994 o | 2: t2+0,0
977 995 |/
978 996 o 1: t1+0,0
979 997 |
980 998 o 0: null+1,1
981 999
982 1000
983 1001 $ cd ..
984 1002
985 1003 Set up repository containing template fragments in commit metadata:
986 1004
987 1005 $ hg init r
988 1006 $ cd r
989 1007 $ echo a > a
990 1008 $ hg ci -Am '{rev}'
991 1009 adding a
992 1010
993 1011 $ hg branch -q 'text.{rev}'
994 1012 $ echo aa >> aa
995 1013 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
996 1014
997 1015 Test termwidth:
998 1016
999 1017 $ COLUMNS=25 hg log -l1 --template '{fill(desc, termwidth, "{node|short}:", "termwidth.{rev}:")}'
1000 1018 bcc7ff960b8e:desc to be
1001 1019 termwidth.1:wrapped desc
1002 1020 termwidth.1:to be wrapped (no-eol)
1003 1021
1004 1022 Just one more commit:
1005 1023
1006 1024 $ echo b > b
1007 1025 $ hg ci -qAm b
1008 1026
1009 1027 Test 'originalnode'
1010 1028
1011 1029 $ hg log -r 1 -T '{revset("null") % "{node|short} {originalnode|short}"}\n'
1012 1030 000000000000 bcc7ff960b8e
1013 1031 $ hg log -r 0 -T '{manifest % "{node} {originalnode}"}\n'
1014 1032 a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 f7769ec2ab975ad19684098ad1ffd9b81ecc71a1
1015 1033
1016 1034 Test active bookmark templating
1017 1035
1018 1036 $ hg book foo
1019 1037 $ hg book bar
1020 1038 $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, active, \"*\")} '}\n"
1021 1039 2 bar* foo
1022 1040 1
1023 1041 0
1024 1042 $ hg log --template "{rev} {activebookmark}\n"
1025 1043 2 bar
1026 1044 1
1027 1045 0
1028 1046 $ hg bookmarks --inactive bar
1029 1047 $ hg log --template "{rev} {activebookmark}\n"
1030 1048 2
1031 1049 1
1032 1050 0
1033 1051 $ hg book -r1 baz
1034 1052 $ hg log --template "{rev} {join(bookmarks, ' ')}\n"
1035 1053 2 bar foo
1036 1054 1 baz
1037 1055 0
1038 1056 $ hg log --template "{rev} {ifcontains('foo', bookmarks, 't', 'f')}\n"
1039 1057 2 t
1040 1058 1 f
1041 1059 0 f
1042 1060
1043 1061 Test namespaces dict
1044 1062
1045 1063 $ hg --config extensions.revnamesext=$TESTDIR/revnamesext.py log -T '{rev}\n{namespaces % " {namespace} color={colorname} builtin={builtin}\n {join(names, ",")}\n"}\n'
1046 1064 2
1047 1065 bookmarks color=bookmark builtin=True
1048 1066 bar,foo
1049 1067 tags color=tag builtin=True
1050 1068 tip
1051 1069 branches color=branch builtin=True
1052 1070 text.{rev}
1053 1071 revnames color=revname builtin=False
1054 1072 r2
1055 1073
1056 1074 1
1057 1075 bookmarks color=bookmark builtin=True
1058 1076 baz
1059 1077 tags color=tag builtin=True
1060 1078
1061 1079 branches color=branch builtin=True
1062 1080 text.{rev}
1063 1081 revnames color=revname builtin=False
1064 1082 r1
1065 1083
1066 1084 0
1067 1085 bookmarks color=bookmark builtin=True
1068 1086
1069 1087 tags color=tag builtin=True
1070 1088
1071 1089 branches color=branch builtin=True
1072 1090 default
1073 1091 revnames color=revname builtin=False
1074 1092 r0
1075 1093
1076 1094 $ hg log -r2 -T '{namespaces % "{namespace}: {names}\n"}'
1077 1095 bookmarks: bar foo
1078 1096 tags: tip
1079 1097 branches: text.{rev}
1080 1098 $ hg log -r2 -T '{namespaces % "{namespace}:\n{names % " {name}\n"}"}'
1081 1099 bookmarks:
1082 1100 bar
1083 1101 foo
1084 1102 tags:
1085 1103 tip
1086 1104 branches:
1087 1105 text.{rev}
1088 1106 $ hg log -r2 -T '{get(namespaces, "bookmarks") % "{name}\n"}'
1089 1107 bar
1090 1108 foo
1091 1109 $ hg log -r2 -T '{namespaces.bookmarks % "{bookmark}\n"}'
1092 1110 bar
1093 1111 foo
1094 1112
1095 1113 $ cd ..
1096 1114
1097 1115 Test 'graphwidth' in 'hg log' on various topologies. The key here is that the
1098 1116 printed graphwidths 3, 5, 7, etc. should all line up in their respective
1099 1117 columns. We don't care about other aspects of the graph rendering here.
1100 1118
1101 1119 $ hg init graphwidth
1102 1120 $ cd graphwidth
1103 1121
1104 1122 $ wrappabletext="a a a a a a a a a a a a"
1105 1123
1106 1124 $ printf "first\n" > file
1107 1125 $ hg add file
1108 1126 $ hg commit -m "$wrappabletext"
1109 1127
1110 1128 $ printf "first\nsecond\n" > file
1111 1129 $ hg commit -m "$wrappabletext"
1112 1130
1113 1131 $ hg checkout 0
1114 1132 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1115 1133 $ printf "third\nfirst\n" > file
1116 1134 $ hg commit -m "$wrappabletext"
1117 1135 created new head
1118 1136
1119 1137 $ hg merge
1120 1138 merging file
1121 1139 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1122 1140 (branch merge, don't forget to commit)
1123 1141
1124 1142 $ hg log --graph -T "{graphwidth}"
1125 1143 @ 3
1126 1144 |
1127 1145 | @ 5
1128 1146 |/
1129 1147 o 3
1130 1148
1131 1149 $ hg commit -m "$wrappabletext"
1132 1150
1133 1151 $ hg log --graph -T "{graphwidth}"
1134 1152 @ 5
1135 1153 |\
1136 1154 | o 5
1137 1155 | |
1138 1156 o | 5
1139 1157 |/
1140 1158 o 3
1141 1159
1142 1160
1143 1161 $ hg checkout 0
1144 1162 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1145 1163 $ printf "third\nfirst\nsecond\n" > file
1146 1164 $ hg commit -m "$wrappabletext"
1147 1165 created new head
1148 1166
1149 1167 $ hg log --graph -T "{graphwidth}"
1150 1168 @ 3
1151 1169 |
1152 1170 | o 7
1153 1171 | |\
1154 1172 +---o 7
1155 1173 | |
1156 1174 | o 5
1157 1175 |/
1158 1176 o 3
1159 1177
1160 1178
1161 1179 $ hg log --graph -T "{graphwidth}" -r 3
1162 1180 o 5
1163 1181 |\
1164 1182 ~ ~
1165 1183
1166 1184 $ hg log --graph -T "{graphwidth}" -r 1
1167 1185 o 3
1168 1186 |
1169 1187 ~
1170 1188
1171 1189 $ hg merge
1172 1190 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1173 1191 (branch merge, don't forget to commit)
1174 1192 $ hg commit -m "$wrappabletext"
1175 1193
1176 1194 $ printf "seventh\n" >> file
1177 1195 $ hg commit -m "$wrappabletext"
1178 1196
1179 1197 $ hg log --graph -T "{graphwidth}"
1180 1198 @ 3
1181 1199 |
1182 1200 o 5
1183 1201 |\
1184 1202 | o 5
1185 1203 | |
1186 1204 o | 7
1187 1205 |\ \
1188 1206 | o | 7
1189 1207 | |/
1190 1208 o / 5
1191 1209 |/
1192 1210 o 3
1193 1211
1194 1212
1195 1213 The point of graphwidth is to allow wrapping that accounts for the space taken
1196 1214 by the graph.
1197 1215
1198 1216 $ COLUMNS=10 hg log --graph -T "{fill(desc, termwidth - graphwidth)}"
1199 1217 @ a a a a
1200 1218 | a a a a
1201 1219 | a a a a
1202 1220 o a a a
1203 1221 |\ a a a
1204 1222 | | a a a
1205 1223 | | a a a
1206 1224 | o a a a
1207 1225 | | a a a
1208 1226 | | a a a
1209 1227 | | a a a
1210 1228 o | a a
1211 1229 |\ \ a a
1212 1230 | | | a a
1213 1231 | | | a a
1214 1232 | | | a a
1215 1233 | | | a a
1216 1234 | o | a a
1217 1235 | |/ a a
1218 1236 | | a a
1219 1237 | | a a
1220 1238 | | a a
1221 1239 | | a a
1222 1240 o | a a a
1223 1241 |/ a a a
1224 1242 | a a a
1225 1243 | a a a
1226 1244 o a a a a
1227 1245 a a a a
1228 1246 a a a a
1229 1247
1230 1248 Something tricky happens when there are elided nodes; the next drawn row of
1231 1249 edges can be more than one column wider, but the graph width only increases by
1232 1250 one column. The remaining columns are added in between the nodes.
1233 1251
1234 1252 $ hg log --graph -T "{graphwidth}" -r "0|2|4|5"
1235 1253 o 5
1236 1254 |\
1237 1255 | \
1238 1256 | :\
1239 1257 o : : 7
1240 1258 :/ /
1241 1259 : o 5
1242 1260 :/
1243 1261 o 3
1244 1262
1245 1263
1246 1264 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now