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