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