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