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