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