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