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