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