##// END OF EJS Templates
templatekw: factor out the changessincetag calculation to a private method...
Matt Harbison -
r26483:e94f9304 default
parent child Browse files
Show More
@@ -1,517 +1,522 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 .node import hex
11 11 from . import (
12 12 error,
13 13 hbisect,
14 14 patch,
15 15 scmutil,
16 16 util,
17 17 )
18 18
19 19 # This helper class allows us to handle both:
20 20 # "{files}" (legacy command-line-specific list hack) and
21 21 # "{files % '{file}\n'}" (hgweb-style with inlining and function support)
22 22 # and to access raw values:
23 23 # "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
24 24 # "{get(extras, key)}"
25 25
26 26 class _hybrid(object):
27 27 def __init__(self, gen, values, makemap, joinfmt=None):
28 28 self.gen = gen
29 29 self.values = values
30 30 self._makemap = makemap
31 31 if joinfmt:
32 32 self.joinfmt = joinfmt
33 33 else:
34 34 self.joinfmt = lambda x: x.values()[0]
35 35 def __iter__(self):
36 36 return self.gen
37 37 def __call__(self):
38 38 makemap = self._makemap
39 39 for x in self.values:
40 40 yield makemap(x)
41 41 def __contains__(self, x):
42 42 return x in self.values
43 43 def __len__(self):
44 44 return len(self.values)
45 45 def __getattr__(self, name):
46 46 if name != 'get':
47 47 raise AttributeError(name)
48 48 return getattr(self.values, name)
49 49
50 50 def showlist(name, values, plural=None, element=None, separator=' ', **args):
51 51 if not element:
52 52 element = name
53 53 f = _showlist(name, values, plural, separator, **args)
54 54 return _hybrid(f, values, lambda x: {element: x})
55 55
56 56 def _showlist(name, values, plural=None, separator=' ', **args):
57 57 '''expand set of values.
58 58 name is name of key in template map.
59 59 values is list of strings or dicts.
60 60 plural is plural of name, if not simply name + 's'.
61 61 separator is used to join values as a string
62 62
63 63 expansion works like this, given name 'foo'.
64 64
65 65 if values is empty, expand 'no_foos'.
66 66
67 67 if 'foo' not in template map, return values as a string,
68 68 joined by 'separator'.
69 69
70 70 expand 'start_foos'.
71 71
72 72 for each value, expand 'foo'. if 'last_foo' in template
73 73 map, expand it instead of 'foo' for last key.
74 74
75 75 expand 'end_foos'.
76 76 '''
77 77 templ = args['templ']
78 78 if plural:
79 79 names = plural
80 80 else: names = name + 's'
81 81 if not values:
82 82 noname = 'no_' + names
83 83 if noname in templ:
84 84 yield templ(noname, **args)
85 85 return
86 86 if name not in templ:
87 87 if isinstance(values[0], str):
88 88 yield separator.join(values)
89 89 else:
90 90 for v in values:
91 91 yield dict(v, **args)
92 92 return
93 93 startname = 'start_' + names
94 94 if startname in templ:
95 95 yield templ(startname, **args)
96 96 vargs = args.copy()
97 97 def one(v, tag=name):
98 98 try:
99 99 vargs.update(v)
100 100 except (AttributeError, ValueError):
101 101 try:
102 102 for a, b in v:
103 103 vargs[a] = b
104 104 except ValueError:
105 105 vargs[name] = v
106 106 return templ(tag, **vargs)
107 107 lastname = 'last_' + name
108 108 if lastname in templ:
109 109 last = values.pop()
110 110 else:
111 111 last = None
112 112 for v in values:
113 113 yield one(v)
114 114 if last is not None:
115 115 yield one(last, tag=lastname)
116 116 endname = 'end_' + names
117 117 if endname in templ:
118 118 yield templ(endname, **args)
119 119
120 120 def getfiles(repo, ctx, revcache):
121 121 if 'files' not in revcache:
122 122 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
123 123 return revcache['files']
124 124
125 125 def getlatesttags(repo, ctx, cache, pattern=None):
126 126 '''return date, distance and name for the latest tag of rev'''
127 127
128 128 cachename = 'latesttags'
129 129 if pattern is not None:
130 130 cachename += '-' + pattern
131 131 match = util.stringmatcher(pattern)[2]
132 132 else:
133 133 match = util.always
134 134
135 135 if cachename not in cache:
136 136 # Cache mapping from rev to a tuple with tag date, tag
137 137 # distance and tag name
138 138 cache[cachename] = {-1: (0, 0, ['null'])}
139 139 latesttags = cache[cachename]
140 140
141 141 rev = ctx.rev()
142 142 todo = [rev]
143 143 while todo:
144 144 rev = todo.pop()
145 145 if rev in latesttags:
146 146 continue
147 147 ctx = repo[rev]
148 148 tags = [t for t in ctx.tags()
149 149 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
150 150 and match(t))]
151 151 if tags:
152 152 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
153 153 continue
154 154 try:
155 155 # The tuples are laid out so the right one can be found by
156 156 # comparison.
157 157 pdate, pdist, ptag = max(
158 158 latesttags[p.rev()] for p in ctx.parents())
159 159 except KeyError:
160 160 # Cache miss - recurse
161 161 todo.append(rev)
162 162 todo.extend(p.rev() for p in ctx.parents())
163 163 continue
164 164 latesttags[rev] = pdate, pdist + 1, ptag
165 165 return latesttags[rev]
166 166
167 167 def getrenamedfn(repo, endrev=None):
168 168 rcache = {}
169 169 if endrev is None:
170 170 endrev = len(repo)
171 171
172 172 def getrenamed(fn, rev):
173 173 '''looks up all renames for a file (up to endrev) the first
174 174 time the file is given. It indexes on the changerev and only
175 175 parses the manifest if linkrev != changerev.
176 176 Returns rename info for fn at changerev rev.'''
177 177 if fn not in rcache:
178 178 rcache[fn] = {}
179 179 fl = repo.file(fn)
180 180 for i in fl:
181 181 lr = fl.linkrev(i)
182 182 renamed = fl.renamed(fl.node(i))
183 183 rcache[fn][lr] = renamed
184 184 if lr >= endrev:
185 185 break
186 186 if rev in rcache[fn]:
187 187 return rcache[fn][rev]
188 188
189 189 # If linkrev != rev (i.e. rev not found in rcache) fallback to
190 190 # filectx logic.
191 191 try:
192 192 return repo[rev][fn].renamed()
193 193 except error.LookupError:
194 194 return None
195 195
196 196 return getrenamed
197 197
198 198
199 199 def showauthor(repo, ctx, templ, **args):
200 200 """:author: String. The unmodified author of the changeset."""
201 201 return ctx.user()
202 202
203 203 def showbisect(repo, ctx, templ, **args):
204 204 """:bisect: String. The changeset bisection status."""
205 205 return hbisect.label(repo, ctx.node())
206 206
207 207 def showbranch(**args):
208 208 """:branch: String. The name of the branch on which the changeset was
209 209 committed.
210 210 """
211 211 return args['ctx'].branch()
212 212
213 213 def showbranches(**args):
214 214 """:branches: List of strings. The name of the branch on which the
215 215 changeset was committed. Will be empty if the branch name was
216 216 default. (DEPRECATED)
217 217 """
218 218 branch = args['ctx'].branch()
219 219 if branch != 'default':
220 220 return showlist('branch', [branch], plural='branches', **args)
221 221 return showlist('branch', [], plural='branches', **args)
222 222
223 223 def showbookmarks(**args):
224 224 """:bookmarks: List of strings. Any bookmarks associated with the
225 225 changeset. Also sets 'active', the name of the active bookmark.
226 226 """
227 227 repo = args['ctx']._repo
228 228 bookmarks = args['ctx'].bookmarks()
229 229 active = repo._activebookmark
230 230 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
231 231 f = _showlist('bookmark', bookmarks, **args)
232 232 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
233 233
234 234 def showchildren(**args):
235 235 """:children: List of strings. The children of the changeset."""
236 236 ctx = args['ctx']
237 237 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
238 238 return showlist('children', childrevs, element='child', **args)
239 239
240 240 # Deprecated, but kept alive for help generation a purpose.
241 241 def showcurrentbookmark(**args):
242 242 """:currentbookmark: String. The active bookmark, if it is
243 243 associated with the changeset (DEPRECATED)"""
244 244 return showactivebookmark(**args)
245 245
246 246 def showactivebookmark(**args):
247 247 """:activebookmark: String. The active bookmark, if it is
248 248 associated with the changeset"""
249 249 active = args['repo']._activebookmark
250 250 if active and active in args['ctx'].bookmarks():
251 251 return active
252 252 return ''
253 253
254 254 def showdate(repo, ctx, templ, **args):
255 255 """:date: Date information. The date when the changeset was committed."""
256 256 return ctx.date()
257 257
258 258 def showdescription(repo, ctx, templ, **args):
259 259 """:desc: String. The text of the changeset description."""
260 260 return ctx.description().strip()
261 261
262 262 def showdiffstat(repo, ctx, templ, **args):
263 263 """:diffstat: String. Statistics of changes with the following format:
264 264 "modified files: +added/-removed lines"
265 265 """
266 266 stats = patch.diffstatdata(util.iterlines(ctx.diff()))
267 267 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
268 268 return '%s: +%s/-%s' % (len(stats), adds, removes)
269 269
270 270 def showextras(**args):
271 271 """:extras: List of dicts with key, value entries of the 'extras'
272 272 field of this changeset."""
273 273 extras = args['ctx'].extra()
274 274 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
275 275 makemap = lambda k: {'key': k, 'value': extras[k]}
276 276 c = [makemap(k) for k in extras]
277 277 f = _showlist('extra', c, plural='extras', **args)
278 278 return _hybrid(f, extras, makemap,
279 279 lambda x: '%s=%s' % (x['key'], x['value']))
280 280
281 281 def showfileadds(**args):
282 282 """:file_adds: List of strings. Files added by this changeset."""
283 283 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
284 284 return showlist('file_add', getfiles(repo, ctx, revcache)[1],
285 285 element='file', **args)
286 286
287 287 def showfilecopies(**args):
288 288 """:file_copies: List of strings. Files copied in this changeset with
289 289 their sources.
290 290 """
291 291 cache, ctx = args['cache'], args['ctx']
292 292 copies = args['revcache'].get('copies')
293 293 if copies is None:
294 294 if 'getrenamed' not in cache:
295 295 cache['getrenamed'] = getrenamedfn(args['repo'])
296 296 copies = []
297 297 getrenamed = cache['getrenamed']
298 298 for fn in ctx.files():
299 299 rename = getrenamed(fn, ctx.rev())
300 300 if rename:
301 301 copies.append((fn, rename[0]))
302 302
303 303 copies = util.sortdict(copies)
304 304 makemap = lambda k: {'name': k, 'source': copies[k]}
305 305 c = [makemap(k) for k in copies]
306 306 f = _showlist('file_copy', c, plural='file_copies', **args)
307 307 return _hybrid(f, copies, makemap,
308 308 lambda x: '%s (%s)' % (x['name'], x['source']))
309 309
310 310 # showfilecopiesswitch() displays file copies only if copy records are
311 311 # provided before calling the templater, usually with a --copies
312 312 # command line switch.
313 313 def showfilecopiesswitch(**args):
314 314 """:file_copies_switch: List of strings. Like "file_copies" but displayed
315 315 only if the --copied switch is set.
316 316 """
317 317 copies = args['revcache'].get('copies') or []
318 318 copies = util.sortdict(copies)
319 319 makemap = lambda k: {'name': k, 'source': copies[k]}
320 320 c = [makemap(k) for k in copies]
321 321 f = _showlist('file_copy', c, plural='file_copies', **args)
322 322 return _hybrid(f, copies, makemap,
323 323 lambda x: '%s (%s)' % (x['name'], x['source']))
324 324
325 325 def showfiledels(**args):
326 326 """:file_dels: List of strings. Files removed by this changeset."""
327 327 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
328 328 return showlist('file_del', getfiles(repo, ctx, revcache)[2],
329 329 element='file', **args)
330 330
331 331 def showfilemods(**args):
332 332 """:file_mods: List of strings. Files modified by this changeset."""
333 333 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
334 334 return showlist('file_mod', getfiles(repo, ctx, revcache)[0],
335 335 element='file', **args)
336 336
337 337 def showfiles(**args):
338 338 """:files: List of strings. All files modified, added, or removed by this
339 339 changeset.
340 340 """
341 341 return showlist('file', args['ctx'].files(), **args)
342 342
343 343 def showlatesttag(**args):
344 344 """:latesttag: List of strings. The global tags on the most recent globally
345 345 tagged ancestor of this changeset.
346 346 """
347 347 repo, ctx = args['repo'], args['ctx']
348 348 cache = args['cache']
349 349 latesttags = getlatesttags(repo, ctx, cache)[2]
350 350
351 351 return showlist('latesttag', latesttags, separator=':', **args)
352 352
353 353 def showlatesttagdistance(repo, ctx, templ, cache, **args):
354 354 """:latesttagdistance: Integer. Longest path to the latest tag."""
355 355 return getlatesttags(repo, ctx, cache)[1]
356 356
357 357 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
358 358 """:changessincelatesttag: Integer. All ancestors not in the latest tag."""
359 359 latesttag = getlatesttags(repo, ctx, cache)[2][0]
360
361 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
362
363 def _showchangessincetag(repo, ctx, **args):
360 364 offset = 0
361 365 revs = [ctx.rev()]
366 tag = args['tag']
362 367
363 368 # The only() revset doesn't currently support wdir()
364 369 if ctx.rev() is None:
365 370 offset = 1
366 371 revs = [p.rev() for p in ctx.parents()]
367 372
368 return len(repo.revs('only(%ld, %s)', revs, latesttag)) + offset
373 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
369 374
370 375 def showmanifest(**args):
371 376 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
372 377 mnode = ctx.manifestnode()
373 378 if mnode is None:
374 379 # just avoid crash, we might want to use the 'ff...' hash in future
375 380 return
376 381 args = args.copy()
377 382 args.update({'rev': repo.manifest.rev(mnode), 'node': hex(mnode)})
378 383 return templ('manifest', **args)
379 384
380 385 def shownode(repo, ctx, templ, **args):
381 386 """:node: String. The changeset identification hash, as a 40 hexadecimal
382 387 digit string.
383 388 """
384 389 return ctx.hex()
385 390
386 391 def showp1rev(repo, ctx, templ, **args):
387 392 """:p1rev: Integer. The repository-local revision number of the changeset's
388 393 first parent, or -1 if the changeset has no parents."""
389 394 return ctx.p1().rev()
390 395
391 396 def showp2rev(repo, ctx, templ, **args):
392 397 """:p2rev: Integer. The repository-local revision number of the changeset's
393 398 second parent, or -1 if the changeset has no second parent."""
394 399 return ctx.p2().rev()
395 400
396 401 def showp1node(repo, ctx, templ, **args):
397 402 """:p1node: String. The identification hash of the changeset's first parent,
398 403 as a 40 digit hexadecimal string. If the changeset has no parents, all
399 404 digits are 0."""
400 405 return ctx.p1().hex()
401 406
402 407 def showp2node(repo, ctx, templ, **args):
403 408 """:p2node: String. The identification hash of the changeset's second
404 409 parent, as a 40 digit hexadecimal string. If the changeset has no second
405 410 parent, all digits are 0."""
406 411 return ctx.p2().hex()
407 412
408 413 def showparents(**args):
409 414 """:parents: List of strings. The parents of the changeset in "rev:node"
410 415 format. If the changeset has only one "natural" parent (the predecessor
411 416 revision) nothing is shown."""
412 417 repo = args['repo']
413 418 ctx = args['ctx']
414 419 parents = [[('rev', p.rev()),
415 420 ('node', p.hex()),
416 421 ('phase', p.phasestr())]
417 422 for p in scmutil.meaningfulparents(repo, ctx)]
418 423 return showlist('parent', parents, **args)
419 424
420 425 def showphase(repo, ctx, templ, **args):
421 426 """:phase: String. The changeset phase name."""
422 427 return ctx.phasestr()
423 428
424 429 def showphaseidx(repo, ctx, templ, **args):
425 430 """:phaseidx: Integer. The changeset phase index."""
426 431 return ctx.phase()
427 432
428 433 def showrev(repo, ctx, templ, **args):
429 434 """:rev: Integer. The repository-local changeset revision number."""
430 435 return scmutil.intrev(ctx.rev())
431 436
432 437 def showrevslist(name, revs, **args):
433 438 """helper to generate a list of revisions in which a mapped template will
434 439 be evaluated"""
435 440 repo = args['ctx'].repo()
436 441 f = _showlist(name, revs, **args)
437 442 return _hybrid(f, revs,
438 443 lambda x: {name: x, 'ctx': repo[x], 'revcache': {}})
439 444
440 445 def showsubrepos(**args):
441 446 """:subrepos: List of strings. Updated subrepositories in the changeset."""
442 447 ctx = args['ctx']
443 448 substate = ctx.substate
444 449 if not substate:
445 450 return showlist('subrepo', [], **args)
446 451 psubstate = ctx.parents()[0].substate or {}
447 452 subrepos = []
448 453 for sub in substate:
449 454 if sub not in psubstate or substate[sub] != psubstate[sub]:
450 455 subrepos.append(sub) # modified or newly added in ctx
451 456 for sub in psubstate:
452 457 if sub not in substate:
453 458 subrepos.append(sub) # removed in ctx
454 459 return showlist('subrepo', sorted(subrepos), **args)
455 460
456 461 def shownames(namespace, **args):
457 462 """helper method to generate a template keyword for a namespace"""
458 463 ctx = args['ctx']
459 464 repo = ctx.repo()
460 465 ns = repo.names[namespace]
461 466 names = ns.names(repo, ctx.node())
462 467 return showlist(ns.templatename, names, plural=namespace, **args)
463 468
464 469 # don't remove "showtags" definition, even though namespaces will put
465 470 # a helper function for "tags" keyword into "keywords" map automatically,
466 471 # because online help text is built without namespaces initialization
467 472 def showtags(**args):
468 473 """:tags: List of strings. Any tags associated with the changeset."""
469 474 return shownames('tags', **args)
470 475
471 476 # keywords are callables like:
472 477 # fn(repo, ctx, templ, cache, revcache, **args)
473 478 # with:
474 479 # repo - current repository instance
475 480 # ctx - the changectx being displayed
476 481 # templ - the templater instance
477 482 # cache - a cache dictionary for the whole templater run
478 483 # revcache - a cache dictionary for the current revision
479 484 keywords = {
480 485 'activebookmark': showactivebookmark,
481 486 'author': showauthor,
482 487 'bisect': showbisect,
483 488 'branch': showbranch,
484 489 'branches': showbranches,
485 490 'bookmarks': showbookmarks,
486 491 'changessincelatesttag': showchangessincelatesttag,
487 492 'children': showchildren,
488 493 # currentbookmark is deprecated
489 494 'currentbookmark': showcurrentbookmark,
490 495 'date': showdate,
491 496 'desc': showdescription,
492 497 'diffstat': showdiffstat,
493 498 'extras': showextras,
494 499 'file_adds': showfileadds,
495 500 'file_copies': showfilecopies,
496 501 'file_copies_switch': showfilecopiesswitch,
497 502 'file_dels': showfiledels,
498 503 'file_mods': showfilemods,
499 504 'files': showfiles,
500 505 'latesttag': showlatesttag,
501 506 'latesttagdistance': showlatesttagdistance,
502 507 'manifest': showmanifest,
503 508 'node': shownode,
504 509 'p1rev': showp1rev,
505 510 'p1node': showp1node,
506 511 'p2rev': showp2rev,
507 512 'p2node': showp2node,
508 513 'parents': showparents,
509 514 'phase': showphase,
510 515 'phaseidx': showphaseidx,
511 516 'rev': showrev,
512 517 'subrepos': showsubrepos,
513 518 'tags': showtags,
514 519 }
515 520
516 521 # tell hggettext to extract docstrings from these functions:
517 522 i18nfunctions = keywords.values()
General Comments 0
You need to be logged in to leave comments. Login now