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