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