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