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