##// END OF EJS Templates
templatekw: add {reporoot} keyword...
Yuya Nishihara -
r36261:48a6b1a2 default
parent child Browse files
Show More
@@ -1,929 +1,934 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 util,
27 27 )
28 28
29 29 class _hybrid(object):
30 30 """Wrapper for list or dict to support legacy template
31 31
32 32 This class allows us to handle both:
33 33 - "{files}" (legacy command-line-specific list hack) and
34 34 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
35 35 and to access raw values:
36 36 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
37 37 - "{get(extras, key)}"
38 38 - "{files|json}"
39 39 """
40 40
41 41 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
42 42 if gen is not None:
43 43 self.gen = gen # generator or function returning generator
44 44 self._values = values
45 45 self._makemap = makemap
46 46 self.joinfmt = joinfmt
47 47 self.keytype = keytype # hint for 'x in y' where type(x) is unresolved
48 48 def gen(self):
49 49 """Default generator to stringify this as {join(self, ' ')}"""
50 50 for i, x in enumerate(self._values):
51 51 if i > 0:
52 52 yield ' '
53 53 yield self.joinfmt(x)
54 54 def itermaps(self):
55 55 makemap = self._makemap
56 56 for x in self._values:
57 57 yield makemap(x)
58 58 def __contains__(self, x):
59 59 return x in self._values
60 60 def __getitem__(self, key):
61 61 return self._values[key]
62 62 def __len__(self):
63 63 return len(self._values)
64 64 def __iter__(self):
65 65 return iter(self._values)
66 66 def __getattr__(self, name):
67 67 if name not in ('get', 'items', 'iteritems', 'iterkeys', 'itervalues',
68 68 'keys', 'values'):
69 69 raise AttributeError(name)
70 70 return getattr(self._values, name)
71 71
72 72 class _mappable(object):
73 73 """Wrapper for non-list/dict object to support map operation
74 74
75 75 This class allows us to handle both:
76 76 - "{manifest}"
77 77 - "{manifest % '{rev}:{node}'}"
78 78 - "{manifest.rev}"
79 79
80 80 Unlike a _hybrid, this does not simulate the behavior of the underling
81 81 value. Use unwrapvalue() or unwraphybrid() to obtain the inner object.
82 82 """
83 83
84 84 def __init__(self, gen, key, value, makemap):
85 85 if gen is not None:
86 86 self.gen = gen # generator or function returning generator
87 87 self._key = key
88 88 self._value = value # may be generator of strings
89 89 self._makemap = makemap
90 90
91 91 def gen(self):
92 92 yield pycompat.bytestr(self._value)
93 93
94 94 def tomap(self):
95 95 return self._makemap(self._key)
96 96
97 97 def itermaps(self):
98 98 yield self.tomap()
99 99
100 100 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
101 101 """Wrap data to support both dict-like and string-like operations"""
102 102 return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
103 103 lambda k: fmt % (k, data[k]))
104 104
105 105 def hybridlist(data, name, fmt='%s', gen=None):
106 106 """Wrap data to support both list-like and string-like operations"""
107 107 return _hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % x)
108 108
109 109 def unwraphybrid(thing):
110 110 """Return an object which can be stringified possibly by using a legacy
111 111 template"""
112 112 gen = getattr(thing, 'gen', None)
113 113 if gen is None:
114 114 return thing
115 115 if callable(gen):
116 116 return gen()
117 117 return gen
118 118
119 119 def unwrapvalue(thing):
120 120 """Move the inner value object out of the wrapper"""
121 121 if not util.safehasattr(thing, '_value'):
122 122 return thing
123 123 return thing._value
124 124
125 125 def wraphybridvalue(container, key, value):
126 126 """Wrap an element of hybrid container to be mappable
127 127
128 128 The key is passed to the makemap function of the given container, which
129 129 should be an item generated by iter(container).
130 130 """
131 131 makemap = getattr(container, '_makemap', None)
132 132 if makemap is None:
133 133 return value
134 134 if util.safehasattr(value, '_makemap'):
135 135 # a nested hybrid list/dict, which has its own way of map operation
136 136 return value
137 137 return _mappable(None, key, value, makemap)
138 138
139 139 def showdict(name, data, mapping, plural=None, key='key', value='value',
140 140 fmt='%s=%s', separator=' '):
141 141 c = [{key: k, value: v} for k, v in data.iteritems()]
142 142 f = _showlist(name, c, mapping, plural, separator)
143 143 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
144 144
145 145 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
146 146 if not element:
147 147 element = name
148 148 f = _showlist(name, values, mapping, plural, separator)
149 149 return hybridlist(values, name=element, gen=f)
150 150
151 151 def _showlist(name, values, mapping, plural=None, separator=' '):
152 152 '''expand set of values.
153 153 name is name of key in template map.
154 154 values is list of strings or dicts.
155 155 plural is plural of name, if not simply name + 's'.
156 156 separator is used to join values as a string
157 157
158 158 expansion works like this, given name 'foo'.
159 159
160 160 if values is empty, expand 'no_foos'.
161 161
162 162 if 'foo' not in template map, return values as a string,
163 163 joined by 'separator'.
164 164
165 165 expand 'start_foos'.
166 166
167 167 for each value, expand 'foo'. if 'last_foo' in template
168 168 map, expand it instead of 'foo' for last key.
169 169
170 170 expand 'end_foos'.
171 171 '''
172 172 templ = mapping['templ']
173 173 strmapping = pycompat.strkwargs(mapping)
174 174 if not plural:
175 175 plural = name + 's'
176 176 if not values:
177 177 noname = 'no_' + plural
178 178 if noname in templ:
179 179 yield templ(noname, **strmapping)
180 180 return
181 181 if name not in templ:
182 182 if isinstance(values[0], bytes):
183 183 yield separator.join(values)
184 184 else:
185 185 for v in values:
186 186 yield dict(v, **strmapping)
187 187 return
188 188 startname = 'start_' + plural
189 189 if startname in templ:
190 190 yield templ(startname, **strmapping)
191 191 vmapping = mapping.copy()
192 192 def one(v, tag=name):
193 193 try:
194 194 vmapping.update(v)
195 195 # Python 2 raises ValueError if the type of v is wrong. Python
196 196 # 3 raises TypeError.
197 197 except (AttributeError, TypeError, ValueError):
198 198 try:
199 199 # Python 2 raises ValueError trying to destructure an e.g.
200 200 # bytes. Python 3 raises TypeError.
201 201 for a, b in v:
202 202 vmapping[a] = b
203 203 except (TypeError, ValueError):
204 204 vmapping[name] = v
205 205 return templ(tag, **pycompat.strkwargs(vmapping))
206 206 lastname = 'last_' + name
207 207 if lastname in templ:
208 208 last = values.pop()
209 209 else:
210 210 last = None
211 211 for v in values:
212 212 yield one(v)
213 213 if last is not None:
214 214 yield one(last, tag=lastname)
215 215 endname = 'end_' + plural
216 216 if endname in templ:
217 217 yield templ(endname, **strmapping)
218 218
219 219 def getfiles(repo, ctx, revcache):
220 220 if 'files' not in revcache:
221 221 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
222 222 return revcache['files']
223 223
224 224 def getlatesttags(repo, ctx, cache, pattern=None):
225 225 '''return date, distance and name for the latest tag of rev'''
226 226
227 227 cachename = 'latesttags'
228 228 if pattern is not None:
229 229 cachename += '-' + pattern
230 230 match = util.stringmatcher(pattern)[2]
231 231 else:
232 232 match = util.always
233 233
234 234 if cachename not in cache:
235 235 # Cache mapping from rev to a tuple with tag date, tag
236 236 # distance and tag name
237 237 cache[cachename] = {-1: (0, 0, ['null'])}
238 238 latesttags = cache[cachename]
239 239
240 240 rev = ctx.rev()
241 241 todo = [rev]
242 242 while todo:
243 243 rev = todo.pop()
244 244 if rev in latesttags:
245 245 continue
246 246 ctx = repo[rev]
247 247 tags = [t for t in ctx.tags()
248 248 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
249 249 and match(t))]
250 250 if tags:
251 251 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
252 252 continue
253 253 try:
254 254 ptags = [latesttags[p.rev()] for p in ctx.parents()]
255 255 if len(ptags) > 1:
256 256 if ptags[0][2] == ptags[1][2]:
257 257 # The tuples are laid out so the right one can be found by
258 258 # comparison in this case.
259 259 pdate, pdist, ptag = max(ptags)
260 260 else:
261 261 def key(x):
262 262 changessincetag = len(repo.revs('only(%d, %s)',
263 263 ctx.rev(), x[2][0]))
264 264 # Smallest number of changes since tag wins. Date is
265 265 # used as tiebreaker.
266 266 return [-changessincetag, x[0]]
267 267 pdate, pdist, ptag = max(ptags, key=key)
268 268 else:
269 269 pdate, pdist, ptag = ptags[0]
270 270 except KeyError:
271 271 # Cache miss - recurse
272 272 todo.append(rev)
273 273 todo.extend(p.rev() for p in ctx.parents())
274 274 continue
275 275 latesttags[rev] = pdate, pdist + 1, ptag
276 276 return latesttags[rev]
277 277
278 278 def getrenamedfn(repo, endrev=None):
279 279 rcache = {}
280 280 if endrev is None:
281 281 endrev = len(repo)
282 282
283 283 def getrenamed(fn, rev):
284 284 '''looks up all renames for a file (up to endrev) the first
285 285 time the file is given. It indexes on the changerev and only
286 286 parses the manifest if linkrev != changerev.
287 287 Returns rename info for fn at changerev rev.'''
288 288 if fn not in rcache:
289 289 rcache[fn] = {}
290 290 fl = repo.file(fn)
291 291 for i in fl:
292 292 lr = fl.linkrev(i)
293 293 renamed = fl.renamed(fl.node(i))
294 294 rcache[fn][lr] = renamed
295 295 if lr >= endrev:
296 296 break
297 297 if rev in rcache[fn]:
298 298 return rcache[fn][rev]
299 299
300 300 # If linkrev != rev (i.e. rev not found in rcache) fallback to
301 301 # filectx logic.
302 302 try:
303 303 return repo[rev][fn].renamed()
304 304 except error.LookupError:
305 305 return None
306 306
307 307 return getrenamed
308 308
309 309 def getlogcolumns():
310 310 """Return a dict of log column labels"""
311 311 _ = pycompat.identity # temporarily disable gettext
312 312 # i18n: column positioning for "hg log"
313 313 columns = _('bookmark: %s\n'
314 314 'branch: %s\n'
315 315 'changeset: %s\n'
316 316 'copies: %s\n'
317 317 'date: %s\n'
318 318 'extra: %s=%s\n'
319 319 'files+: %s\n'
320 320 'files-: %s\n'
321 321 'files: %s\n'
322 322 'instability: %s\n'
323 323 'manifest: %s\n'
324 324 'obsolete: %s\n'
325 325 'parent: %s\n'
326 326 'phase: %s\n'
327 327 'summary: %s\n'
328 328 'tag: %s\n'
329 329 'user: %s\n')
330 330 return dict(zip([s.split(':', 1)[0] for s in columns.splitlines()],
331 331 i18n._(columns).splitlines(True)))
332 332
333 333 # default templates internally used for rendering of lists
334 334 defaulttempl = {
335 335 'parent': '{rev}:{node|formatnode} ',
336 336 'manifest': '{rev}:{node|formatnode}',
337 337 'file_copy': '{name} ({source})',
338 338 'envvar': '{key}={value}',
339 339 'extra': '{key}={value|stringescape}'
340 340 }
341 341 # filecopy is preserved for compatibility reasons
342 342 defaulttempl['filecopy'] = defaulttempl['file_copy']
343 343
344 344 # keywords are callables like:
345 345 # fn(repo, ctx, templ, cache, revcache, **args)
346 346 # with:
347 347 # repo - current repository instance
348 348 # ctx - the changectx being displayed
349 349 # templ - the templater instance
350 350 # cache - a cache dictionary for the whole templater run
351 351 # revcache - a cache dictionary for the current revision
352 352 keywords = {}
353 353
354 354 templatekeyword = registrar.templatekeyword(keywords)
355 355
356 356 @templatekeyword('author')
357 357 def showauthor(repo, ctx, templ, **args):
358 358 """String. The unmodified author of the changeset."""
359 359 return ctx.user()
360 360
361 361 @templatekeyword('bisect')
362 362 def showbisect(repo, ctx, templ, **args):
363 363 """String. The changeset bisection status."""
364 364 return hbisect.label(repo, ctx.node())
365 365
366 366 @templatekeyword('branch')
367 367 def showbranch(**args):
368 368 """String. The name of the branch on which the changeset was
369 369 committed.
370 370 """
371 371 return args[r'ctx'].branch()
372 372
373 373 @templatekeyword('branches')
374 374 def showbranches(**args):
375 375 """List of strings. The name of the branch on which the
376 376 changeset was committed. Will be empty if the branch name was
377 377 default. (DEPRECATED)
378 378 """
379 379 args = pycompat.byteskwargs(args)
380 380 branch = args['ctx'].branch()
381 381 if branch != 'default':
382 382 return showlist('branch', [branch], args, plural='branches')
383 383 return showlist('branch', [], args, plural='branches')
384 384
385 385 @templatekeyword('bookmarks')
386 386 def showbookmarks(**args):
387 387 """List of strings. Any bookmarks associated with the
388 388 changeset. Also sets 'active', the name of the active bookmark.
389 389 """
390 390 args = pycompat.byteskwargs(args)
391 391 repo = args['ctx']._repo
392 392 bookmarks = args['ctx'].bookmarks()
393 393 active = repo._activebookmark
394 394 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
395 395 f = _showlist('bookmark', bookmarks, args)
396 396 return _hybrid(f, bookmarks, makemap, pycompat.identity)
397 397
398 398 @templatekeyword('children')
399 399 def showchildren(**args):
400 400 """List of strings. The children of the changeset."""
401 401 args = pycompat.byteskwargs(args)
402 402 ctx = args['ctx']
403 403 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
404 404 return showlist('children', childrevs, args, element='child')
405 405
406 406 # Deprecated, but kept alive for help generation a purpose.
407 407 @templatekeyword('currentbookmark')
408 408 def showcurrentbookmark(**args):
409 409 """String. The active bookmark, if it is associated with the changeset.
410 410 (DEPRECATED)"""
411 411 return showactivebookmark(**args)
412 412
413 413 @templatekeyword('activebookmark')
414 414 def showactivebookmark(**args):
415 415 """String. The active bookmark, if it is associated with the changeset."""
416 416 active = args[r'repo']._activebookmark
417 417 if active and active in args[r'ctx'].bookmarks():
418 418 return active
419 419 return ''
420 420
421 421 @templatekeyword('date')
422 422 def showdate(repo, ctx, templ, **args):
423 423 """Date information. The date when the changeset was committed."""
424 424 return ctx.date()
425 425
426 426 @templatekeyword('desc')
427 427 def showdescription(repo, ctx, templ, **args):
428 428 """String. The text of the changeset description."""
429 429 s = ctx.description()
430 430 if isinstance(s, encoding.localstr):
431 431 # try hard to preserve utf-8 bytes
432 432 return encoding.tolocal(encoding.fromlocal(s).strip())
433 433 else:
434 434 return s.strip()
435 435
436 436 @templatekeyword('diffstat')
437 437 def showdiffstat(repo, ctx, templ, **args):
438 438 """String. Statistics of changes with the following format:
439 439 "modified files: +added/-removed lines"
440 440 """
441 441 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
442 442 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
443 443 return '%s: +%s/-%s' % (len(stats), adds, removes)
444 444
445 445 @templatekeyword('envvars')
446 446 def showenvvars(repo, **args):
447 447 """A dictionary of environment variables. (EXPERIMENTAL)"""
448 448 args = pycompat.byteskwargs(args)
449 449 env = repo.ui.exportableenviron()
450 450 env = util.sortdict((k, env[k]) for k in sorted(env))
451 451 return showdict('envvar', env, args, plural='envvars')
452 452
453 453 @templatekeyword('extras')
454 454 def showextras(**args):
455 455 """List of dicts with key, value entries of the 'extras'
456 456 field of this changeset."""
457 457 args = pycompat.byteskwargs(args)
458 458 extras = args['ctx'].extra()
459 459 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
460 460 makemap = lambda k: {'key': k, 'value': extras[k]}
461 461 c = [makemap(k) for k in extras]
462 462 f = _showlist('extra', c, args, plural='extras')
463 463 return _hybrid(f, extras, makemap,
464 464 lambda k: '%s=%s' % (k, util.escapestr(extras[k])))
465 465
466 466 @templatekeyword('file_adds')
467 467 def showfileadds(**args):
468 468 """List of strings. Files added by this changeset."""
469 469 args = pycompat.byteskwargs(args)
470 470 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
471 471 return showlist('file_add', getfiles(repo, ctx, revcache)[1], args,
472 472 element='file')
473 473
474 474 @templatekeyword('file_copies')
475 475 def showfilecopies(**args):
476 476 """List of strings. Files copied in this changeset with
477 477 their sources.
478 478 """
479 479 args = pycompat.byteskwargs(args)
480 480 cache, ctx = args['cache'], args['ctx']
481 481 copies = args['revcache'].get('copies')
482 482 if copies is None:
483 483 if 'getrenamed' not in cache:
484 484 cache['getrenamed'] = getrenamedfn(args['repo'])
485 485 copies = []
486 486 getrenamed = cache['getrenamed']
487 487 for fn in ctx.files():
488 488 rename = getrenamed(fn, ctx.rev())
489 489 if rename:
490 490 copies.append((fn, rename[0]))
491 491
492 492 copies = util.sortdict(copies)
493 493 return showdict('file_copy', copies, args, plural='file_copies',
494 494 key='name', value='source', fmt='%s (%s)')
495 495
496 496 # showfilecopiesswitch() displays file copies only if copy records are
497 497 # provided before calling the templater, usually with a --copies
498 498 # command line switch.
499 499 @templatekeyword('file_copies_switch')
500 500 def showfilecopiesswitch(**args):
501 501 """List of strings. Like "file_copies" but displayed
502 502 only if the --copied switch is set.
503 503 """
504 504 args = pycompat.byteskwargs(args)
505 505 copies = args['revcache'].get('copies') or []
506 506 copies = util.sortdict(copies)
507 507 return showdict('file_copy', copies, args, plural='file_copies',
508 508 key='name', value='source', fmt='%s (%s)')
509 509
510 510 @templatekeyword('file_dels')
511 511 def showfiledels(**args):
512 512 """List of strings. Files removed by this changeset."""
513 513 args = pycompat.byteskwargs(args)
514 514 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
515 515 return showlist('file_del', getfiles(repo, ctx, revcache)[2], args,
516 516 element='file')
517 517
518 518 @templatekeyword('file_mods')
519 519 def showfilemods(**args):
520 520 """List of strings. Files modified by this changeset."""
521 521 args = pycompat.byteskwargs(args)
522 522 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
523 523 return showlist('file_mod', getfiles(repo, ctx, revcache)[0], args,
524 524 element='file')
525 525
526 526 @templatekeyword('files')
527 527 def showfiles(**args):
528 528 """List of strings. All files modified, added, or removed by this
529 529 changeset.
530 530 """
531 531 args = pycompat.byteskwargs(args)
532 532 return showlist('file', args['ctx'].files(), args)
533 533
534 534 @templatekeyword('graphnode')
535 535 def showgraphnode(repo, ctx, **args):
536 536 """String. The character representing the changeset node in an ASCII
537 537 revision graph."""
538 538 wpnodes = repo.dirstate.parents()
539 539 if wpnodes[1] == nullid:
540 540 wpnodes = wpnodes[:1]
541 541 if ctx.node() in wpnodes:
542 542 return '@'
543 543 elif ctx.obsolete():
544 544 return 'x'
545 545 elif ctx.isunstable():
546 546 return '*'
547 547 elif ctx.closesbranch():
548 548 return '_'
549 549 else:
550 550 return 'o'
551 551
552 552 @templatekeyword('graphwidth')
553 553 def showgraphwidth(repo, ctx, templ, **args):
554 554 """Integer. The width of the graph drawn by 'log --graph' or zero."""
555 555 # The value args['graphwidth'] will be this function, so we use an internal
556 556 # name to pass the value through props into this function.
557 557 return args.get('_graphwidth', 0)
558 558
559 559 @templatekeyword('index')
560 560 def showindex(**args):
561 561 """Integer. The current iteration of the loop. (0 indexed)"""
562 562 # just hosts documentation; should be overridden by template mapping
563 563 raise error.Abort(_("can't use index in this context"))
564 564
565 565 @templatekeyword('latesttag')
566 566 def showlatesttag(**args):
567 567 """List of strings. The global tags on the most recent globally
568 568 tagged ancestor of this changeset. If no such tags exist, the list
569 569 consists of the single string "null".
570 570 """
571 571 return showlatesttags(None, **args)
572 572
573 573 def showlatesttags(pattern, **args):
574 574 """helper method for the latesttag keyword and function"""
575 575 args = pycompat.byteskwargs(args)
576 576 repo, ctx = args['repo'], args['ctx']
577 577 cache = args['cache']
578 578 latesttags = getlatesttags(repo, ctx, cache, pattern)
579 579
580 580 # latesttag[0] is an implementation detail for sorting csets on different
581 581 # branches in a stable manner- it is the date the tagged cset was created,
582 582 # not the date the tag was created. Therefore it isn't made visible here.
583 583 makemap = lambda v: {
584 584 'changes': _showchangessincetag,
585 585 'distance': latesttags[1],
586 586 'latesttag': v, # BC with {latesttag % '{latesttag}'}
587 587 'tag': v
588 588 }
589 589
590 590 tags = latesttags[2]
591 591 f = _showlist('latesttag', tags, args, separator=':')
592 592 return _hybrid(f, tags, makemap, pycompat.identity)
593 593
594 594 @templatekeyword('latesttagdistance')
595 595 def showlatesttagdistance(repo, ctx, templ, cache, **args):
596 596 """Integer. Longest path to the latest tag."""
597 597 return getlatesttags(repo, ctx, cache)[1]
598 598
599 599 @templatekeyword('changessincelatesttag')
600 600 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
601 601 """Integer. All ancestors not in the latest tag."""
602 602 latesttag = getlatesttags(repo, ctx, cache)[2][0]
603 603
604 604 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
605 605
606 606 def _showchangessincetag(repo, ctx, **args):
607 607 offset = 0
608 608 revs = [ctx.rev()]
609 609 tag = args[r'tag']
610 610
611 611 # The only() revset doesn't currently support wdir()
612 612 if ctx.rev() is None:
613 613 offset = 1
614 614 revs = [p.rev() for p in ctx.parents()]
615 615
616 616 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
617 617
618 618 @templatekeyword('manifest')
619 619 def showmanifest(**args):
620 620 repo, ctx, templ = args[r'repo'], args[r'ctx'], args[r'templ']
621 621 mnode = ctx.manifestnode()
622 622 if mnode is None:
623 623 # just avoid crash, we might want to use the 'ff...' hash in future
624 624 return
625 625 mrev = repo.manifestlog._revlog.rev(mnode)
626 626 mhex = hex(mnode)
627 627 args = args.copy()
628 628 args.update({r'rev': mrev, r'node': mhex})
629 629 f = templ('manifest', **args)
630 630 # TODO: perhaps 'ctx' should be dropped from mapping because manifest
631 631 # rev and node are completely different from changeset's.
632 632 return _mappable(f, None, f, lambda x: {'rev': mrev, 'node': mhex})
633 633
634 634 @templatekeyword('obsfate')
635 635 def showobsfate(**args):
636 636 # this function returns a list containing pre-formatted obsfate strings.
637 637 #
638 638 # This function will be replaced by templates fragments when we will have
639 639 # the verbosity templatekw available.
640 640 succsandmarkers = showsuccsandmarkers(**args)
641 641
642 642 args = pycompat.byteskwargs(args)
643 643 ui = args['ui']
644 644
645 645 values = []
646 646
647 647 for x in succsandmarkers:
648 648 values.append(obsutil.obsfateprinter(x['successors'], x['markers'], ui))
649 649
650 650 return showlist("fate", values, args)
651 651
652 652 def shownames(namespace, **args):
653 653 """helper method to generate a template keyword for a namespace"""
654 654 args = pycompat.byteskwargs(args)
655 655 ctx = args['ctx']
656 656 repo = ctx.repo()
657 657 ns = repo.names[namespace]
658 658 names = ns.names(repo, ctx.node())
659 659 return showlist(ns.templatename, names, args, plural=namespace)
660 660
661 661 @templatekeyword('namespaces')
662 662 def shownamespaces(**args):
663 663 """Dict of lists. Names attached to this changeset per
664 664 namespace."""
665 665 args = pycompat.byteskwargs(args)
666 666 ctx = args['ctx']
667 667 repo = ctx.repo()
668 668
669 669 namespaces = util.sortdict()
670 670 def makensmapfn(ns):
671 671 # 'name' for iterating over namespaces, templatename for local reference
672 672 return lambda v: {'name': v, ns.templatename: v}
673 673
674 674 for k, ns in repo.names.iteritems():
675 675 names = ns.names(repo, ctx.node())
676 676 f = _showlist('name', names, args)
677 677 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
678 678
679 679 f = _showlist('namespace', list(namespaces), args)
680 680
681 681 def makemap(ns):
682 682 return {
683 683 'namespace': ns,
684 684 'names': namespaces[ns],
685 685 'builtin': repo.names[ns].builtin,
686 686 'colorname': repo.names[ns].colorname,
687 687 }
688 688
689 689 return _hybrid(f, namespaces, makemap, pycompat.identity)
690 690
691 691 @templatekeyword('node')
692 692 def shownode(repo, ctx, templ, **args):
693 693 """String. The changeset identification hash, as a 40 hexadecimal
694 694 digit string.
695 695 """
696 696 return ctx.hex()
697 697
698 698 @templatekeyword('obsolete')
699 699 def showobsolete(repo, ctx, templ, **args):
700 700 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
701 701 if ctx.obsolete():
702 702 return 'obsolete'
703 703 return ''
704 704
705 705 @templatekeyword('peerurls')
706 706 def showpeerurls(repo, **args):
707 707 """A dictionary of repository locations defined in the [paths] section
708 708 of your configuration file."""
709 709 # see commands.paths() for naming of dictionary keys
710 710 paths = repo.ui.paths
711 711 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
712 712 def makemap(k):
713 713 p = paths[k]
714 714 d = {'name': k, 'url': p.rawloc}
715 715 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
716 716 return d
717 717 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
718 718
719 719 @templatekeyword("predecessors")
720 720 def showpredecessors(repo, ctx, **args):
721 721 """Returns the list if the closest visible successors. (EXPERIMENTAL)"""
722 722 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
723 723 predecessors = map(hex, predecessors)
724 724
725 725 return _hybrid(None, predecessors,
726 726 lambda x: {'ctx': repo[x], 'revcache': {}},
727 727 lambda x: scmutil.formatchangeid(repo[x]))
728 728
729 @templatekeyword('reporoot')
730 def showreporoot(repo, **args):
731 """String. The root directory of the current repository."""
732 return repo.root
733
729 734 @templatekeyword("successorssets")
730 735 def showsuccessorssets(repo, ctx, **args):
731 736 """Returns a string of sets of successors for a changectx. Format used
732 737 is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
733 738 while also diverged into ctx3. (EXPERIMENTAL)"""
734 739 if not ctx.obsolete():
735 740 return ''
736 741 args = pycompat.byteskwargs(args)
737 742
738 743 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
739 744 ssets = [[hex(n) for n in ss] for ss in ssets]
740 745
741 746 data = []
742 747 for ss in ssets:
743 748 h = _hybrid(None, ss, lambda x: {'ctx': repo[x], 'revcache': {}},
744 749 lambda x: scmutil.formatchangeid(repo[x]))
745 750 data.append(h)
746 751
747 752 # Format the successorssets
748 753 def render(d):
749 754 t = []
750 755 for i in d.gen():
751 756 t.append(i)
752 757 return "".join(t)
753 758
754 759 def gen(data):
755 760 yield "; ".join(render(d) for d in data)
756 761
757 762 return _hybrid(gen(data), data, lambda x: {'successorset': x},
758 763 pycompat.identity)
759 764
760 765 @templatekeyword("succsandmarkers")
761 766 def showsuccsandmarkers(repo, ctx, **args):
762 767 """Returns a list of dict for each final successor of ctx. The dict
763 768 contains successors node id in "successors" keys and the list of
764 769 obs-markers from ctx to the set of successors in "markers".
765 770 (EXPERIMENTAL)
766 771 """
767 772
768 773 values = obsutil.successorsandmarkers(repo, ctx)
769 774
770 775 if values is None:
771 776 values = []
772 777
773 778 # Format successors and markers to avoid exposing binary to templates
774 779 data = []
775 780 for i in values:
776 781 # Format successors
777 782 successors = i['successors']
778 783
779 784 successors = [hex(n) for n in successors]
780 785 successors = _hybrid(None, successors,
781 786 lambda x: {'ctx': repo[x], 'revcache': {}},
782 787 lambda x: scmutil.formatchangeid(repo[x]))
783 788
784 789 # Format markers
785 790 finalmarkers = []
786 791 for m in i['markers']:
787 792 hexprec = hex(m[0])
788 793 hexsucs = tuple(hex(n) for n in m[1])
789 794 hexparents = None
790 795 if m[5] is not None:
791 796 hexparents = tuple(hex(n) for n in m[5])
792 797 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
793 798 finalmarkers.append(newmarker)
794 799
795 800 data.append({'successors': successors, 'markers': finalmarkers})
796 801
797 802 f = _showlist('succsandmarkers', data, args)
798 803 return _hybrid(f, data, lambda x: x, pycompat.identity)
799 804
800 805 @templatekeyword('p1rev')
801 806 def showp1rev(repo, ctx, templ, **args):
802 807 """Integer. The repository-local revision number of the changeset's
803 808 first parent, or -1 if the changeset has no parents."""
804 809 return ctx.p1().rev()
805 810
806 811 @templatekeyword('p2rev')
807 812 def showp2rev(repo, ctx, templ, **args):
808 813 """Integer. The repository-local revision number of the changeset's
809 814 second parent, or -1 if the changeset has no second parent."""
810 815 return ctx.p2().rev()
811 816
812 817 @templatekeyword('p1node')
813 818 def showp1node(repo, ctx, templ, **args):
814 819 """String. The identification hash of the changeset's first parent,
815 820 as a 40 digit hexadecimal string. If the changeset has no parents, all
816 821 digits are 0."""
817 822 return ctx.p1().hex()
818 823
819 824 @templatekeyword('p2node')
820 825 def showp2node(repo, ctx, templ, **args):
821 826 """String. The identification hash of the changeset's second
822 827 parent, as a 40 digit hexadecimal string. If the changeset has no second
823 828 parent, all digits are 0."""
824 829 return ctx.p2().hex()
825 830
826 831 @templatekeyword('parents')
827 832 def showparents(**args):
828 833 """List of strings. The parents of the changeset in "rev:node"
829 834 format. If the changeset has only one "natural" parent (the predecessor
830 835 revision) nothing is shown."""
831 836 args = pycompat.byteskwargs(args)
832 837 repo = args['repo']
833 838 ctx = args['ctx']
834 839 pctxs = scmutil.meaningfulparents(repo, ctx)
835 840 prevs = [p.rev() for p in pctxs]
836 841 parents = [[('rev', p.rev()),
837 842 ('node', p.hex()),
838 843 ('phase', p.phasestr())]
839 844 for p in pctxs]
840 845 f = _showlist('parent', parents, args)
841 846 return _hybrid(f, prevs, lambda x: {'ctx': repo[x], 'revcache': {}},
842 847 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
843 848
844 849 @templatekeyword('phase')
845 850 def showphase(repo, ctx, templ, **args):
846 851 """String. The changeset phase name."""
847 852 return ctx.phasestr()
848 853
849 854 @templatekeyword('phaseidx')
850 855 def showphaseidx(repo, ctx, templ, **args):
851 856 """Integer. The changeset phase index. (ADVANCED)"""
852 857 return ctx.phase()
853 858
854 859 @templatekeyword('rev')
855 860 def showrev(repo, ctx, templ, **args):
856 861 """Integer. The repository-local changeset revision number."""
857 862 return scmutil.intrev(ctx)
858 863
859 864 def showrevslist(name, revs, **args):
860 865 """helper to generate a list of revisions in which a mapped template will
861 866 be evaluated"""
862 867 args = pycompat.byteskwargs(args)
863 868 repo = args['ctx'].repo()
864 869 f = _showlist(name, ['%d' % r for r in revs], args)
865 870 return _hybrid(f, revs,
866 871 lambda x: {name: x, 'ctx': repo[x], 'revcache': {}},
867 872 pycompat.identity, keytype=int)
868 873
869 874 @templatekeyword('subrepos')
870 875 def showsubrepos(**args):
871 876 """List of strings. Updated subrepositories in the changeset."""
872 877 args = pycompat.byteskwargs(args)
873 878 ctx = args['ctx']
874 879 substate = ctx.substate
875 880 if not substate:
876 881 return showlist('subrepo', [], args)
877 882 psubstate = ctx.parents()[0].substate or {}
878 883 subrepos = []
879 884 for sub in substate:
880 885 if sub not in psubstate or substate[sub] != psubstate[sub]:
881 886 subrepos.append(sub) # modified or newly added in ctx
882 887 for sub in psubstate:
883 888 if sub not in substate:
884 889 subrepos.append(sub) # removed in ctx
885 890 return showlist('subrepo', sorted(subrepos), args)
886 891
887 892 # don't remove "showtags" definition, even though namespaces will put
888 893 # a helper function for "tags" keyword into "keywords" map automatically,
889 894 # because online help text is built without namespaces initialization
890 895 @templatekeyword('tags')
891 896 def showtags(**args):
892 897 """List of strings. Any tags associated with the changeset."""
893 898 return shownames('tags', **args)
894 899
895 900 @templatekeyword('termwidth')
896 901 def showtermwidth(repo, ctx, templ, **args):
897 902 """Integer. The width of the current terminal."""
898 903 return repo.ui.termwidth()
899 904
900 905 @templatekeyword('instabilities')
901 906 def showinstabilities(**args):
902 907 """List of strings. Evolution instabilities affecting the changeset.
903 908 (EXPERIMENTAL)
904 909 """
905 910 args = pycompat.byteskwargs(args)
906 911 return showlist('instability', args['ctx'].instabilities(), args,
907 912 plural='instabilities')
908 913
909 914 @templatekeyword('verbosity')
910 915 def showverbosity(ui, **args):
911 916 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
912 917 or ''."""
913 918 # see logcmdutil.changesettemplater for priority of these flags
914 919 if ui.debugflag:
915 920 return 'debug'
916 921 elif ui.quiet:
917 922 return 'quiet'
918 923 elif ui.verbose:
919 924 return 'verbose'
920 925 return ''
921 926
922 927 def loadkeyword(ui, extname, registrarobj):
923 928 """Load template keyword from specified registrarobj
924 929 """
925 930 for name, func in registrarobj._table.iteritems():
926 931 keywords[name] = func
927 932
928 933 # tell hggettext to extract docstrings from these functions:
929 934 i18nfunctions = keywords.values()
@@ -1,92 +1,99 b''
1 1 Create a repository:
2 2
3 3 $ hg config
4 4 devel.all-warnings=true
5 5 devel.default-date=0 0
6 6 extensions.fsmonitor= (fsmonitor !)
7 7 largefiles.usercache=$TESTTMP/.cache/largefiles
8 8 lfs.usercache=$TESTTMP/.cache/lfs
9 9 ui.slash=True
10 10 ui.interactive=False
11 11 ui.mergemarkers=detailed
12 12 ui.promptecho=True
13 13 web.address=localhost
14 14 web\.ipv6=(?:True|False) (re)
15 15 $ hg init t
16 16 $ cd t
17 17
18 18 Prepare a changeset:
19 19
20 20 $ echo a > a
21 21 $ hg add a
22 22
23 23 $ hg status
24 24 A a
25 25
26 26 Writes to stdio succeed and fail appropriately
27 27
28 28 #if devfull
29 29 $ hg status 2>/dev/full
30 30 A a
31 31
32 32 $ hg status >/dev/full
33 33 abort: No space left on device
34 34 [255]
35 35 #endif
36 36
37 37 #if devfull
38 38 $ hg status >/dev/full 2>&1
39 39 [255]
40 40
41 41 $ hg status ENOENT 2>/dev/full
42 42 [255]
43 43 #endif
44 44
45 45 $ hg commit -m test
46 46
47 47 This command is ancient:
48 48
49 49 $ hg history
50 50 changeset: 0:acb14030fe0a
51 51 tag: tip
52 52 user: test
53 53 date: Thu Jan 01 00:00:00 1970 +0000
54 54 summary: test
55 55
56 56
57 57 Verify that updating to revision 0 via commands.update() works properly
58 58
59 59 $ cat <<EOF > update_to_rev0.py
60 60 > from mercurial import ui, hg, commands
61 61 > myui = ui.ui.load()
62 62 > repo = hg.repository(myui, path='.')
63 63 > commands.update(myui, repo, rev=0)
64 64 > EOF
65 65 $ hg up null
66 66 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
67 67 $ $PYTHON ./update_to_rev0.py
68 68 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
69 69 $ hg identify -n
70 70 0
71 71
72 72
73 73 Poke around at hashes:
74 74
75 75 $ hg manifest --debug
76 76 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
77 77
78 78 $ hg cat a
79 79 a
80 80
81 81 Verify should succeed:
82 82
83 83 $ hg verify
84 84 checking changesets
85 85 checking manifests
86 86 crosschecking files in changesets and manifests
87 87 checking files
88 88 1 files, 1 changesets, 1 total revisions
89 89
90 Repository root:
91
92 $ hg root
93 $TESTTMP/t
94 $ hg log -l1 -T '{reporoot}\n'
95 $TESTTMP/t
96
90 97 At the end...
91 98
92 99 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now