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