##// END OF EJS Templates
help: fix formatting of template keywords...
Yuya Nishihara -
r34657:eb7fffdc default
parent child Browse files
Show More
@@ -1,886 +1,879
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 (
11 from .node import (
12 hex,
12 hex,
13 nullid,
13 nullid,
14 )
14 )
15
15
16 from . import (
16 from . import (
17 encoding,
17 encoding,
18 error,
18 error,
19 hbisect,
19 hbisect,
20 obsutil,
20 obsutil,
21 patch,
21 patch,
22 pycompat,
22 pycompat,
23 registrar,
23 registrar,
24 scmutil,
24 scmutil,
25 util,
25 util,
26 )
26 )
27
27
28 class _hybrid(object):
28 class _hybrid(object):
29 """Wrapper for list or dict to support legacy template
29 """Wrapper for list or dict to support legacy template
30
30
31 This class allows us to handle both:
31 This class allows us to handle both:
32 - "{files}" (legacy command-line-specific list hack) and
32 - "{files}" (legacy command-line-specific list hack) and
33 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
33 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
34 and to access raw values:
34 and to access raw values:
35 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
35 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
36 - "{get(extras, key)}"
36 - "{get(extras, key)}"
37 - "{files|json}"
37 - "{files|json}"
38 """
38 """
39
39
40 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
40 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
41 if gen is not None:
41 if gen is not None:
42 self.gen = gen # generator or function returning generator
42 self.gen = gen # generator or function returning generator
43 self._values = values
43 self._values = values
44 self._makemap = makemap
44 self._makemap = makemap
45 self.joinfmt = joinfmt
45 self.joinfmt = joinfmt
46 self.keytype = keytype # hint for 'x in y' where type(x) is unresolved
46 self.keytype = keytype # hint for 'x in y' where type(x) is unresolved
47 def gen(self):
47 def gen(self):
48 """Default generator to stringify this as {join(self, ' ')}"""
48 """Default generator to stringify this as {join(self, ' ')}"""
49 for i, x in enumerate(self._values):
49 for i, x in enumerate(self._values):
50 if i > 0:
50 if i > 0:
51 yield ' '
51 yield ' '
52 yield self.joinfmt(x)
52 yield self.joinfmt(x)
53 def itermaps(self):
53 def itermaps(self):
54 makemap = self._makemap
54 makemap = self._makemap
55 for x in self._values:
55 for x in self._values:
56 yield makemap(x)
56 yield makemap(x)
57 def __contains__(self, x):
57 def __contains__(self, x):
58 return x in self._values
58 return x in self._values
59 def __getitem__(self, key):
59 def __getitem__(self, key):
60 return self._values[key]
60 return self._values[key]
61 def __len__(self):
61 def __len__(self):
62 return len(self._values)
62 return len(self._values)
63 def __iter__(self):
63 def __iter__(self):
64 return iter(self._values)
64 return iter(self._values)
65 def __getattr__(self, name):
65 def __getattr__(self, name):
66 if name not in ('get', 'items', 'iteritems', 'iterkeys', 'itervalues',
66 if name not in ('get', 'items', 'iteritems', 'iterkeys', 'itervalues',
67 'keys', 'values'):
67 'keys', 'values'):
68 raise AttributeError(name)
68 raise AttributeError(name)
69 return getattr(self._values, name)
69 return getattr(self._values, name)
70
70
71 class _mappable(object):
71 class _mappable(object):
72 """Wrapper for non-list/dict object to support map operation
72 """Wrapper for non-list/dict object to support map operation
73
73
74 This class allows us to handle both:
74 This class allows us to handle both:
75 - "{manifest}"
75 - "{manifest}"
76 - "{manifest % '{rev}:{node}'}"
76 - "{manifest % '{rev}:{node}'}"
77 - "{manifest.rev}"
77 - "{manifest.rev}"
78
78
79 Unlike a _hybrid, this does not simulate the behavior of the underling
79 Unlike a _hybrid, this does not simulate the behavior of the underling
80 value. Use unwrapvalue() or unwraphybrid() to obtain the inner object.
80 value. Use unwrapvalue() or unwraphybrid() to obtain the inner object.
81 """
81 """
82
82
83 def __init__(self, gen, key, value, makemap):
83 def __init__(self, gen, key, value, makemap):
84 if gen is not None:
84 if gen is not None:
85 self.gen = gen # generator or function returning generator
85 self.gen = gen # generator or function returning generator
86 self._key = key
86 self._key = key
87 self._value = value # may be generator of strings
87 self._value = value # may be generator of strings
88 self._makemap = makemap
88 self._makemap = makemap
89
89
90 def gen(self):
90 def gen(self):
91 yield pycompat.bytestr(self._value)
91 yield pycompat.bytestr(self._value)
92
92
93 def tomap(self):
93 def tomap(self):
94 return self._makemap(self._key)
94 return self._makemap(self._key)
95
95
96 def itermaps(self):
96 def itermaps(self):
97 yield self.tomap()
97 yield self.tomap()
98
98
99 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
99 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
100 """Wrap data to support both dict-like and string-like operations"""
100 """Wrap data to support both dict-like and string-like operations"""
101 return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
101 return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
102 lambda k: fmt % (k, data[k]))
102 lambda k: fmt % (k, data[k]))
103
103
104 def hybridlist(data, name, fmt='%s', gen=None):
104 def hybridlist(data, name, fmt='%s', gen=None):
105 """Wrap data to support both list-like and string-like operations"""
105 """Wrap data to support both list-like and string-like operations"""
106 return _hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % x)
106 return _hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % x)
107
107
108 def unwraphybrid(thing):
108 def unwraphybrid(thing):
109 """Return an object which can be stringified possibly by using a legacy
109 """Return an object which can be stringified possibly by using a legacy
110 template"""
110 template"""
111 gen = getattr(thing, 'gen', None)
111 gen = getattr(thing, 'gen', None)
112 if gen is None:
112 if gen is None:
113 return thing
113 return thing
114 if callable(gen):
114 if callable(gen):
115 return gen()
115 return gen()
116 return gen
116 return gen
117
117
118 def unwrapvalue(thing):
118 def unwrapvalue(thing):
119 """Move the inner value object out of the wrapper"""
119 """Move the inner value object out of the wrapper"""
120 if not util.safehasattr(thing, '_value'):
120 if not util.safehasattr(thing, '_value'):
121 return thing
121 return thing
122 return thing._value
122 return thing._value
123
123
124 def wraphybridvalue(container, key, value):
124 def wraphybridvalue(container, key, value):
125 """Wrap an element of hybrid container to be mappable
125 """Wrap an element of hybrid container to be mappable
126
126
127 The key is passed to the makemap function of the given container, which
127 The key is passed to the makemap function of the given container, which
128 should be an item generated by iter(container).
128 should be an item generated by iter(container).
129 """
129 """
130 makemap = getattr(container, '_makemap', None)
130 makemap = getattr(container, '_makemap', None)
131 if makemap is None:
131 if makemap is None:
132 return value
132 return value
133 if util.safehasattr(value, '_makemap'):
133 if util.safehasattr(value, '_makemap'):
134 # a nested hybrid list/dict, which has its own way of map operation
134 # a nested hybrid list/dict, which has its own way of map operation
135 return value
135 return value
136 return _mappable(None, key, value, makemap)
136 return _mappable(None, key, value, makemap)
137
137
138 def showdict(name, data, mapping, plural=None, key='key', value='value',
138 def showdict(name, data, mapping, plural=None, key='key', value='value',
139 fmt='%s=%s', separator=' '):
139 fmt='%s=%s', separator=' '):
140 c = [{key: k, value: v} for k, v in data.iteritems()]
140 c = [{key: k, value: v} for k, v in data.iteritems()]
141 f = _showlist(name, c, mapping, plural, separator)
141 f = _showlist(name, c, mapping, plural, separator)
142 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
142 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
143
143
144 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
144 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
145 if not element:
145 if not element:
146 element = name
146 element = name
147 f = _showlist(name, values, mapping, plural, separator)
147 f = _showlist(name, values, mapping, plural, separator)
148 return hybridlist(values, name=element, gen=f)
148 return hybridlist(values, name=element, gen=f)
149
149
150 def _showlist(name, values, mapping, plural=None, separator=' '):
150 def _showlist(name, values, mapping, plural=None, separator=' '):
151 '''expand set of values.
151 '''expand set of values.
152 name is name of key in template map.
152 name is name of key in template map.
153 values is list of strings or dicts.
153 values is list of strings or dicts.
154 plural is plural of name, if not simply name + 's'.
154 plural is plural of name, if not simply name + 's'.
155 separator is used to join values as a string
155 separator is used to join values as a string
156
156
157 expansion works like this, given name 'foo'.
157 expansion works like this, given name 'foo'.
158
158
159 if values is empty, expand 'no_foos'.
159 if values is empty, expand 'no_foos'.
160
160
161 if 'foo' not in template map, return values as a string,
161 if 'foo' not in template map, return values as a string,
162 joined by 'separator'.
162 joined by 'separator'.
163
163
164 expand 'start_foos'.
164 expand 'start_foos'.
165
165
166 for each value, expand 'foo'. if 'last_foo' in template
166 for each value, expand 'foo'. if 'last_foo' in template
167 map, expand it instead of 'foo' for last key.
167 map, expand it instead of 'foo' for last key.
168
168
169 expand 'end_foos'.
169 expand 'end_foos'.
170 '''
170 '''
171 templ = mapping['templ']
171 templ = mapping['templ']
172 strmapping = pycompat.strkwargs(mapping)
172 strmapping = pycompat.strkwargs(mapping)
173 if not plural:
173 if not plural:
174 plural = name + 's'
174 plural = name + 's'
175 if not values:
175 if not values:
176 noname = 'no_' + plural
176 noname = 'no_' + plural
177 if noname in templ:
177 if noname in templ:
178 yield templ(noname, **strmapping)
178 yield templ(noname, **strmapping)
179 return
179 return
180 if name not in templ:
180 if name not in templ:
181 if isinstance(values[0], bytes):
181 if isinstance(values[0], bytes):
182 yield separator.join(values)
182 yield separator.join(values)
183 else:
183 else:
184 for v in values:
184 for v in values:
185 yield dict(v, **strmapping)
185 yield dict(v, **strmapping)
186 return
186 return
187 startname = 'start_' + plural
187 startname = 'start_' + plural
188 if startname in templ:
188 if startname in templ:
189 yield templ(startname, **strmapping)
189 yield templ(startname, **strmapping)
190 vmapping = mapping.copy()
190 vmapping = mapping.copy()
191 def one(v, tag=name):
191 def one(v, tag=name):
192 try:
192 try:
193 vmapping.update(v)
193 vmapping.update(v)
194 except (AttributeError, ValueError):
194 except (AttributeError, ValueError):
195 try:
195 try:
196 for a, b in v:
196 for a, b in v:
197 vmapping[a] = b
197 vmapping[a] = b
198 except ValueError:
198 except ValueError:
199 vmapping[name] = v
199 vmapping[name] = v
200 return templ(tag, **pycompat.strkwargs(vmapping))
200 return templ(tag, **pycompat.strkwargs(vmapping))
201 lastname = 'last_' + name
201 lastname = 'last_' + name
202 if lastname in templ:
202 if lastname in templ:
203 last = values.pop()
203 last = values.pop()
204 else:
204 else:
205 last = None
205 last = None
206 for v in values:
206 for v in values:
207 yield one(v)
207 yield one(v)
208 if last is not None:
208 if last is not None:
209 yield one(last, tag=lastname)
209 yield one(last, tag=lastname)
210 endname = 'end_' + plural
210 endname = 'end_' + plural
211 if endname in templ:
211 if endname in templ:
212 yield templ(endname, **strmapping)
212 yield templ(endname, **strmapping)
213
213
214 def getfiles(repo, ctx, revcache):
214 def getfiles(repo, ctx, revcache):
215 if 'files' not in revcache:
215 if 'files' not in revcache:
216 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
216 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
217 return revcache['files']
217 return revcache['files']
218
218
219 def getlatesttags(repo, ctx, cache, pattern=None):
219 def getlatesttags(repo, ctx, cache, pattern=None):
220 '''return date, distance and name for the latest tag of rev'''
220 '''return date, distance and name for the latest tag of rev'''
221
221
222 cachename = 'latesttags'
222 cachename = 'latesttags'
223 if pattern is not None:
223 if pattern is not None:
224 cachename += '-' + pattern
224 cachename += '-' + pattern
225 match = util.stringmatcher(pattern)[2]
225 match = util.stringmatcher(pattern)[2]
226 else:
226 else:
227 match = util.always
227 match = util.always
228
228
229 if cachename not in cache:
229 if cachename not in cache:
230 # Cache mapping from rev to a tuple with tag date, tag
230 # Cache mapping from rev to a tuple with tag date, tag
231 # distance and tag name
231 # distance and tag name
232 cache[cachename] = {-1: (0, 0, ['null'])}
232 cache[cachename] = {-1: (0, 0, ['null'])}
233 latesttags = cache[cachename]
233 latesttags = cache[cachename]
234
234
235 rev = ctx.rev()
235 rev = ctx.rev()
236 todo = [rev]
236 todo = [rev]
237 while todo:
237 while todo:
238 rev = todo.pop()
238 rev = todo.pop()
239 if rev in latesttags:
239 if rev in latesttags:
240 continue
240 continue
241 ctx = repo[rev]
241 ctx = repo[rev]
242 tags = [t for t in ctx.tags()
242 tags = [t for t in ctx.tags()
243 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
243 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
244 and match(t))]
244 and match(t))]
245 if tags:
245 if tags:
246 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
246 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
247 continue
247 continue
248 try:
248 try:
249 ptags = [latesttags[p.rev()] for p in ctx.parents()]
249 ptags = [latesttags[p.rev()] for p in ctx.parents()]
250 if len(ptags) > 1:
250 if len(ptags) > 1:
251 if ptags[0][2] == ptags[1][2]:
251 if ptags[0][2] == ptags[1][2]:
252 # The tuples are laid out so the right one can be found by
252 # The tuples are laid out so the right one can be found by
253 # comparison in this case.
253 # comparison in this case.
254 pdate, pdist, ptag = max(ptags)
254 pdate, pdist, ptag = max(ptags)
255 else:
255 else:
256 def key(x):
256 def key(x):
257 changessincetag = len(repo.revs('only(%d, %s)',
257 changessincetag = len(repo.revs('only(%d, %s)',
258 ctx.rev(), x[2][0]))
258 ctx.rev(), x[2][0]))
259 # Smallest number of changes since tag wins. Date is
259 # Smallest number of changes since tag wins. Date is
260 # used as tiebreaker.
260 # used as tiebreaker.
261 return [-changessincetag, x[0]]
261 return [-changessincetag, x[0]]
262 pdate, pdist, ptag = max(ptags, key=key)
262 pdate, pdist, ptag = max(ptags, key=key)
263 else:
263 else:
264 pdate, pdist, ptag = ptags[0]
264 pdate, pdist, ptag = ptags[0]
265 except KeyError:
265 except KeyError:
266 # Cache miss - recurse
266 # Cache miss - recurse
267 todo.append(rev)
267 todo.append(rev)
268 todo.extend(p.rev() for p in ctx.parents())
268 todo.extend(p.rev() for p in ctx.parents())
269 continue
269 continue
270 latesttags[rev] = pdate, pdist + 1, ptag
270 latesttags[rev] = pdate, pdist + 1, ptag
271 return latesttags[rev]
271 return latesttags[rev]
272
272
273 def getrenamedfn(repo, endrev=None):
273 def getrenamedfn(repo, endrev=None):
274 rcache = {}
274 rcache = {}
275 if endrev is None:
275 if endrev is None:
276 endrev = len(repo)
276 endrev = len(repo)
277
277
278 def getrenamed(fn, rev):
278 def getrenamed(fn, rev):
279 '''looks up all renames for a file (up to endrev) the first
279 '''looks up all renames for a file (up to endrev) the first
280 time the file is given. It indexes on the changerev and only
280 time the file is given. It indexes on the changerev and only
281 parses the manifest if linkrev != changerev.
281 parses the manifest if linkrev != changerev.
282 Returns rename info for fn at changerev rev.'''
282 Returns rename info for fn at changerev rev.'''
283 if fn not in rcache:
283 if fn not in rcache:
284 rcache[fn] = {}
284 rcache[fn] = {}
285 fl = repo.file(fn)
285 fl = repo.file(fn)
286 for i in fl:
286 for i in fl:
287 lr = fl.linkrev(i)
287 lr = fl.linkrev(i)
288 renamed = fl.renamed(fl.node(i))
288 renamed = fl.renamed(fl.node(i))
289 rcache[fn][lr] = renamed
289 rcache[fn][lr] = renamed
290 if lr >= endrev:
290 if lr >= endrev:
291 break
291 break
292 if rev in rcache[fn]:
292 if rev in rcache[fn]:
293 return rcache[fn][rev]
293 return rcache[fn][rev]
294
294
295 # If linkrev != rev (i.e. rev not found in rcache) fallback to
295 # If linkrev != rev (i.e. rev not found in rcache) fallback to
296 # filectx logic.
296 # filectx logic.
297 try:
297 try:
298 return repo[rev][fn].renamed()
298 return repo[rev][fn].renamed()
299 except error.LookupError:
299 except error.LookupError:
300 return None
300 return None
301
301
302 return getrenamed
302 return getrenamed
303
303
304 # default templates internally used for rendering of lists
304 # default templates internally used for rendering of lists
305 defaulttempl = {
305 defaulttempl = {
306 'parent': '{rev}:{node|formatnode} ',
306 'parent': '{rev}:{node|formatnode} ',
307 'manifest': '{rev}:{node|formatnode}',
307 'manifest': '{rev}:{node|formatnode}',
308 'file_copy': '{name} ({source})',
308 'file_copy': '{name} ({source})',
309 'envvar': '{key}={value}',
309 'envvar': '{key}={value}',
310 'extra': '{key}={value|stringescape}'
310 'extra': '{key}={value|stringescape}'
311 }
311 }
312 # filecopy is preserved for compatibility reasons
312 # filecopy is preserved for compatibility reasons
313 defaulttempl['filecopy'] = defaulttempl['file_copy']
313 defaulttempl['filecopy'] = defaulttempl['file_copy']
314
314
315 # keywords are callables like:
315 # keywords are callables like:
316 # fn(repo, ctx, templ, cache, revcache, **args)
316 # fn(repo, ctx, templ, cache, revcache, **args)
317 # with:
317 # with:
318 # repo - current repository instance
318 # repo - current repository instance
319 # ctx - the changectx being displayed
319 # ctx - the changectx being displayed
320 # templ - the templater instance
320 # templ - the templater instance
321 # cache - a cache dictionary for the whole templater run
321 # cache - a cache dictionary for the whole templater run
322 # revcache - a cache dictionary for the current revision
322 # revcache - a cache dictionary for the current revision
323 keywords = {}
323 keywords = {}
324
324
325 templatekeyword = registrar.templatekeyword(keywords)
325 templatekeyword = registrar.templatekeyword(keywords)
326
326
327 @templatekeyword('author')
327 @templatekeyword('author')
328 def showauthor(repo, ctx, templ, **args):
328 def showauthor(repo, ctx, templ, **args):
329 """String. The unmodified author of the changeset."""
329 """String. The unmodified author of the changeset."""
330 return ctx.user()
330 return ctx.user()
331
331
332 @templatekeyword('bisect')
332 @templatekeyword('bisect')
333 def showbisect(repo, ctx, templ, **args):
333 def showbisect(repo, ctx, templ, **args):
334 """String. The changeset bisection status."""
334 """String. The changeset bisection status."""
335 return hbisect.label(repo, ctx.node())
335 return hbisect.label(repo, ctx.node())
336
336
337 @templatekeyword('branch')
337 @templatekeyword('branch')
338 def showbranch(**args):
338 def showbranch(**args):
339 """String. The name of the branch on which the changeset was
339 """String. The name of the branch on which the changeset was
340 committed.
340 committed.
341 """
341 """
342 return args[r'ctx'].branch()
342 return args[r'ctx'].branch()
343
343
344 @templatekeyword('branches')
344 @templatekeyword('branches')
345 def showbranches(**args):
345 def showbranches(**args):
346 """List of strings. The name of the branch on which the
346 """List of strings. The name of the branch on which the
347 changeset was committed. Will be empty if the branch name was
347 changeset was committed. Will be empty if the branch name was
348 default. (DEPRECATED)
348 default. (DEPRECATED)
349 """
349 """
350 args = pycompat.byteskwargs(args)
350 args = pycompat.byteskwargs(args)
351 branch = args['ctx'].branch()
351 branch = args['ctx'].branch()
352 if branch != 'default':
352 if branch != 'default':
353 return showlist('branch', [branch], args, plural='branches')
353 return showlist('branch', [branch], args, plural='branches')
354 return showlist('branch', [], args, plural='branches')
354 return showlist('branch', [], args, plural='branches')
355
355
356 @templatekeyword('bookmarks')
356 @templatekeyword('bookmarks')
357 def showbookmarks(**args):
357 def showbookmarks(**args):
358 """List of strings. Any bookmarks associated with the
358 """List of strings. Any bookmarks associated with the
359 changeset. Also sets 'active', the name of the active bookmark.
359 changeset. Also sets 'active', the name of the active bookmark.
360 """
360 """
361 args = pycompat.byteskwargs(args)
361 args = pycompat.byteskwargs(args)
362 repo = args['ctx']._repo
362 repo = args['ctx']._repo
363 bookmarks = args['ctx'].bookmarks()
363 bookmarks = args['ctx'].bookmarks()
364 active = repo._activebookmark
364 active = repo._activebookmark
365 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
365 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
366 f = _showlist('bookmark', bookmarks, args)
366 f = _showlist('bookmark', bookmarks, args)
367 return _hybrid(f, bookmarks, makemap, pycompat.identity)
367 return _hybrid(f, bookmarks, makemap, pycompat.identity)
368
368
369 @templatekeyword('children')
369 @templatekeyword('children')
370 def showchildren(**args):
370 def showchildren(**args):
371 """List of strings. The children of the changeset."""
371 """List of strings. The children of the changeset."""
372 args = pycompat.byteskwargs(args)
372 args = pycompat.byteskwargs(args)
373 ctx = args['ctx']
373 ctx = args['ctx']
374 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
374 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
375 return showlist('children', childrevs, args, element='child')
375 return showlist('children', childrevs, args, element='child')
376
376
377 # Deprecated, but kept alive for help generation a purpose.
377 # Deprecated, but kept alive for help generation a purpose.
378 @templatekeyword('currentbookmark')
378 @templatekeyword('currentbookmark')
379 def showcurrentbookmark(**args):
379 def showcurrentbookmark(**args):
380 """String. The active bookmark, if it is
380 """String. The active bookmark, if it is associated with the changeset.
381 associated with the changeset (DEPRECATED)"""
381 (DEPRECATED)"""
382 return showactivebookmark(**args)
382 return showactivebookmark(**args)
383
383
384 @templatekeyword('activebookmark')
384 @templatekeyword('activebookmark')
385 def showactivebookmark(**args):
385 def showactivebookmark(**args):
386 """String. The active bookmark, if it is
386 """String. The active bookmark, if it is associated with the changeset."""
387 associated with the changeset"""
388 active = args[r'repo']._activebookmark
387 active = args[r'repo']._activebookmark
389 if active and active in args[r'ctx'].bookmarks():
388 if active and active in args[r'ctx'].bookmarks():
390 return active
389 return active
391 return ''
390 return ''
392
391
393 @templatekeyword('date')
392 @templatekeyword('date')
394 def showdate(repo, ctx, templ, **args):
393 def showdate(repo, ctx, templ, **args):
395 """Date information. The date when the changeset was committed."""
394 """Date information. The date when the changeset was committed."""
396 return ctx.date()
395 return ctx.date()
397
396
398 @templatekeyword('desc')
397 @templatekeyword('desc')
399 def showdescription(repo, ctx, templ, **args):
398 def showdescription(repo, ctx, templ, **args):
400 """String. The text of the changeset description."""
399 """String. The text of the changeset description."""
401 s = ctx.description()
400 s = ctx.description()
402 if isinstance(s, encoding.localstr):
401 if isinstance(s, encoding.localstr):
403 # try hard to preserve utf-8 bytes
402 # try hard to preserve utf-8 bytes
404 return encoding.tolocal(encoding.fromlocal(s).strip())
403 return encoding.tolocal(encoding.fromlocal(s).strip())
405 else:
404 else:
406 return s.strip()
405 return s.strip()
407
406
408 @templatekeyword('diffstat')
407 @templatekeyword('diffstat')
409 def showdiffstat(repo, ctx, templ, **args):
408 def showdiffstat(repo, ctx, templ, **args):
410 """String. Statistics of changes with the following format:
409 """String. Statistics of changes with the following format:
411 "modified files: +added/-removed lines"
410 "modified files: +added/-removed lines"
412 """
411 """
413 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
412 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
414 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
413 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
415 return '%s: +%s/-%s' % (len(stats), adds, removes)
414 return '%s: +%s/-%s' % (len(stats), adds, removes)
416
415
417 @templatekeyword('envvars')
416 @templatekeyword('envvars')
418 def showenvvars(repo, **args):
417 def showenvvars(repo, **args):
419 """A dictionary of environment variables. (EXPERIMENTAL)"""
418 """A dictionary of environment variables. (EXPERIMENTAL)"""
420 args = pycompat.byteskwargs(args)
419 args = pycompat.byteskwargs(args)
421 env = repo.ui.exportableenviron()
420 env = repo.ui.exportableenviron()
422 env = util.sortdict((k, env[k]) for k in sorted(env))
421 env = util.sortdict((k, env[k]) for k in sorted(env))
423 return showdict('envvar', env, args, plural='envvars')
422 return showdict('envvar', env, args, plural='envvars')
424
423
425 @templatekeyword('extras')
424 @templatekeyword('extras')
426 def showextras(**args):
425 def showextras(**args):
427 """List of dicts with key, value entries of the 'extras'
426 """List of dicts with key, value entries of the 'extras'
428 field of this changeset."""
427 field of this changeset."""
429 args = pycompat.byteskwargs(args)
428 args = pycompat.byteskwargs(args)
430 extras = args['ctx'].extra()
429 extras = args['ctx'].extra()
431 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
430 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
432 makemap = lambda k: {'key': k, 'value': extras[k]}
431 makemap = lambda k: {'key': k, 'value': extras[k]}
433 c = [makemap(k) for k in extras]
432 c = [makemap(k) for k in extras]
434 f = _showlist('extra', c, args, plural='extras')
433 f = _showlist('extra', c, args, plural='extras')
435 return _hybrid(f, extras, makemap,
434 return _hybrid(f, extras, makemap,
436 lambda k: '%s=%s' % (k, util.escapestr(extras[k])))
435 lambda k: '%s=%s' % (k, util.escapestr(extras[k])))
437
436
438 @templatekeyword('file_adds')
437 @templatekeyword('file_adds')
439 def showfileadds(**args):
438 def showfileadds(**args):
440 """List of strings. Files added by this changeset."""
439 """List of strings. Files added by this changeset."""
441 args = pycompat.byteskwargs(args)
440 args = pycompat.byteskwargs(args)
442 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
441 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
443 return showlist('file_add', getfiles(repo, ctx, revcache)[1], args,
442 return showlist('file_add', getfiles(repo, ctx, revcache)[1], args,
444 element='file')
443 element='file')
445
444
446 @templatekeyword('file_copies')
445 @templatekeyword('file_copies')
447 def showfilecopies(**args):
446 def showfilecopies(**args):
448 """List of strings. Files copied in this changeset with
447 """List of strings. Files copied in this changeset with
449 their sources.
448 their sources.
450 """
449 """
451 args = pycompat.byteskwargs(args)
450 args = pycompat.byteskwargs(args)
452 cache, ctx = args['cache'], args['ctx']
451 cache, ctx = args['cache'], args['ctx']
453 copies = args['revcache'].get('copies')
452 copies = args['revcache'].get('copies')
454 if copies is None:
453 if copies is None:
455 if 'getrenamed' not in cache:
454 if 'getrenamed' not in cache:
456 cache['getrenamed'] = getrenamedfn(args['repo'])
455 cache['getrenamed'] = getrenamedfn(args['repo'])
457 copies = []
456 copies = []
458 getrenamed = cache['getrenamed']
457 getrenamed = cache['getrenamed']
459 for fn in ctx.files():
458 for fn in ctx.files():
460 rename = getrenamed(fn, ctx.rev())
459 rename = getrenamed(fn, ctx.rev())
461 if rename:
460 if rename:
462 copies.append((fn, rename[0]))
461 copies.append((fn, rename[0]))
463
462
464 copies = util.sortdict(copies)
463 copies = util.sortdict(copies)
465 return showdict('file_copy', copies, args, plural='file_copies',
464 return showdict('file_copy', copies, args, plural='file_copies',
466 key='name', value='source', fmt='%s (%s)')
465 key='name', value='source', fmt='%s (%s)')
467
466
468 # showfilecopiesswitch() displays file copies only if copy records are
467 # showfilecopiesswitch() displays file copies only if copy records are
469 # provided before calling the templater, usually with a --copies
468 # provided before calling the templater, usually with a --copies
470 # command line switch.
469 # command line switch.
471 @templatekeyword('file_copies_switch')
470 @templatekeyword('file_copies_switch')
472 def showfilecopiesswitch(**args):
471 def showfilecopiesswitch(**args):
473 """List of strings. Like "file_copies" but displayed
472 """List of strings. Like "file_copies" but displayed
474 only if the --copied switch is set.
473 only if the --copied switch is set.
475 """
474 """
476 args = pycompat.byteskwargs(args)
475 args = pycompat.byteskwargs(args)
477 copies = args['revcache'].get('copies') or []
476 copies = args['revcache'].get('copies') or []
478 copies = util.sortdict(copies)
477 copies = util.sortdict(copies)
479 return showdict('file_copy', copies, args, plural='file_copies',
478 return showdict('file_copy', copies, args, plural='file_copies',
480 key='name', value='source', fmt='%s (%s)')
479 key='name', value='source', fmt='%s (%s)')
481
480
482 @templatekeyword('file_dels')
481 @templatekeyword('file_dels')
483 def showfiledels(**args):
482 def showfiledels(**args):
484 """List of strings. Files removed by this changeset."""
483 """List of strings. Files removed by this changeset."""
485 args = pycompat.byteskwargs(args)
484 args = pycompat.byteskwargs(args)
486 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
485 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
487 return showlist('file_del', getfiles(repo, ctx, revcache)[2], args,
486 return showlist('file_del', getfiles(repo, ctx, revcache)[2], args,
488 element='file')
487 element='file')
489
488
490 @templatekeyword('file_mods')
489 @templatekeyword('file_mods')
491 def showfilemods(**args):
490 def showfilemods(**args):
492 """List of strings. Files modified by this changeset."""
491 """List of strings. Files modified by this changeset."""
493 args = pycompat.byteskwargs(args)
492 args = pycompat.byteskwargs(args)
494 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
493 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
495 return showlist('file_mod', getfiles(repo, ctx, revcache)[0], args,
494 return showlist('file_mod', getfiles(repo, ctx, revcache)[0], args,
496 element='file')
495 element='file')
497
496
498 @templatekeyword('files')
497 @templatekeyword('files')
499 def showfiles(**args):
498 def showfiles(**args):
500 """List of strings. All files modified, added, or removed by this
499 """List of strings. All files modified, added, or removed by this
501 changeset.
500 changeset.
502 """
501 """
503 args = pycompat.byteskwargs(args)
502 args = pycompat.byteskwargs(args)
504 return showlist('file', args['ctx'].files(), args)
503 return showlist('file', args['ctx'].files(), args)
505
504
506 @templatekeyword('graphnode')
505 @templatekeyword('graphnode')
507 def showgraphnode(repo, ctx, **args):
506 def showgraphnode(repo, ctx, **args):
508 """String. The character representing the changeset node in
507 """String. The character representing the changeset node in an ASCII
509 an ASCII revision graph"""
508 revision graph."""
510 wpnodes = repo.dirstate.parents()
509 wpnodes = repo.dirstate.parents()
511 if wpnodes[1] == nullid:
510 if wpnodes[1] == nullid:
512 wpnodes = wpnodes[:1]
511 wpnodes = wpnodes[:1]
513 if ctx.node() in wpnodes:
512 if ctx.node() in wpnodes:
514 return '@'
513 return '@'
515 elif ctx.obsolete():
514 elif ctx.obsolete():
516 return 'x'
515 return 'x'
517 elif ctx.closesbranch():
516 elif ctx.closesbranch():
518 return '_'
517 return '_'
519 else:
518 else:
520 return 'o'
519 return 'o'
521
520
522 @templatekeyword('graphwidth')
521 @templatekeyword('graphwidth')
523 def showgraphwidth(repo, ctx, templ, **args):
522 def showgraphwidth(repo, ctx, templ, **args):
524 """Integer. The width of the graph drawn by 'log --graph' or zero."""
523 """Integer. The width of the graph drawn by 'log --graph' or zero."""
525 # The value args['graphwidth'] will be this function, so we use an internal
524 # The value args['graphwidth'] will be this function, so we use an internal
526 # name to pass the value through props into this function.
525 # name to pass the value through props into this function.
527 return args.get('_graphwidth', 0)
526 return args.get('_graphwidth', 0)
528
527
529 @templatekeyword('index')
528 @templatekeyword('index')
530 def showindex(**args):
529 def showindex(**args):
531 """Integer. The current iteration of the loop. (0 indexed)"""
530 """Integer. The current iteration of the loop. (0 indexed)"""
532 # just hosts documentation; should be overridden by template mapping
531 # just hosts documentation; should be overridden by template mapping
533 raise error.Abort(_("can't use index in this context"))
532 raise error.Abort(_("can't use index in this context"))
534
533
535 @templatekeyword('latesttag')
534 @templatekeyword('latesttag')
536 def showlatesttag(**args):
535 def showlatesttag(**args):
537 """List of strings. The global tags on the most recent globally
536 """List of strings. The global tags on the most recent globally
538 tagged ancestor of this changeset. If no such tags exist, the list
537 tagged ancestor of this changeset. If no such tags exist, the list
539 consists of the single string "null".
538 consists of the single string "null".
540 """
539 """
541 return showlatesttags(None, **args)
540 return showlatesttags(None, **args)
542
541
543 def showlatesttags(pattern, **args):
542 def showlatesttags(pattern, **args):
544 """helper method for the latesttag keyword and function"""
543 """helper method for the latesttag keyword and function"""
545 args = pycompat.byteskwargs(args)
544 args = pycompat.byteskwargs(args)
546 repo, ctx = args['repo'], args['ctx']
545 repo, ctx = args['repo'], args['ctx']
547 cache = args['cache']
546 cache = args['cache']
548 latesttags = getlatesttags(repo, ctx, cache, pattern)
547 latesttags = getlatesttags(repo, ctx, cache, pattern)
549
548
550 # latesttag[0] is an implementation detail for sorting csets on different
549 # latesttag[0] is an implementation detail for sorting csets on different
551 # branches in a stable manner- it is the date the tagged cset was created,
550 # branches in a stable manner- it is the date the tagged cset was created,
552 # not the date the tag was created. Therefore it isn't made visible here.
551 # not the date the tag was created. Therefore it isn't made visible here.
553 makemap = lambda v: {
552 makemap = lambda v: {
554 'changes': _showchangessincetag,
553 'changes': _showchangessincetag,
555 'distance': latesttags[1],
554 'distance': latesttags[1],
556 'latesttag': v, # BC with {latesttag % '{latesttag}'}
555 'latesttag': v, # BC with {latesttag % '{latesttag}'}
557 'tag': v
556 'tag': v
558 }
557 }
559
558
560 tags = latesttags[2]
559 tags = latesttags[2]
561 f = _showlist('latesttag', tags, args, separator=':')
560 f = _showlist('latesttag', tags, args, separator=':')
562 return _hybrid(f, tags, makemap, pycompat.identity)
561 return _hybrid(f, tags, makemap, pycompat.identity)
563
562
564 @templatekeyword('latesttagdistance')
563 @templatekeyword('latesttagdistance')
565 def showlatesttagdistance(repo, ctx, templ, cache, **args):
564 def showlatesttagdistance(repo, ctx, templ, cache, **args):
566 """Integer. Longest path to the latest tag."""
565 """Integer. Longest path to the latest tag."""
567 return getlatesttags(repo, ctx, cache)[1]
566 return getlatesttags(repo, ctx, cache)[1]
568
567
569 @templatekeyword('changessincelatesttag')
568 @templatekeyword('changessincelatesttag')
570 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
569 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
571 """Integer. All ancestors not in the latest tag."""
570 """Integer. All ancestors not in the latest tag."""
572 latesttag = getlatesttags(repo, ctx, cache)[2][0]
571 latesttag = getlatesttags(repo, ctx, cache)[2][0]
573
572
574 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
573 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
575
574
576 def _showchangessincetag(repo, ctx, **args):
575 def _showchangessincetag(repo, ctx, **args):
577 offset = 0
576 offset = 0
578 revs = [ctx.rev()]
577 revs = [ctx.rev()]
579 tag = args[r'tag']
578 tag = args[r'tag']
580
579
581 # The only() revset doesn't currently support wdir()
580 # The only() revset doesn't currently support wdir()
582 if ctx.rev() is None:
581 if ctx.rev() is None:
583 offset = 1
582 offset = 1
584 revs = [p.rev() for p in ctx.parents()]
583 revs = [p.rev() for p in ctx.parents()]
585
584
586 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
585 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
587
586
588 @templatekeyword('manifest')
587 @templatekeyword('manifest')
589 def showmanifest(**args):
588 def showmanifest(**args):
590 repo, ctx, templ = args[r'repo'], args[r'ctx'], args[r'templ']
589 repo, ctx, templ = args[r'repo'], args[r'ctx'], args[r'templ']
591 mnode = ctx.manifestnode()
590 mnode = ctx.manifestnode()
592 if mnode is None:
591 if mnode is None:
593 # just avoid crash, we might want to use the 'ff...' hash in future
592 # just avoid crash, we might want to use the 'ff...' hash in future
594 return
593 return
595 mrev = repo.manifestlog._revlog.rev(mnode)
594 mrev = repo.manifestlog._revlog.rev(mnode)
596 mhex = hex(mnode)
595 mhex = hex(mnode)
597 args = args.copy()
596 args = args.copy()
598 args.update({r'rev': mrev, r'node': mhex})
597 args.update({r'rev': mrev, r'node': mhex})
599 f = templ('manifest', **args)
598 f = templ('manifest', **args)
600 # TODO: perhaps 'ctx' should be dropped from mapping because manifest
599 # TODO: perhaps 'ctx' should be dropped from mapping because manifest
601 # rev and node are completely different from changeset's.
600 # rev and node are completely different from changeset's.
602 return _mappable(f, None, f, lambda x: {'rev': mrev, 'node': mhex})
601 return _mappable(f, None, f, lambda x: {'rev': mrev, 'node': mhex})
603
602
604 def shownames(namespace, **args):
603 def shownames(namespace, **args):
605 """helper method to generate a template keyword for a namespace"""
604 """helper method to generate a template keyword for a namespace"""
606 args = pycompat.byteskwargs(args)
605 args = pycompat.byteskwargs(args)
607 ctx = args['ctx']
606 ctx = args['ctx']
608 repo = ctx.repo()
607 repo = ctx.repo()
609 ns = repo.names[namespace]
608 ns = repo.names[namespace]
610 names = ns.names(repo, ctx.node())
609 names = ns.names(repo, ctx.node())
611 return showlist(ns.templatename, names, args, plural=namespace)
610 return showlist(ns.templatename, names, args, plural=namespace)
612
611
613 @templatekeyword('namespaces')
612 @templatekeyword('namespaces')
614 def shownamespaces(**args):
613 def shownamespaces(**args):
615 """Dict of lists. Names attached to this changeset per
614 """Dict of lists. Names attached to this changeset per
616 namespace."""
615 namespace."""
617 args = pycompat.byteskwargs(args)
616 args = pycompat.byteskwargs(args)
618 ctx = args['ctx']
617 ctx = args['ctx']
619 repo = ctx.repo()
618 repo = ctx.repo()
620
619
621 namespaces = util.sortdict()
620 namespaces = util.sortdict()
622 def makensmapfn(ns):
621 def makensmapfn(ns):
623 # 'name' for iterating over namespaces, templatename for local reference
622 # 'name' for iterating over namespaces, templatename for local reference
624 return lambda v: {'name': v, ns.templatename: v}
623 return lambda v: {'name': v, ns.templatename: v}
625
624
626 for k, ns in repo.names.iteritems():
625 for k, ns in repo.names.iteritems():
627 names = ns.names(repo, ctx.node())
626 names = ns.names(repo, ctx.node())
628 f = _showlist('name', names, args)
627 f = _showlist('name', names, args)
629 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
628 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
630
629
631 f = _showlist('namespace', list(namespaces), args)
630 f = _showlist('namespace', list(namespaces), args)
632
631
633 def makemap(ns):
632 def makemap(ns):
634 return {
633 return {
635 'namespace': ns,
634 'namespace': ns,
636 'names': namespaces[ns],
635 'names': namespaces[ns],
637 'builtin': repo.names[ns].builtin,
636 'builtin': repo.names[ns].builtin,
638 'colorname': repo.names[ns].colorname,
637 'colorname': repo.names[ns].colorname,
639 }
638 }
640
639
641 return _hybrid(f, namespaces, makemap, pycompat.identity)
640 return _hybrid(f, namespaces, makemap, pycompat.identity)
642
641
643 @templatekeyword('node')
642 @templatekeyword('node')
644 def shownode(repo, ctx, templ, **args):
643 def shownode(repo, ctx, templ, **args):
645 """String. The changeset identification hash, as a 40 hexadecimal
644 """String. The changeset identification hash, as a 40 hexadecimal
646 digit string.
645 digit string.
647 """
646 """
648 return ctx.hex()
647 return ctx.hex()
649
648
650 @templatekeyword('obsolete')
649 @templatekeyword('obsolete')
651 def showobsolete(repo, ctx, templ, **args):
650 def showobsolete(repo, ctx, templ, **args):
652 """String. Whether the changeset is obsolete.
651 """String. Whether the changeset is obsolete.
653 """
652 """
654 if ctx.obsolete():
653 if ctx.obsolete():
655 return 'obsolete'
654 return 'obsolete'
656 return ''
655 return ''
657
656
658 @templatekeyword('peerurls')
657 @templatekeyword('peerurls')
659 def showpeerurls(repo, **args):
658 def showpeerurls(repo, **args):
660 """A dictionary of repository locations defined in the [paths] section
659 """A dictionary of repository locations defined in the [paths] section
661 of your configuration file."""
660 of your configuration file."""
662 # see commands.paths() for naming of dictionary keys
661 # see commands.paths() for naming of dictionary keys
663 paths = repo.ui.paths
662 paths = repo.ui.paths
664 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
663 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
665 def makemap(k):
664 def makemap(k):
666 p = paths[k]
665 p = paths[k]
667 d = {'name': k, 'url': p.rawloc}
666 d = {'name': k, 'url': p.rawloc}
668 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
667 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
669 return d
668 return d
670 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
669 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
671
670
672 @templatekeyword("predecessors")
671 @templatekeyword("predecessors")
673 def showpredecessors(repo, ctx, **args):
672 def showpredecessors(repo, ctx, **args):
674 """Returns the list if the closest visible successors
673 """Returns the list if the closest visible successors."""
675 """
676 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
674 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
677 predecessors = map(hex, predecessors)
675 predecessors = map(hex, predecessors)
678
676
679 return _hybrid(None, predecessors,
677 return _hybrid(None, predecessors,
680 lambda x: {'ctx': repo[x], 'revcache': {}},
678 lambda x: {'ctx': repo[x], 'revcache': {}},
681 lambda x: scmutil.formatchangeid(repo[x]))
679 lambda x: scmutil.formatchangeid(repo[x]))
682
680
683 @templatekeyword("successorssets")
681 @templatekeyword("successorssets")
684 def showsuccessorssets(repo, ctx, **args):
682 def showsuccessorssets(repo, ctx, **args):
685 """Returns a string of sets of successors for a changectx
683 """Returns a string of sets of successors for a changectx. Format used
686
684 is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
687 Format used is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and
685 while also diverged into ctx3."""
688 ctx2 while also diverged into ctx3"""
689 if not ctx.obsolete():
686 if not ctx.obsolete():
690 return ''
687 return ''
691 args = pycompat.byteskwargs(args)
688 args = pycompat.byteskwargs(args)
692
689
693 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
690 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
694 ssets = [[hex(n) for n in ss] for ss in ssets]
691 ssets = [[hex(n) for n in ss] for ss in ssets]
695
692
696 data = []
693 data = []
697 for ss in ssets:
694 for ss in ssets:
698 h = _hybrid(None, ss, lambda x: {'ctx': repo[x], 'revcache': {}},
695 h = _hybrid(None, ss, lambda x: {'ctx': repo[x], 'revcache': {}},
699 lambda x: scmutil.formatchangeid(repo[x]))
696 lambda x: scmutil.formatchangeid(repo[x]))
700 data.append(h)
697 data.append(h)
701
698
702 # Format the successorssets
699 # Format the successorssets
703 def render(d):
700 def render(d):
704 t = []
701 t = []
705 for i in d.gen():
702 for i in d.gen():
706 t.append(i)
703 t.append(i)
707 return "".join(t)
704 return "".join(t)
708
705
709 def gen(data):
706 def gen(data):
710 yield "; ".join(render(d) for d in data)
707 yield "; ".join(render(d) for d in data)
711
708
712 return _hybrid(gen(data), data, lambda x: {'successorset': x},
709 return _hybrid(gen(data), data, lambda x: {'successorset': x},
713 pycompat.identity)
710 pycompat.identity)
714
711
715 @templatekeyword("succsandmarkers")
712 @templatekeyword("succsandmarkers")
716 def showsuccsandmarkers(repo, ctx, **args):
713 def showsuccsandmarkers(repo, ctx, **args):
717 """Returns a list of dict for each final successor of ctx.
714 """Returns a list of dict for each final successor of ctx. The dict
718
715 contains successors node id in "successors" keys and the list of
719 The dict contains successors node id in "successors" keys and the list of
716 obs-markers from ctx to the set of successors in "markers".
720 obs-markers from ctx to the set of successors in "markers"
721
722 (EXPERIMENTAL)
717 (EXPERIMENTAL)
723 """
718 """
724
719
725 values = obsutil.successorsandmarkers(repo, ctx)
720 values = obsutil.successorsandmarkers(repo, ctx)
726
721
727 if values is None:
722 if values is None:
728 values = []
723 values = []
729
724
730 # Format successors and markers to avoid exposing binary to templates
725 # Format successors and markers to avoid exposing binary to templates
731 data = []
726 data = []
732 for i in values:
727 for i in values:
733 # Format successors
728 # Format successors
734 successors = i['successors']
729 successors = i['successors']
735
730
736 successors = [hex(n) for n in successors]
731 successors = [hex(n) for n in successors]
737 successors = _hybrid(None, successors,
732 successors = _hybrid(None, successors,
738 lambda x: {'ctx': repo[x], 'revcache': {}},
733 lambda x: {'ctx': repo[x], 'revcache': {}},
739 lambda x: scmutil.formatchangeid(repo[x]))
734 lambda x: scmutil.formatchangeid(repo[x]))
740
735
741 # Format markers
736 # Format markers
742 finalmarkers = []
737 finalmarkers = []
743 for m in i['markers']:
738 for m in i['markers']:
744 hexprec = hex(m[0])
739 hexprec = hex(m[0])
745 hexsucs = tuple(hex(n) for n in m[1])
740 hexsucs = tuple(hex(n) for n in m[1])
746 hexparents = None
741 hexparents = None
747 if m[5] is not None:
742 if m[5] is not None:
748 hexparents = tuple(hex(n) for n in m[5])
743 hexparents = tuple(hex(n) for n in m[5])
749 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
744 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
750 finalmarkers.append(newmarker)
745 finalmarkers.append(newmarker)
751
746
752 data.append({'successors': successors, 'markers': finalmarkers})
747 data.append({'successors': successors, 'markers': finalmarkers})
753
748
754 f = _showlist('succsandmarkers', data, args)
749 f = _showlist('succsandmarkers', data, args)
755 return _hybrid(f, data, lambda x: x, pycompat.identity)
750 return _hybrid(f, data, lambda x: x, pycompat.identity)
756
751
757 @templatekeyword('p1rev')
752 @templatekeyword('p1rev')
758 def showp1rev(repo, ctx, templ, **args):
753 def showp1rev(repo, ctx, templ, **args):
759 """Integer. The repository-local revision number of the changeset's
754 """Integer. The repository-local revision number of the changeset's
760 first parent, or -1 if the changeset has no parents."""
755 first parent, or -1 if the changeset has no parents."""
761 return ctx.p1().rev()
756 return ctx.p1().rev()
762
757
763 @templatekeyword('p2rev')
758 @templatekeyword('p2rev')
764 def showp2rev(repo, ctx, templ, **args):
759 def showp2rev(repo, ctx, templ, **args):
765 """Integer. The repository-local revision number of the changeset's
760 """Integer. The repository-local revision number of the changeset's
766 second parent, or -1 if the changeset has no second parent."""
761 second parent, or -1 if the changeset has no second parent."""
767 return ctx.p2().rev()
762 return ctx.p2().rev()
768
763
769 @templatekeyword('p1node')
764 @templatekeyword('p1node')
770 def showp1node(repo, ctx, templ, **args):
765 def showp1node(repo, ctx, templ, **args):
771 """String. The identification hash of the changeset's first parent,
766 """String. The identification hash of the changeset's first parent,
772 as a 40 digit hexadecimal string. If the changeset has no parents, all
767 as a 40 digit hexadecimal string. If the changeset has no parents, all
773 digits are 0."""
768 digits are 0."""
774 return ctx.p1().hex()
769 return ctx.p1().hex()
775
770
776 @templatekeyword('p2node')
771 @templatekeyword('p2node')
777 def showp2node(repo, ctx, templ, **args):
772 def showp2node(repo, ctx, templ, **args):
778 """String. The identification hash of the changeset's second
773 """String. The identification hash of the changeset's second
779 parent, as a 40 digit hexadecimal string. If the changeset has no second
774 parent, as a 40 digit hexadecimal string. If the changeset has no second
780 parent, all digits are 0."""
775 parent, all digits are 0."""
781 return ctx.p2().hex()
776 return ctx.p2().hex()
782
777
783 @templatekeyword('parents')
778 @templatekeyword('parents')
784 def showparents(**args):
779 def showparents(**args):
785 """List of strings. The parents of the changeset in "rev:node"
780 """List of strings. The parents of the changeset in "rev:node"
786 format. If the changeset has only one "natural" parent (the predecessor
781 format. If the changeset has only one "natural" parent (the predecessor
787 revision) nothing is shown."""
782 revision) nothing is shown."""
788 args = pycompat.byteskwargs(args)
783 args = pycompat.byteskwargs(args)
789 repo = args['repo']
784 repo = args['repo']
790 ctx = args['ctx']
785 ctx = args['ctx']
791 pctxs = scmutil.meaningfulparents(repo, ctx)
786 pctxs = scmutil.meaningfulparents(repo, ctx)
792 prevs = [p.rev() for p in pctxs]
787 prevs = [p.rev() for p in pctxs]
793 parents = [[('rev', p.rev()),
788 parents = [[('rev', p.rev()),
794 ('node', p.hex()),
789 ('node', p.hex()),
795 ('phase', p.phasestr())]
790 ('phase', p.phasestr())]
796 for p in pctxs]
791 for p in pctxs]
797 f = _showlist('parent', parents, args)
792 f = _showlist('parent', parents, args)
798 return _hybrid(f, prevs, lambda x: {'ctx': repo[x], 'revcache': {}},
793 return _hybrid(f, prevs, lambda x: {'ctx': repo[x], 'revcache': {}},
799 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
794 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
800
795
801 @templatekeyword('phase')
796 @templatekeyword('phase')
802 def showphase(repo, ctx, templ, **args):
797 def showphase(repo, ctx, templ, **args):
803 """String. The changeset phase name."""
798 """String. The changeset phase name."""
804 return ctx.phasestr()
799 return ctx.phasestr()
805
800
806 @templatekeyword('phaseidx')
801 @templatekeyword('phaseidx')
807 def showphaseidx(repo, ctx, templ, **args):
802 def showphaseidx(repo, ctx, templ, **args):
808 """Integer. The changeset phase index."""
803 """Integer. The changeset phase index."""
809 return ctx.phase()
804 return ctx.phase()
810
805
811 @templatekeyword('rev')
806 @templatekeyword('rev')
812 def showrev(repo, ctx, templ, **args):
807 def showrev(repo, ctx, templ, **args):
813 """Integer. The repository-local changeset revision number."""
808 """Integer. The repository-local changeset revision number."""
814 return scmutil.intrev(ctx)
809 return scmutil.intrev(ctx)
815
810
816 def showrevslist(name, revs, **args):
811 def showrevslist(name, revs, **args):
817 """helper to generate a list of revisions in which a mapped template will
812 """helper to generate a list of revisions in which a mapped template will
818 be evaluated"""
813 be evaluated"""
819 args = pycompat.byteskwargs(args)
814 args = pycompat.byteskwargs(args)
820 repo = args['ctx'].repo()
815 repo = args['ctx'].repo()
821 f = _showlist(name, ['%d' % r for r in revs], args)
816 f = _showlist(name, ['%d' % r for r in revs], args)
822 return _hybrid(f, revs,
817 return _hybrid(f, revs,
823 lambda x: {name: x, 'ctx': repo[x], 'revcache': {}},
818 lambda x: {name: x, 'ctx': repo[x], 'revcache': {}},
824 pycompat.identity, keytype=int)
819 pycompat.identity, keytype=int)
825
820
826 @templatekeyword('subrepos')
821 @templatekeyword('subrepos')
827 def showsubrepos(**args):
822 def showsubrepos(**args):
828 """List of strings. Updated subrepositories in the changeset."""
823 """List of strings. Updated subrepositories in the changeset."""
829 args = pycompat.byteskwargs(args)
824 args = pycompat.byteskwargs(args)
830 ctx = args['ctx']
825 ctx = args['ctx']
831 substate = ctx.substate
826 substate = ctx.substate
832 if not substate:
827 if not substate:
833 return showlist('subrepo', [], args)
828 return showlist('subrepo', [], args)
834 psubstate = ctx.parents()[0].substate or {}
829 psubstate = ctx.parents()[0].substate or {}
835 subrepos = []
830 subrepos = []
836 for sub in substate:
831 for sub in substate:
837 if sub not in psubstate or substate[sub] != psubstate[sub]:
832 if sub not in psubstate or substate[sub] != psubstate[sub]:
838 subrepos.append(sub) # modified or newly added in ctx
833 subrepos.append(sub) # modified or newly added in ctx
839 for sub in psubstate:
834 for sub in psubstate:
840 if sub not in substate:
835 if sub not in substate:
841 subrepos.append(sub) # removed in ctx
836 subrepos.append(sub) # removed in ctx
842 return showlist('subrepo', sorted(subrepos), args)
837 return showlist('subrepo', sorted(subrepos), args)
843
838
844 # don't remove "showtags" definition, even though namespaces will put
839 # don't remove "showtags" definition, even though namespaces will put
845 # a helper function for "tags" keyword into "keywords" map automatically,
840 # a helper function for "tags" keyword into "keywords" map automatically,
846 # because online help text is built without namespaces initialization
841 # because online help text is built without namespaces initialization
847 @templatekeyword('tags')
842 @templatekeyword('tags')
848 def showtags(**args):
843 def showtags(**args):
849 """List of strings. Any tags associated with the changeset."""
844 """List of strings. Any tags associated with the changeset."""
850 return shownames('tags', **args)
845 return shownames('tags', **args)
851
846
852 def loadkeyword(ui, extname, registrarobj):
847 def loadkeyword(ui, extname, registrarobj):
853 """Load template keyword from specified registrarobj
848 """Load template keyword from specified registrarobj
854 """
849 """
855 for name, func in registrarobj._table.iteritems():
850 for name, func in registrarobj._table.iteritems():
856 keywords[name] = func
851 keywords[name] = func
857
852
858 @templatekeyword('termwidth')
853 @templatekeyword('termwidth')
859 def showtermwidth(repo, ctx, templ, **args):
854 def showtermwidth(repo, ctx, templ, **args):
860 """Integer. The width of the current terminal."""
855 """Integer. The width of the current terminal."""
861 return repo.ui.termwidth()
856 return repo.ui.termwidth()
862
857
863 @templatekeyword('troubles')
858 @templatekeyword('troubles')
864 def showtroubles(repo, **args):
859 def showtroubles(repo, **args):
865 """List of strings. Evolution troubles affecting the changeset.
860 """List of strings. Evolution troubles affecting the changeset.
866
867 (DEPRECATED)
861 (DEPRECATED)
868 """
862 """
869 msg = ("'troubles' is deprecated, "
863 msg = ("'troubles' is deprecated, "
870 "use 'instabilities'")
864 "use 'instabilities'")
871 repo.ui.deprecwarn(msg, '4.4')
865 repo.ui.deprecwarn(msg, '4.4')
872
866
873 return showinstabilities(repo=repo, **args)
867 return showinstabilities(repo=repo, **args)
874
868
875 @templatekeyword('instabilities')
869 @templatekeyword('instabilities')
876 def showinstabilities(**args):
870 def showinstabilities(**args):
877 """List of strings. Evolution instabilities affecting the changeset.
871 """List of strings. Evolution instabilities affecting the changeset.
878
879 (EXPERIMENTAL)
872 (EXPERIMENTAL)
880 """
873 """
881 args = pycompat.byteskwargs(args)
874 args = pycompat.byteskwargs(args)
882 return showlist('instability', args['ctx'].instabilities(), args,
875 return showlist('instability', args['ctx'].instabilities(), args,
883 plural='instabilities')
876 plural='instabilities')
884
877
885 # tell hggettext to extract docstrings from these functions:
878 # tell hggettext to extract docstrings from these functions:
886 i18nfunctions = keywords.values()
879 i18nfunctions = keywords.values()
General Comments 0
You need to be logged in to leave comments. Login now