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