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