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