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