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