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