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