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