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