##// END OF EJS Templates
py3: convert keys of kwargs in template keywords functions to bytes...
Pulkit Goyal -
r32972:26e710f0 default
parent child Browse files
Show More
@@ -1,688 +1,707 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 short,
15 15 )
16 16
17 17 from . import (
18 18 encoding,
19 19 error,
20 20 hbisect,
21 21 obsutil,
22 22 patch,
23 pycompat,
23 24 registrar,
24 25 scmutil,
25 26 util,
26 27 )
27 28
28 29 class _hybrid(object):
29 30 """Wrapper for list or dict to support legacy template
30 31
31 32 This class allows us to handle both:
32 33 - "{files}" (legacy command-line-specific list hack) and
33 34 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
34 35 and to access raw values:
35 36 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
36 37 - "{get(extras, key)}"
37 38 - "{files|json}"
38 39 """
39 40
40 41 def __init__(self, gen, values, makemap, joinfmt):
41 42 if gen is not None:
42 43 self.gen = gen
43 44 self._values = values
44 45 self._makemap = makemap
45 46 self.joinfmt = joinfmt
46 47 @util.propertycache
47 48 def gen(self):
48 49 return self._defaultgen()
49 50 def _defaultgen(self):
50 51 """Generator to stringify this as {join(self, ' ')}"""
51 52 for i, d in enumerate(self.itermaps()):
52 53 if i > 0:
53 54 yield ' '
54 55 yield self.joinfmt(d)
55 56 def itermaps(self):
56 57 makemap = self._makemap
57 58 for x in self._values:
58 59 yield makemap(x)
59 60 def __contains__(self, x):
60 61 return x in self._values
61 62 def __len__(self):
62 63 return len(self._values)
63 64 def __iter__(self):
64 65 return iter(self._values)
65 66 def __getattr__(self, name):
66 67 if name not in ('get', 'items', 'iteritems', 'iterkeys', 'itervalues',
67 68 'keys', 'values'):
68 69 raise AttributeError(name)
69 70 return getattr(self._values, name)
70 71
71 72 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
72 73 """Wrap data to support both dict-like and string-like operations"""
73 74 return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
74 75 lambda d: fmt % (d[key], d[value]))
75 76
76 77 def hybridlist(data, name, fmt='%s', gen=None):
77 78 """Wrap data to support both list-like and string-like operations"""
78 79 return _hybrid(gen, data, lambda x: {name: x}, lambda d: fmt % d[name])
79 80
80 81 def unwraphybrid(thing):
81 82 """Return an object which can be stringified possibly by using a legacy
82 83 template"""
83 84 if not util.safehasattr(thing, 'gen'):
84 85 return thing
85 86 return thing.gen
86 87
87 88 def showdict(name, data, mapping, plural=None, key='key', value='value',
88 89 fmt='%s=%s', separator=' '):
89 90 c = [{key: k, value: v} for k, v in data.iteritems()]
90 91 f = _showlist(name, c, mapping, plural, separator)
91 92 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
92 93
93 94 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
94 95 if not element:
95 96 element = name
96 97 f = _showlist(name, values, mapping, plural, separator)
97 98 return hybridlist(values, name=element, gen=f)
98 99
99 100 def _showlist(name, values, mapping, plural=None, separator=' '):
100 101 '''expand set of values.
101 102 name is name of key in template map.
102 103 values is list of strings or dicts.
103 104 plural is plural of name, if not simply name + 's'.
104 105 separator is used to join values as a string
105 106
106 107 expansion works like this, given name 'foo'.
107 108
108 109 if values is empty, expand 'no_foos'.
109 110
110 111 if 'foo' not in template map, return values as a string,
111 112 joined by 'separator'.
112 113
113 114 expand 'start_foos'.
114 115
115 116 for each value, expand 'foo'. if 'last_foo' in template
116 117 map, expand it instead of 'foo' for last key.
117 118
118 119 expand 'end_foos'.
119 120 '''
120 121 templ = mapping['templ']
121 122 if not plural:
122 123 plural = name + 's'
123 124 if not values:
124 125 noname = 'no_' + plural
125 126 if noname in templ:
126 127 yield templ(noname, **mapping)
127 128 return
128 129 if name not in templ:
129 130 if isinstance(values[0], bytes):
130 131 yield separator.join(values)
131 132 else:
132 133 for v in values:
133 134 yield dict(v, **mapping)
134 135 return
135 136 startname = 'start_' + plural
136 137 if startname in templ:
137 138 yield templ(startname, **mapping)
138 139 vmapping = mapping.copy()
139 140 def one(v, tag=name):
140 141 try:
141 142 vmapping.update(v)
142 143 except (AttributeError, ValueError):
143 144 try:
144 145 for a, b in v:
145 146 vmapping[a] = b
146 147 except ValueError:
147 148 vmapping[name] = v
148 149 return templ(tag, **vmapping)
149 150 lastname = 'last_' + name
150 151 if lastname in templ:
151 152 last = values.pop()
152 153 else:
153 154 last = None
154 155 for v in values:
155 156 yield one(v)
156 157 if last is not None:
157 158 yield one(last, tag=lastname)
158 159 endname = 'end_' + plural
159 160 if endname in templ:
160 161 yield templ(endname, **mapping)
161 162
162 163 def _formatrevnode(ctx):
163 164 """Format changeset as '{rev}:{node|formatnode}', which is the default
164 165 template provided by cmdutil.changeset_templater"""
165 166 repo = ctx.repo()
166 167 if repo.ui.debugflag:
167 168 hexfunc = hex
168 169 else:
169 170 hexfunc = short
170 171 return '%d:%s' % (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
171 172
172 173 def getfiles(repo, ctx, revcache):
173 174 if 'files' not in revcache:
174 175 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
175 176 return revcache['files']
176 177
177 178 def getlatesttags(repo, ctx, cache, pattern=None):
178 179 '''return date, distance and name for the latest tag of rev'''
179 180
180 181 cachename = 'latesttags'
181 182 if pattern is not None:
182 183 cachename += '-' + pattern
183 184 match = util.stringmatcher(pattern)[2]
184 185 else:
185 186 match = util.always
186 187
187 188 if cachename not in cache:
188 189 # Cache mapping from rev to a tuple with tag date, tag
189 190 # distance and tag name
190 191 cache[cachename] = {-1: (0, 0, ['null'])}
191 192 latesttags = cache[cachename]
192 193
193 194 rev = ctx.rev()
194 195 todo = [rev]
195 196 while todo:
196 197 rev = todo.pop()
197 198 if rev in latesttags:
198 199 continue
199 200 ctx = repo[rev]
200 201 tags = [t for t in ctx.tags()
201 202 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
202 203 and match(t))]
203 204 if tags:
204 205 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
205 206 continue
206 207 try:
207 208 # The tuples are laid out so the right one can be found by
208 209 # comparison.
209 210 pdate, pdist, ptag = max(
210 211 latesttags[p.rev()] for p in ctx.parents())
211 212 except KeyError:
212 213 # Cache miss - recurse
213 214 todo.append(rev)
214 215 todo.extend(p.rev() for p in ctx.parents())
215 216 continue
216 217 latesttags[rev] = pdate, pdist + 1, ptag
217 218 return latesttags[rev]
218 219
219 220 def getrenamedfn(repo, endrev=None):
220 221 rcache = {}
221 222 if endrev is None:
222 223 endrev = len(repo)
223 224
224 225 def getrenamed(fn, rev):
225 226 '''looks up all renames for a file (up to endrev) the first
226 227 time the file is given. It indexes on the changerev and only
227 228 parses the manifest if linkrev != changerev.
228 229 Returns rename info for fn at changerev rev.'''
229 230 if fn not in rcache:
230 231 rcache[fn] = {}
231 232 fl = repo.file(fn)
232 233 for i in fl:
233 234 lr = fl.linkrev(i)
234 235 renamed = fl.renamed(fl.node(i))
235 236 rcache[fn][lr] = renamed
236 237 if lr >= endrev:
237 238 break
238 239 if rev in rcache[fn]:
239 240 return rcache[fn][rev]
240 241
241 242 # If linkrev != rev (i.e. rev not found in rcache) fallback to
242 243 # filectx logic.
243 244 try:
244 245 return repo[rev][fn].renamed()
245 246 except error.LookupError:
246 247 return None
247 248
248 249 return getrenamed
249 250
250 251 # default templates internally used for rendering of lists
251 252 defaulttempl = {
252 253 'parent': '{rev}:{node|formatnode} ',
253 254 'manifest': '{rev}:{node|formatnode}',
254 255 'file_copy': '{name} ({source})',
255 256 'envvar': '{key}={value}',
256 257 'extra': '{key}={value|stringescape}'
257 258 }
258 259 # filecopy is preserved for compatibility reasons
259 260 defaulttempl['filecopy'] = defaulttempl['file_copy']
260 261
261 262 # keywords are callables like:
262 263 # fn(repo, ctx, templ, cache, revcache, **args)
263 264 # with:
264 265 # repo - current repository instance
265 266 # ctx - the changectx being displayed
266 267 # templ - the templater instance
267 268 # cache - a cache dictionary for the whole templater run
268 269 # revcache - a cache dictionary for the current revision
269 270 keywords = {}
270 271
271 272 templatekeyword = registrar.templatekeyword(keywords)
272 273
273 274 @templatekeyword('author')
274 275 def showauthor(repo, ctx, templ, **args):
275 276 """String. The unmodified author of the changeset."""
276 277 return ctx.user()
277 278
278 279 @templatekeyword('bisect')
279 280 def showbisect(repo, ctx, templ, **args):
280 281 """String. The changeset bisection status."""
281 282 return hbisect.label(repo, ctx.node())
282 283
283 284 @templatekeyword('branch')
284 285 def showbranch(**args):
285 286 """String. The name of the branch on which the changeset was
286 287 committed.
287 288 """
288 289 return args['ctx'].branch()
289 290
290 291 @templatekeyword('branches')
291 292 def showbranches(**args):
292 293 """List of strings. The name of the branch on which the
293 294 changeset was committed. Will be empty if the branch name was
294 295 default. (DEPRECATED)
295 296 """
297 args = pycompat.byteskwargs(args)
296 298 branch = args['ctx'].branch()
297 299 if branch != 'default':
298 300 return showlist('branch', [branch], args, plural='branches')
299 301 return showlist('branch', [], args, plural='branches')
300 302
301 303 @templatekeyword('bookmarks')
302 304 def showbookmarks(**args):
303 305 """List of strings. Any bookmarks associated with the
304 306 changeset. Also sets 'active', the name of the active bookmark.
305 307 """
308 args = pycompat.byteskwargs(args)
306 309 repo = args['ctx']._repo
307 310 bookmarks = args['ctx'].bookmarks()
308 311 active = repo._activebookmark
309 312 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
310 313 f = _showlist('bookmark', bookmarks, args)
311 314 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
312 315
313 316 @templatekeyword('children')
314 317 def showchildren(**args):
315 318 """List of strings. The children of the changeset."""
319 args = pycompat.byteskwargs(args)
316 320 ctx = args['ctx']
317 321 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
318 322 return showlist('children', childrevs, args, element='child')
319 323
320 324 # Deprecated, but kept alive for help generation a purpose.
321 325 @templatekeyword('currentbookmark')
322 326 def showcurrentbookmark(**args):
323 327 """String. The active bookmark, if it is
324 328 associated with the changeset (DEPRECATED)"""
325 329 return showactivebookmark(**args)
326 330
327 331 @templatekeyword('activebookmark')
328 332 def showactivebookmark(**args):
329 333 """String. The active bookmark, if it is
330 334 associated with the changeset"""
331 335 active = args['repo']._activebookmark
332 336 if active and active in args['ctx'].bookmarks():
333 337 return active
334 338 return ''
335 339
336 340 @templatekeyword('date')
337 341 def showdate(repo, ctx, templ, **args):
338 342 """Date information. The date when the changeset was committed."""
339 343 return ctx.date()
340 344
341 345 @templatekeyword('desc')
342 346 def showdescription(repo, ctx, templ, **args):
343 347 """String. The text of the changeset description."""
344 348 s = ctx.description()
345 349 if isinstance(s, encoding.localstr):
346 350 # try hard to preserve utf-8 bytes
347 351 return encoding.tolocal(encoding.fromlocal(s).strip())
348 352 else:
349 353 return s.strip()
350 354
351 355 @templatekeyword('diffstat')
352 356 def showdiffstat(repo, ctx, templ, **args):
353 357 """String. Statistics of changes with the following format:
354 358 "modified files: +added/-removed lines"
355 359 """
356 360 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
357 361 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
358 362 return '%s: +%s/-%s' % (len(stats), adds, removes)
359 363
360 364 @templatekeyword('envvars')
361 365 def showenvvars(repo, **args):
362 366 """A dictionary of environment variables. (EXPERIMENTAL)"""
367 args = pycompat.byteskwargs(args)
363 368 env = repo.ui.exportableenviron()
364 369 env = util.sortdict((k, env[k]) for k in sorted(env))
365 370 return showdict('envvar', env, args, plural='envvars')
366 371
367 372 @templatekeyword('extras')
368 373 def showextras(**args):
369 374 """List of dicts with key, value entries of the 'extras'
370 375 field of this changeset."""
376 args = pycompat.byteskwargs(args)
371 377 extras = args['ctx'].extra()
372 378 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
373 379 makemap = lambda k: {'key': k, 'value': extras[k]}
374 380 c = [makemap(k) for k in extras]
375 381 f = _showlist('extra', c, args, plural='extras')
376 382 return _hybrid(f, extras, makemap,
377 383 lambda x: '%s=%s' % (x['key'], util.escapestr(x['value'])))
378 384
379 385 @templatekeyword('file_adds')
380 386 def showfileadds(**args):
381 387 """List of strings. Files added by this changeset."""
388 args = pycompat.byteskwargs(args)
382 389 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
383 390 return showlist('file_add', getfiles(repo, ctx, revcache)[1], args,
384 391 element='file')
385 392
386 393 @templatekeyword('file_copies')
387 394 def showfilecopies(**args):
388 395 """List of strings. Files copied in this changeset with
389 396 their sources.
390 397 """
398 args = pycompat.byteskwargs(args)
391 399 cache, ctx = args['cache'], args['ctx']
392 400 copies = args['revcache'].get('copies')
393 401 if copies is None:
394 402 if 'getrenamed' not in cache:
395 403 cache['getrenamed'] = getrenamedfn(args['repo'])
396 404 copies = []
397 405 getrenamed = cache['getrenamed']
398 406 for fn in ctx.files():
399 407 rename = getrenamed(fn, ctx.rev())
400 408 if rename:
401 409 copies.append((fn, rename[0]))
402 410
403 411 copies = util.sortdict(copies)
404 412 return showdict('file_copy', copies, args, plural='file_copies',
405 413 key='name', value='source', fmt='%s (%s)')
406 414
407 415 # showfilecopiesswitch() displays file copies only if copy records are
408 416 # provided before calling the templater, usually with a --copies
409 417 # command line switch.
410 418 @templatekeyword('file_copies_switch')
411 419 def showfilecopiesswitch(**args):
412 420 """List of strings. Like "file_copies" but displayed
413 421 only if the --copied switch is set.
414 422 """
423 args = pycompat.byteskwargs(args)
415 424 copies = args['revcache'].get('copies') or []
416 425 copies = util.sortdict(copies)
417 426 return showdict('file_copy', copies, args, plural='file_copies',
418 427 key='name', value='source', fmt='%s (%s)')
419 428
420 429 @templatekeyword('file_dels')
421 430 def showfiledels(**args):
422 431 """List of strings. Files removed by this changeset."""
432 args = pycompat.byteskwargs(args)
423 433 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
424 434 return showlist('file_del', getfiles(repo, ctx, revcache)[2], args,
425 435 element='file')
426 436
427 437 @templatekeyword('file_mods')
428 438 def showfilemods(**args):
429 439 """List of strings. Files modified by this changeset."""
440 args = pycompat.byteskwargs(args)
430 441 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
431 442 return showlist('file_mod', getfiles(repo, ctx, revcache)[0], args,
432 443 element='file')
433 444
434 445 @templatekeyword('files')
435 446 def showfiles(**args):
436 447 """List of strings. All files modified, added, or removed by this
437 448 changeset.
438 449 """
450 args = pycompat.byteskwargs(args)
439 451 return showlist('file', args['ctx'].files(), args)
440 452
441 453 @templatekeyword('graphnode')
442 454 def showgraphnode(repo, ctx, **args):
443 455 """String. The character representing the changeset node in
444 456 an ASCII revision graph"""
445 457 wpnodes = repo.dirstate.parents()
446 458 if wpnodes[1] == nullid:
447 459 wpnodes = wpnodes[:1]
448 460 if ctx.node() in wpnodes:
449 461 return '@'
450 462 elif ctx.obsolete():
451 463 return 'x'
452 464 elif ctx.closesbranch():
453 465 return '_'
454 466 else:
455 467 return 'o'
456 468
457 469 @templatekeyword('index')
458 470 def showindex(**args):
459 471 """Integer. The current iteration of the loop. (0 indexed)"""
460 472 # just hosts documentation; should be overridden by template mapping
461 473 raise error.Abort(_("can't use index in this context"))
462 474
463 475 @templatekeyword('latesttag')
464 476 def showlatesttag(**args):
465 477 """List of strings. The global tags on the most recent globally
466 478 tagged ancestor of this changeset. If no such tags exist, the list
467 479 consists of the single string "null".
468 480 """
469 481 return showlatesttags(None, **args)
470 482
471 483 def showlatesttags(pattern, **args):
472 484 """helper method for the latesttag keyword and function"""
485 args = pycompat.byteskwargs(args)
473 486 repo, ctx = args['repo'], args['ctx']
474 487 cache = args['cache']
475 488 latesttags = getlatesttags(repo, ctx, cache, pattern)
476 489
477 490 # latesttag[0] is an implementation detail for sorting csets on different
478 491 # branches in a stable manner- it is the date the tagged cset was created,
479 492 # not the date the tag was created. Therefore it isn't made visible here.
480 493 makemap = lambda v: {
481 494 'changes': _showchangessincetag,
482 495 'distance': latesttags[1],
483 496 'latesttag': v, # BC with {latesttag % '{latesttag}'}
484 497 'tag': v
485 498 }
486 499
487 500 tags = latesttags[2]
488 501 f = _showlist('latesttag', tags, args, separator=':')
489 502 return _hybrid(f, tags, makemap, lambda x: x['latesttag'])
490 503
491 504 @templatekeyword('latesttagdistance')
492 505 def showlatesttagdistance(repo, ctx, templ, cache, **args):
493 506 """Integer. Longest path to the latest tag."""
494 507 return getlatesttags(repo, ctx, cache)[1]
495 508
496 509 @templatekeyword('changessincelatesttag')
497 510 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
498 511 """Integer. All ancestors not in the latest tag."""
499 512 latesttag = getlatesttags(repo, ctx, cache)[2][0]
500 513
501 514 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
502 515
503 516 def _showchangessincetag(repo, ctx, **args):
504 517 offset = 0
505 518 revs = [ctx.rev()]
506 519 tag = args['tag']
507 520
508 521 # The only() revset doesn't currently support wdir()
509 522 if ctx.rev() is None:
510 523 offset = 1
511 524 revs = [p.rev() for p in ctx.parents()]
512 525
513 526 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
514 527
515 528 @templatekeyword('manifest')
516 529 def showmanifest(**args):
517 530 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
518 531 mnode = ctx.manifestnode()
519 532 if mnode is None:
520 533 # just avoid crash, we might want to use the 'ff...' hash in future
521 534 return
522 535 args = args.copy()
523 536 args.update({'rev': repo.manifestlog._revlog.rev(mnode),
524 537 'node': hex(mnode)})
525 538 return templ('manifest', **args)
526 539
527 540 def shownames(namespace, **args):
528 541 """helper method to generate a template keyword for a namespace"""
542 args = pycompat.byteskwargs(args)
529 543 ctx = args['ctx']
530 544 repo = ctx.repo()
531 545 ns = repo.names[namespace]
532 546 names = ns.names(repo, ctx.node())
533 547 return showlist(ns.templatename, names, args, plural=namespace)
534 548
535 549 @templatekeyword('namespaces')
536 550 def shownamespaces(**args):
537 551 """Dict of lists. Names attached to this changeset per
538 552 namespace."""
553 args = pycompat.byteskwargs(args)
539 554 ctx = args['ctx']
540 555 repo = ctx.repo()
541 556 namespaces = util.sortdict((k, showlist('name', ns.names(repo, ctx.node()),
542 557 args))
543 558 for k, ns in repo.names.iteritems())
544 559 f = _showlist('namespace', list(namespaces), args)
545 560 return _hybrid(f, namespaces,
546 561 lambda k: {'namespace': k, 'names': namespaces[k]},
547 562 lambda x: x['namespace'])
548 563
549 564 @templatekeyword('node')
550 565 def shownode(repo, ctx, templ, **args):
551 566 """String. The changeset identification hash, as a 40 hexadecimal
552 567 digit string.
553 568 """
554 569 return ctx.hex()
555 570
556 571 @templatekeyword('obsolete')
557 572 def showobsolete(repo, ctx, templ, **args):
558 573 """String. Whether the changeset is obsolete.
559 574 """
560 575 if ctx.obsolete():
561 576 return 'obsolete'
562 577 return ''
563 578
564 579 @templatekeyword("predecessors")
565 580 def showpredecessors(repo, ctx, **args):
566 581 """Returns the list if the closest visible successors
567 582 """
568 583 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
569 584 predecessors = map(hex, predecessors)
570 585
571 586 return _hybrid(None, predecessors,
572 587 lambda x: {'ctx': repo[x], 'revcache': {}},
573 588 lambda d: _formatrevnode(d['ctx']))
574 589
575 590 @templatekeyword('p1rev')
576 591 def showp1rev(repo, ctx, templ, **args):
577 592 """Integer. The repository-local revision number of the changeset's
578 593 first parent, or -1 if the changeset has no parents."""
579 594 return ctx.p1().rev()
580 595
581 596 @templatekeyword('p2rev')
582 597 def showp2rev(repo, ctx, templ, **args):
583 598 """Integer. The repository-local revision number of the changeset's
584 599 second parent, or -1 if the changeset has no second parent."""
585 600 return ctx.p2().rev()
586 601
587 602 @templatekeyword('p1node')
588 603 def showp1node(repo, ctx, templ, **args):
589 604 """String. The identification hash of the changeset's first parent,
590 605 as a 40 digit hexadecimal string. If the changeset has no parents, all
591 606 digits are 0."""
592 607 return ctx.p1().hex()
593 608
594 609 @templatekeyword('p2node')
595 610 def showp2node(repo, ctx, templ, **args):
596 611 """String. The identification hash of the changeset's second
597 612 parent, as a 40 digit hexadecimal string. If the changeset has no second
598 613 parent, all digits are 0."""
599 614 return ctx.p2().hex()
600 615
601 616 @templatekeyword('parents')
602 617 def showparents(**args):
603 618 """List of strings. The parents of the changeset in "rev:node"
604 619 format. If the changeset has only one "natural" parent (the predecessor
605 620 revision) nothing is shown."""
621 args = pycompat.byteskwargs(args)
606 622 repo = args['repo']
607 623 ctx = args['ctx']
608 624 pctxs = scmutil.meaningfulparents(repo, ctx)
609 625 prevs = [str(p.rev()) for p in pctxs] # ifcontains() needs a list of str
610 626 parents = [[('rev', p.rev()),
611 627 ('node', p.hex()),
612 628 ('phase', p.phasestr())]
613 629 for p in pctxs]
614 630 f = _showlist('parent', parents, args)
615 631 return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
616 632 lambda d: _formatrevnode(d['ctx']))
617 633
618 634 @templatekeyword('phase')
619 635 def showphase(repo, ctx, templ, **args):
620 636 """String. The changeset phase name."""
621 637 return ctx.phasestr()
622 638
623 639 @templatekeyword('phaseidx')
624 640 def showphaseidx(repo, ctx, templ, **args):
625 641 """Integer. The changeset phase index."""
626 642 return ctx.phase()
627 643
628 644 @templatekeyword('rev')
629 645 def showrev(repo, ctx, templ, **args):
630 646 """Integer. The repository-local changeset revision number."""
631 647 return scmutil.intrev(ctx)
632 648
633 649 def showrevslist(name, revs, **args):
634 650 """helper to generate a list of revisions in which a mapped template will
635 651 be evaluated"""
652 args = pycompat.byteskwargs(args)
636 653 repo = args['ctx'].repo()
637 654 revs = [str(r) for r in revs] # ifcontains() needs a list of str
638 655 f = _showlist(name, revs, args)
639 656 return _hybrid(f, revs,
640 657 lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}},
641 658 lambda d: d[name])
642 659
643 660 @templatekeyword('subrepos')
644 661 def showsubrepos(**args):
645 662 """List of strings. Updated subrepositories in the changeset."""
663 args = pycompat.byteskwargs(args)
646 664 ctx = args['ctx']
647 665 substate = ctx.substate
648 666 if not substate:
649 667 return showlist('subrepo', [], args)
650 668 psubstate = ctx.parents()[0].substate or {}
651 669 subrepos = []
652 670 for sub in substate:
653 671 if sub not in psubstate or substate[sub] != psubstate[sub]:
654 672 subrepos.append(sub) # modified or newly added in ctx
655 673 for sub in psubstate:
656 674 if sub not in substate:
657 675 subrepos.append(sub) # removed in ctx
658 676 return showlist('subrepo', sorted(subrepos), args)
659 677
660 678 # don't remove "showtags" definition, even though namespaces will put
661 679 # a helper function for "tags" keyword into "keywords" map automatically,
662 680 # because online help text is built without namespaces initialization
663 681 @templatekeyword('tags')
664 682 def showtags(**args):
665 683 """List of strings. Any tags associated with the changeset."""
666 684 return shownames('tags', **args)
667 685
668 686 def loadkeyword(ui, extname, registrarobj):
669 687 """Load template keyword from specified registrarobj
670 688 """
671 689 for name, func in registrarobj._table.iteritems():
672 690 keywords[name] = func
673 691
674 692 @templatekeyword('termwidth')
675 693 def termwidth(repo, ctx, templ, **args):
676 694 """Integer. The width of the current terminal."""
677 695 return repo.ui.termwidth()
678 696
679 697 @templatekeyword('troubles')
680 698 def showtroubles(**args):
681 699 """List of strings. Evolution troubles affecting the changeset.
682 700
683 701 (EXPERIMENTAL)
684 702 """
703 args = pycompat.byteskwargs(args)
685 704 return showlist('trouble', args['ctx'].troubles(), args)
686 705
687 706 # tell hggettext to extract docstrings from these functions:
688 707 i18nfunctions = keywords.values()
General Comments 0
You need to be logged in to leave comments. Login now