##// END OF EJS Templates
py3: fix args handling for obsfate template...
Pulkit Goyal -
r35143:6fe99a8e default
parent child Browse files
Show More
@@ -1,908 +1,909 b''
1 # templatekw.py - common changeset template keywords
1 # templatekw.py - common changeset template keywords
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from .i18n import _
10 from .i18n import _
11 from .node import (
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 associated with the changeset.
380 """String. The active bookmark, if it is associated with the changeset.
381 (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 associated with the changeset."""
386 """String. The active bookmark, if it is associated with the changeset."""
387 active = args[r'repo']._activebookmark
387 active = args[r'repo']._activebookmark
388 if active and active in args[r'ctx'].bookmarks():
388 if active and active in args[r'ctx'].bookmarks():
389 return active
389 return active
390 return ''
390 return ''
391
391
392 @templatekeyword('date')
392 @templatekeyword('date')
393 def showdate(repo, ctx, templ, **args):
393 def showdate(repo, ctx, templ, **args):
394 """Date information. The date when the changeset was committed."""
394 """Date information. The date when the changeset was committed."""
395 return ctx.date()
395 return ctx.date()
396
396
397 @templatekeyword('desc')
397 @templatekeyword('desc')
398 def showdescription(repo, ctx, templ, **args):
398 def showdescription(repo, ctx, templ, **args):
399 """String. The text of the changeset description."""
399 """String. The text of the changeset description."""
400 s = ctx.description()
400 s = ctx.description()
401 if isinstance(s, encoding.localstr):
401 if isinstance(s, encoding.localstr):
402 # try hard to preserve utf-8 bytes
402 # try hard to preserve utf-8 bytes
403 return encoding.tolocal(encoding.fromlocal(s).strip())
403 return encoding.tolocal(encoding.fromlocal(s).strip())
404 else:
404 else:
405 return s.strip()
405 return s.strip()
406
406
407 @templatekeyword('diffstat')
407 @templatekeyword('diffstat')
408 def showdiffstat(repo, ctx, templ, **args):
408 def showdiffstat(repo, ctx, templ, **args):
409 """String. Statistics of changes with the following format:
409 """String. Statistics of changes with the following format:
410 "modified files: +added/-removed lines"
410 "modified files: +added/-removed lines"
411 """
411 """
412 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
412 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
413 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
413 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
414 return '%s: +%s/-%s' % (len(stats), adds, removes)
414 return '%s: +%s/-%s' % (len(stats), adds, removes)
415
415
416 @templatekeyword('envvars')
416 @templatekeyword('envvars')
417 def showenvvars(repo, **args):
417 def showenvvars(repo, **args):
418 """A dictionary of environment variables. (EXPERIMENTAL)"""
418 """A dictionary of environment variables. (EXPERIMENTAL)"""
419 args = pycompat.byteskwargs(args)
419 args = pycompat.byteskwargs(args)
420 env = repo.ui.exportableenviron()
420 env = repo.ui.exportableenviron()
421 env = util.sortdict((k, env[k]) for k in sorted(env))
421 env = util.sortdict((k, env[k]) for k in sorted(env))
422 return showdict('envvar', env, args, plural='envvars')
422 return showdict('envvar', env, args, plural='envvars')
423
423
424 @templatekeyword('extras')
424 @templatekeyword('extras')
425 def showextras(**args):
425 def showextras(**args):
426 """List of dicts with key, value entries of the 'extras'
426 """List of dicts with key, value entries of the 'extras'
427 field of this changeset."""
427 field of this changeset."""
428 args = pycompat.byteskwargs(args)
428 args = pycompat.byteskwargs(args)
429 extras = args['ctx'].extra()
429 extras = args['ctx'].extra()
430 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
430 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
431 makemap = lambda k: {'key': k, 'value': extras[k]}
431 makemap = lambda k: {'key': k, 'value': extras[k]}
432 c = [makemap(k) for k in extras]
432 c = [makemap(k) for k in extras]
433 f = _showlist('extra', c, args, plural='extras')
433 f = _showlist('extra', c, args, plural='extras')
434 return _hybrid(f, extras, makemap,
434 return _hybrid(f, extras, makemap,
435 lambda k: '%s=%s' % (k, util.escapestr(extras[k])))
435 lambda k: '%s=%s' % (k, util.escapestr(extras[k])))
436
436
437 @templatekeyword('file_adds')
437 @templatekeyword('file_adds')
438 def showfileadds(**args):
438 def showfileadds(**args):
439 """List of strings. Files added by this changeset."""
439 """List of strings. Files added by this changeset."""
440 args = pycompat.byteskwargs(args)
440 args = pycompat.byteskwargs(args)
441 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
441 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
442 return showlist('file_add', getfiles(repo, ctx, revcache)[1], args,
442 return showlist('file_add', getfiles(repo, ctx, revcache)[1], args,
443 element='file')
443 element='file')
444
444
445 @templatekeyword('file_copies')
445 @templatekeyword('file_copies')
446 def showfilecopies(**args):
446 def showfilecopies(**args):
447 """List of strings. Files copied in this changeset with
447 """List of strings. Files copied in this changeset with
448 their sources.
448 their sources.
449 """
449 """
450 args = pycompat.byteskwargs(args)
450 args = pycompat.byteskwargs(args)
451 cache, ctx = args['cache'], args['ctx']
451 cache, ctx = args['cache'], args['ctx']
452 copies = args['revcache'].get('copies')
452 copies = args['revcache'].get('copies')
453 if copies is None:
453 if copies is None:
454 if 'getrenamed' not in cache:
454 if 'getrenamed' not in cache:
455 cache['getrenamed'] = getrenamedfn(args['repo'])
455 cache['getrenamed'] = getrenamedfn(args['repo'])
456 copies = []
456 copies = []
457 getrenamed = cache['getrenamed']
457 getrenamed = cache['getrenamed']
458 for fn in ctx.files():
458 for fn in ctx.files():
459 rename = getrenamed(fn, ctx.rev())
459 rename = getrenamed(fn, ctx.rev())
460 if rename:
460 if rename:
461 copies.append((fn, rename[0]))
461 copies.append((fn, rename[0]))
462
462
463 copies = util.sortdict(copies)
463 copies = util.sortdict(copies)
464 return showdict('file_copy', copies, args, plural='file_copies',
464 return showdict('file_copy', copies, args, plural='file_copies',
465 key='name', value='source', fmt='%s (%s)')
465 key='name', value='source', fmt='%s (%s)')
466
466
467 # showfilecopiesswitch() displays file copies only if copy records are
467 # showfilecopiesswitch() displays file copies only if copy records are
468 # provided before calling the templater, usually with a --copies
468 # provided before calling the templater, usually with a --copies
469 # command line switch.
469 # command line switch.
470 @templatekeyword('file_copies_switch')
470 @templatekeyword('file_copies_switch')
471 def showfilecopiesswitch(**args):
471 def showfilecopiesswitch(**args):
472 """List of strings. Like "file_copies" but displayed
472 """List of strings. Like "file_copies" but displayed
473 only if the --copied switch is set.
473 only if the --copied switch is set.
474 """
474 """
475 args = pycompat.byteskwargs(args)
475 args = pycompat.byteskwargs(args)
476 copies = args['revcache'].get('copies') or []
476 copies = args['revcache'].get('copies') or []
477 copies = util.sortdict(copies)
477 copies = util.sortdict(copies)
478 return showdict('file_copy', copies, args, plural='file_copies',
478 return showdict('file_copy', copies, args, plural='file_copies',
479 key='name', value='source', fmt='%s (%s)')
479 key='name', value='source', fmt='%s (%s)')
480
480
481 @templatekeyword('file_dels')
481 @templatekeyword('file_dels')
482 def showfiledels(**args):
482 def showfiledels(**args):
483 """List of strings. Files removed by this changeset."""
483 """List of strings. Files removed by this changeset."""
484 args = pycompat.byteskwargs(args)
484 args = pycompat.byteskwargs(args)
485 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
485 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
486 return showlist('file_del', getfiles(repo, ctx, revcache)[2], args,
486 return showlist('file_del', getfiles(repo, ctx, revcache)[2], args,
487 element='file')
487 element='file')
488
488
489 @templatekeyword('file_mods')
489 @templatekeyword('file_mods')
490 def showfilemods(**args):
490 def showfilemods(**args):
491 """List of strings. Files modified by this changeset."""
491 """List of strings. Files modified by this changeset."""
492 args = pycompat.byteskwargs(args)
492 args = pycompat.byteskwargs(args)
493 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
493 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
494 return showlist('file_mod', getfiles(repo, ctx, revcache)[0], args,
494 return showlist('file_mod', getfiles(repo, ctx, revcache)[0], args,
495 element='file')
495 element='file')
496
496
497 @templatekeyword('files')
497 @templatekeyword('files')
498 def showfiles(**args):
498 def showfiles(**args):
499 """List of strings. All files modified, added, or removed by this
499 """List of strings. All files modified, added, or removed by this
500 changeset.
500 changeset.
501 """
501 """
502 args = pycompat.byteskwargs(args)
502 args = pycompat.byteskwargs(args)
503 return showlist('file', args['ctx'].files(), args)
503 return showlist('file', args['ctx'].files(), args)
504
504
505 @templatekeyword('graphnode')
505 @templatekeyword('graphnode')
506 def showgraphnode(repo, ctx, **args):
506 def showgraphnode(repo, ctx, **args):
507 """String. The character representing the changeset node in an ASCII
507 """String. The character representing the changeset node in an ASCII
508 revision graph."""
508 revision graph."""
509 wpnodes = repo.dirstate.parents()
509 wpnodes = repo.dirstate.parents()
510 if wpnodes[1] == nullid:
510 if wpnodes[1] == nullid:
511 wpnodes = wpnodes[:1]
511 wpnodes = wpnodes[:1]
512 if ctx.node() in wpnodes:
512 if ctx.node() in wpnodes:
513 return '@'
513 return '@'
514 elif ctx.obsolete():
514 elif ctx.obsolete():
515 return 'x'
515 return 'x'
516 elif ctx.closesbranch():
516 elif ctx.closesbranch():
517 return '_'
517 return '_'
518 else:
518 else:
519 return 'o'
519 return 'o'
520
520
521 @templatekeyword('graphwidth')
521 @templatekeyword('graphwidth')
522 def showgraphwidth(repo, ctx, templ, **args):
522 def showgraphwidth(repo, ctx, templ, **args):
523 """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."""
524 # 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
525 # name to pass the value through props into this function.
525 # name to pass the value through props into this function.
526 return args.get('_graphwidth', 0)
526 return args.get('_graphwidth', 0)
527
527
528 @templatekeyword('index')
528 @templatekeyword('index')
529 def showindex(**args):
529 def showindex(**args):
530 """Integer. The current iteration of the loop. (0 indexed)"""
530 """Integer. The current iteration of the loop. (0 indexed)"""
531 # just hosts documentation; should be overridden by template mapping
531 # just hosts documentation; should be overridden by template mapping
532 raise error.Abort(_("can't use index in this context"))
532 raise error.Abort(_("can't use index in this context"))
533
533
534 @templatekeyword('latesttag')
534 @templatekeyword('latesttag')
535 def showlatesttag(**args):
535 def showlatesttag(**args):
536 """List of strings. The global tags on the most recent globally
536 """List of strings. The global tags on the most recent globally
537 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
538 consists of the single string "null".
538 consists of the single string "null".
539 """
539 """
540 return showlatesttags(None, **args)
540 return showlatesttags(None, **args)
541
541
542 def showlatesttags(pattern, **args):
542 def showlatesttags(pattern, **args):
543 """helper method for the latesttag keyword and function"""
543 """helper method for the latesttag keyword and function"""
544 args = pycompat.byteskwargs(args)
544 args = pycompat.byteskwargs(args)
545 repo, ctx = args['repo'], args['ctx']
545 repo, ctx = args['repo'], args['ctx']
546 cache = args['cache']
546 cache = args['cache']
547 latesttags = getlatesttags(repo, ctx, cache, pattern)
547 latesttags = getlatesttags(repo, ctx, cache, pattern)
548
548
549 # latesttag[0] is an implementation detail for sorting csets on different
549 # latesttag[0] is an implementation detail for sorting csets on different
550 # 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,
551 # 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.
552 makemap = lambda v: {
552 makemap = lambda v: {
553 'changes': _showchangessincetag,
553 'changes': _showchangessincetag,
554 'distance': latesttags[1],
554 'distance': latesttags[1],
555 'latesttag': v, # BC with {latesttag % '{latesttag}'}
555 'latesttag': v, # BC with {latesttag % '{latesttag}'}
556 'tag': v
556 'tag': v
557 }
557 }
558
558
559 tags = latesttags[2]
559 tags = latesttags[2]
560 f = _showlist('latesttag', tags, args, separator=':')
560 f = _showlist('latesttag', tags, args, separator=':')
561 return _hybrid(f, tags, makemap, pycompat.identity)
561 return _hybrid(f, tags, makemap, pycompat.identity)
562
562
563 @templatekeyword('latesttagdistance')
563 @templatekeyword('latesttagdistance')
564 def showlatesttagdistance(repo, ctx, templ, cache, **args):
564 def showlatesttagdistance(repo, ctx, templ, cache, **args):
565 """Integer. Longest path to the latest tag."""
565 """Integer. Longest path to the latest tag."""
566 return getlatesttags(repo, ctx, cache)[1]
566 return getlatesttags(repo, ctx, cache)[1]
567
567
568 @templatekeyword('changessincelatesttag')
568 @templatekeyword('changessincelatesttag')
569 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
569 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
570 """Integer. All ancestors not in the latest tag."""
570 """Integer. All ancestors not in the latest tag."""
571 latesttag = getlatesttags(repo, ctx, cache)[2][0]
571 latesttag = getlatesttags(repo, ctx, cache)[2][0]
572
572
573 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
573 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
574
574
575 def _showchangessincetag(repo, ctx, **args):
575 def _showchangessincetag(repo, ctx, **args):
576 offset = 0
576 offset = 0
577 revs = [ctx.rev()]
577 revs = [ctx.rev()]
578 tag = args[r'tag']
578 tag = args[r'tag']
579
579
580 # The only() revset doesn't currently support wdir()
580 # The only() revset doesn't currently support wdir()
581 if ctx.rev() is None:
581 if ctx.rev() is None:
582 offset = 1
582 offset = 1
583 revs = [p.rev() for p in ctx.parents()]
583 revs = [p.rev() for p in ctx.parents()]
584
584
585 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
585 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
586
586
587 @templatekeyword('manifest')
587 @templatekeyword('manifest')
588 def showmanifest(**args):
588 def showmanifest(**args):
589 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']
590 mnode = ctx.manifestnode()
590 mnode = ctx.manifestnode()
591 if mnode is None:
591 if mnode is None:
592 # 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
593 return
593 return
594 mrev = repo.manifestlog._revlog.rev(mnode)
594 mrev = repo.manifestlog._revlog.rev(mnode)
595 mhex = hex(mnode)
595 mhex = hex(mnode)
596 args = args.copy()
596 args = args.copy()
597 args.update({r'rev': mrev, r'node': mhex})
597 args.update({r'rev': mrev, r'node': mhex})
598 f = templ('manifest', **args)
598 f = templ('manifest', **args)
599 # TODO: perhaps 'ctx' should be dropped from mapping because manifest
599 # TODO: perhaps 'ctx' should be dropped from mapping because manifest
600 # rev and node are completely different from changeset's.
600 # rev and node are completely different from changeset's.
601 return _mappable(f, None, f, lambda x: {'rev': mrev, 'node': mhex})
601 return _mappable(f, None, f, lambda x: {'rev': mrev, 'node': mhex})
602
602
603 @templatekeyword('obsfate')
603 @templatekeyword('obsfate')
604 def showobsfate(**args):
604 def showobsfate(**args):
605 # this function returns a list containing pre-formatted obsfate strings.
605 # this function returns a list containing pre-formatted obsfate strings.
606 #
606 #
607 # This function will be replaced by templates fragments when we will have
607 # This function will be replaced by templates fragments when we will have
608 # the verbosity templatekw available.
608 # the verbosity templatekw available.
609 succsandmarkers = showsuccsandmarkers(**args)
609 succsandmarkers = showsuccsandmarkers(**args)
610
610
611 args = pycompat.byteskwargs(args)
611 ui = args['ui']
612 ui = args['ui']
612
613
613 values = []
614 values = []
614
615
615 for x in succsandmarkers:
616 for x in succsandmarkers:
616 values.append(obsutil.obsfateprinter(x['successors'], x['markers'], ui))
617 values.append(obsutil.obsfateprinter(x['successors'], x['markers'], ui))
617
618
618 return showlist("fate", values, args)
619 return showlist("fate", values, args)
619
620
620 def shownames(namespace, **args):
621 def shownames(namespace, **args):
621 """helper method to generate a template keyword for a namespace"""
622 """helper method to generate a template keyword for a namespace"""
622 args = pycompat.byteskwargs(args)
623 args = pycompat.byteskwargs(args)
623 ctx = args['ctx']
624 ctx = args['ctx']
624 repo = ctx.repo()
625 repo = ctx.repo()
625 ns = repo.names[namespace]
626 ns = repo.names[namespace]
626 names = ns.names(repo, ctx.node())
627 names = ns.names(repo, ctx.node())
627 return showlist(ns.templatename, names, args, plural=namespace)
628 return showlist(ns.templatename, names, args, plural=namespace)
628
629
629 @templatekeyword('namespaces')
630 @templatekeyword('namespaces')
630 def shownamespaces(**args):
631 def shownamespaces(**args):
631 """Dict of lists. Names attached to this changeset per
632 """Dict of lists. Names attached to this changeset per
632 namespace."""
633 namespace."""
633 args = pycompat.byteskwargs(args)
634 args = pycompat.byteskwargs(args)
634 ctx = args['ctx']
635 ctx = args['ctx']
635 repo = ctx.repo()
636 repo = ctx.repo()
636
637
637 namespaces = util.sortdict()
638 namespaces = util.sortdict()
638 def makensmapfn(ns):
639 def makensmapfn(ns):
639 # 'name' for iterating over namespaces, templatename for local reference
640 # 'name' for iterating over namespaces, templatename for local reference
640 return lambda v: {'name': v, ns.templatename: v}
641 return lambda v: {'name': v, ns.templatename: v}
641
642
642 for k, ns in repo.names.iteritems():
643 for k, ns in repo.names.iteritems():
643 names = ns.names(repo, ctx.node())
644 names = ns.names(repo, ctx.node())
644 f = _showlist('name', names, args)
645 f = _showlist('name', names, args)
645 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
646 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
646
647
647 f = _showlist('namespace', list(namespaces), args)
648 f = _showlist('namespace', list(namespaces), args)
648
649
649 def makemap(ns):
650 def makemap(ns):
650 return {
651 return {
651 'namespace': ns,
652 'namespace': ns,
652 'names': namespaces[ns],
653 'names': namespaces[ns],
653 'builtin': repo.names[ns].builtin,
654 'builtin': repo.names[ns].builtin,
654 'colorname': repo.names[ns].colorname,
655 'colorname': repo.names[ns].colorname,
655 }
656 }
656
657
657 return _hybrid(f, namespaces, makemap, pycompat.identity)
658 return _hybrid(f, namespaces, makemap, pycompat.identity)
658
659
659 @templatekeyword('node')
660 @templatekeyword('node')
660 def shownode(repo, ctx, templ, **args):
661 def shownode(repo, ctx, templ, **args):
661 """String. The changeset identification hash, as a 40 hexadecimal
662 """String. The changeset identification hash, as a 40 hexadecimal
662 digit string.
663 digit string.
663 """
664 """
664 return ctx.hex()
665 return ctx.hex()
665
666
666 @templatekeyword('obsolete')
667 @templatekeyword('obsolete')
667 def showobsolete(repo, ctx, templ, **args):
668 def showobsolete(repo, ctx, templ, **args):
668 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
669 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
669 if ctx.obsolete():
670 if ctx.obsolete():
670 return 'obsolete'
671 return 'obsolete'
671 return ''
672 return ''
672
673
673 @templatekeyword('peerurls')
674 @templatekeyword('peerurls')
674 def showpeerurls(repo, **args):
675 def showpeerurls(repo, **args):
675 """A dictionary of repository locations defined in the [paths] section
676 """A dictionary of repository locations defined in the [paths] section
676 of your configuration file."""
677 of your configuration file."""
677 # see commands.paths() for naming of dictionary keys
678 # see commands.paths() for naming of dictionary keys
678 paths = repo.ui.paths
679 paths = repo.ui.paths
679 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
680 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
680 def makemap(k):
681 def makemap(k):
681 p = paths[k]
682 p = paths[k]
682 d = {'name': k, 'url': p.rawloc}
683 d = {'name': k, 'url': p.rawloc}
683 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
684 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
684 return d
685 return d
685 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
686 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
686
687
687 @templatekeyword("predecessors")
688 @templatekeyword("predecessors")
688 def showpredecessors(repo, ctx, **args):
689 def showpredecessors(repo, ctx, **args):
689 """Returns the list if the closest visible successors. (EXPERIMENTAL)"""
690 """Returns the list if the closest visible successors. (EXPERIMENTAL)"""
690 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
691 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
691 predecessors = map(hex, predecessors)
692 predecessors = map(hex, predecessors)
692
693
693 return _hybrid(None, predecessors,
694 return _hybrid(None, predecessors,
694 lambda x: {'ctx': repo[x], 'revcache': {}},
695 lambda x: {'ctx': repo[x], 'revcache': {}},
695 lambda x: scmutil.formatchangeid(repo[x]))
696 lambda x: scmutil.formatchangeid(repo[x]))
696
697
697 @templatekeyword("successorssets")
698 @templatekeyword("successorssets")
698 def showsuccessorssets(repo, ctx, **args):
699 def showsuccessorssets(repo, ctx, **args):
699 """Returns a string of sets of successors for a changectx. Format used
700 """Returns a string of sets of successors for a changectx. Format used
700 is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
701 is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
701 while also diverged into ctx3. (EXPERIMENTAL)"""
702 while also diverged into ctx3. (EXPERIMENTAL)"""
702 if not ctx.obsolete():
703 if not ctx.obsolete():
703 return ''
704 return ''
704 args = pycompat.byteskwargs(args)
705 args = pycompat.byteskwargs(args)
705
706
706 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
707 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
707 ssets = [[hex(n) for n in ss] for ss in ssets]
708 ssets = [[hex(n) for n in ss] for ss in ssets]
708
709
709 data = []
710 data = []
710 for ss in ssets:
711 for ss in ssets:
711 h = _hybrid(None, ss, lambda x: {'ctx': repo[x], 'revcache': {}},
712 h = _hybrid(None, ss, lambda x: {'ctx': repo[x], 'revcache': {}},
712 lambda x: scmutil.formatchangeid(repo[x]))
713 lambda x: scmutil.formatchangeid(repo[x]))
713 data.append(h)
714 data.append(h)
714
715
715 # Format the successorssets
716 # Format the successorssets
716 def render(d):
717 def render(d):
717 t = []
718 t = []
718 for i in d.gen():
719 for i in d.gen():
719 t.append(i)
720 t.append(i)
720 return "".join(t)
721 return "".join(t)
721
722
722 def gen(data):
723 def gen(data):
723 yield "; ".join(render(d) for d in data)
724 yield "; ".join(render(d) for d in data)
724
725
725 return _hybrid(gen(data), data, lambda x: {'successorset': x},
726 return _hybrid(gen(data), data, lambda x: {'successorset': x},
726 pycompat.identity)
727 pycompat.identity)
727
728
728 @templatekeyword("succsandmarkers")
729 @templatekeyword("succsandmarkers")
729 def showsuccsandmarkers(repo, ctx, **args):
730 def showsuccsandmarkers(repo, ctx, **args):
730 """Returns a list of dict for each final successor of ctx. The dict
731 """Returns a list of dict for each final successor of ctx. The dict
731 contains successors node id in "successors" keys and the list of
732 contains successors node id in "successors" keys and the list of
732 obs-markers from ctx to the set of successors in "markers".
733 obs-markers from ctx to the set of successors in "markers".
733 (EXPERIMENTAL)
734 (EXPERIMENTAL)
734 """
735 """
735
736
736 values = obsutil.successorsandmarkers(repo, ctx)
737 values = obsutil.successorsandmarkers(repo, ctx)
737
738
738 if values is None:
739 if values is None:
739 values = []
740 values = []
740
741
741 # Format successors and markers to avoid exposing binary to templates
742 # Format successors and markers to avoid exposing binary to templates
742 data = []
743 data = []
743 for i in values:
744 for i in values:
744 # Format successors
745 # Format successors
745 successors = i['successors']
746 successors = i['successors']
746
747
747 successors = [hex(n) for n in successors]
748 successors = [hex(n) for n in successors]
748 successors = _hybrid(None, successors,
749 successors = _hybrid(None, successors,
749 lambda x: {'ctx': repo[x], 'revcache': {}},
750 lambda x: {'ctx': repo[x], 'revcache': {}},
750 lambda x: scmutil.formatchangeid(repo[x]))
751 lambda x: scmutil.formatchangeid(repo[x]))
751
752
752 # Format markers
753 # Format markers
753 finalmarkers = []
754 finalmarkers = []
754 for m in i['markers']:
755 for m in i['markers']:
755 hexprec = hex(m[0])
756 hexprec = hex(m[0])
756 hexsucs = tuple(hex(n) for n in m[1])
757 hexsucs = tuple(hex(n) for n in m[1])
757 hexparents = None
758 hexparents = None
758 if m[5] is not None:
759 if m[5] is not None:
759 hexparents = tuple(hex(n) for n in m[5])
760 hexparents = tuple(hex(n) for n in m[5])
760 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
761 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
761 finalmarkers.append(newmarker)
762 finalmarkers.append(newmarker)
762
763
763 data.append({'successors': successors, 'markers': finalmarkers})
764 data.append({'successors': successors, 'markers': finalmarkers})
764
765
765 f = _showlist('succsandmarkers', data, args)
766 f = _showlist('succsandmarkers', data, args)
766 return _hybrid(f, data, lambda x: x, pycompat.identity)
767 return _hybrid(f, data, lambda x: x, pycompat.identity)
767
768
768 @templatekeyword('p1rev')
769 @templatekeyword('p1rev')
769 def showp1rev(repo, ctx, templ, **args):
770 def showp1rev(repo, ctx, templ, **args):
770 """Integer. The repository-local revision number of the changeset's
771 """Integer. The repository-local revision number of the changeset's
771 first parent, or -1 if the changeset has no parents."""
772 first parent, or -1 if the changeset has no parents."""
772 return ctx.p1().rev()
773 return ctx.p1().rev()
773
774
774 @templatekeyword('p2rev')
775 @templatekeyword('p2rev')
775 def showp2rev(repo, ctx, templ, **args):
776 def showp2rev(repo, ctx, templ, **args):
776 """Integer. The repository-local revision number of the changeset's
777 """Integer. The repository-local revision number of the changeset's
777 second parent, or -1 if the changeset has no second parent."""
778 second parent, or -1 if the changeset has no second parent."""
778 return ctx.p2().rev()
779 return ctx.p2().rev()
779
780
780 @templatekeyword('p1node')
781 @templatekeyword('p1node')
781 def showp1node(repo, ctx, templ, **args):
782 def showp1node(repo, ctx, templ, **args):
782 """String. The identification hash of the changeset's first parent,
783 """String. The identification hash of the changeset's first parent,
783 as a 40 digit hexadecimal string. If the changeset has no parents, all
784 as a 40 digit hexadecimal string. If the changeset has no parents, all
784 digits are 0."""
785 digits are 0."""
785 return ctx.p1().hex()
786 return ctx.p1().hex()
786
787
787 @templatekeyword('p2node')
788 @templatekeyword('p2node')
788 def showp2node(repo, ctx, templ, **args):
789 def showp2node(repo, ctx, templ, **args):
789 """String. The identification hash of the changeset's second
790 """String. The identification hash of the changeset's second
790 parent, as a 40 digit hexadecimal string. If the changeset has no second
791 parent, as a 40 digit hexadecimal string. If the changeset has no second
791 parent, all digits are 0."""
792 parent, all digits are 0."""
792 return ctx.p2().hex()
793 return ctx.p2().hex()
793
794
794 @templatekeyword('parents')
795 @templatekeyword('parents')
795 def showparents(**args):
796 def showparents(**args):
796 """List of strings. The parents of the changeset in "rev:node"
797 """List of strings. The parents of the changeset in "rev:node"
797 format. If the changeset has only one "natural" parent (the predecessor
798 format. If the changeset has only one "natural" parent (the predecessor
798 revision) nothing is shown."""
799 revision) nothing is shown."""
799 args = pycompat.byteskwargs(args)
800 args = pycompat.byteskwargs(args)
800 repo = args['repo']
801 repo = args['repo']
801 ctx = args['ctx']
802 ctx = args['ctx']
802 pctxs = scmutil.meaningfulparents(repo, ctx)
803 pctxs = scmutil.meaningfulparents(repo, ctx)
803 prevs = [p.rev() for p in pctxs]
804 prevs = [p.rev() for p in pctxs]
804 parents = [[('rev', p.rev()),
805 parents = [[('rev', p.rev()),
805 ('node', p.hex()),
806 ('node', p.hex()),
806 ('phase', p.phasestr())]
807 ('phase', p.phasestr())]
807 for p in pctxs]
808 for p in pctxs]
808 f = _showlist('parent', parents, args)
809 f = _showlist('parent', parents, args)
809 return _hybrid(f, prevs, lambda x: {'ctx': repo[x], 'revcache': {}},
810 return _hybrid(f, prevs, lambda x: {'ctx': repo[x], 'revcache': {}},
810 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
811 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
811
812
812 @templatekeyword('phase')
813 @templatekeyword('phase')
813 def showphase(repo, ctx, templ, **args):
814 def showphase(repo, ctx, templ, **args):
814 """String. The changeset phase name."""
815 """String. The changeset phase name."""
815 return ctx.phasestr()
816 return ctx.phasestr()
816
817
817 @templatekeyword('phaseidx')
818 @templatekeyword('phaseidx')
818 def showphaseidx(repo, ctx, templ, **args):
819 def showphaseidx(repo, ctx, templ, **args):
819 """Integer. The changeset phase index. (ADVANCED)"""
820 """Integer. The changeset phase index. (ADVANCED)"""
820 return ctx.phase()
821 return ctx.phase()
821
822
822 @templatekeyword('rev')
823 @templatekeyword('rev')
823 def showrev(repo, ctx, templ, **args):
824 def showrev(repo, ctx, templ, **args):
824 """Integer. The repository-local changeset revision number."""
825 """Integer. The repository-local changeset revision number."""
825 return scmutil.intrev(ctx)
826 return scmutil.intrev(ctx)
826
827
827 def showrevslist(name, revs, **args):
828 def showrevslist(name, revs, **args):
828 """helper to generate a list of revisions in which a mapped template will
829 """helper to generate a list of revisions in which a mapped template will
829 be evaluated"""
830 be evaluated"""
830 args = pycompat.byteskwargs(args)
831 args = pycompat.byteskwargs(args)
831 repo = args['ctx'].repo()
832 repo = args['ctx'].repo()
832 f = _showlist(name, ['%d' % r for r in revs], args)
833 f = _showlist(name, ['%d' % r for r in revs], args)
833 return _hybrid(f, revs,
834 return _hybrid(f, revs,
834 lambda x: {name: x, 'ctx': repo[x], 'revcache': {}},
835 lambda x: {name: x, 'ctx': repo[x], 'revcache': {}},
835 pycompat.identity, keytype=int)
836 pycompat.identity, keytype=int)
836
837
837 @templatekeyword('subrepos')
838 @templatekeyword('subrepos')
838 def showsubrepos(**args):
839 def showsubrepos(**args):
839 """List of strings. Updated subrepositories in the changeset."""
840 """List of strings. Updated subrepositories in the changeset."""
840 args = pycompat.byteskwargs(args)
841 args = pycompat.byteskwargs(args)
841 ctx = args['ctx']
842 ctx = args['ctx']
842 substate = ctx.substate
843 substate = ctx.substate
843 if not substate:
844 if not substate:
844 return showlist('subrepo', [], args)
845 return showlist('subrepo', [], args)
845 psubstate = ctx.parents()[0].substate or {}
846 psubstate = ctx.parents()[0].substate or {}
846 subrepos = []
847 subrepos = []
847 for sub in substate:
848 for sub in substate:
848 if sub not in psubstate or substate[sub] != psubstate[sub]:
849 if sub not in psubstate or substate[sub] != psubstate[sub]:
849 subrepos.append(sub) # modified or newly added in ctx
850 subrepos.append(sub) # modified or newly added in ctx
850 for sub in psubstate:
851 for sub in psubstate:
851 if sub not in substate:
852 if sub not in substate:
852 subrepos.append(sub) # removed in ctx
853 subrepos.append(sub) # removed in ctx
853 return showlist('subrepo', sorted(subrepos), args)
854 return showlist('subrepo', sorted(subrepos), args)
854
855
855 # don't remove "showtags" definition, even though namespaces will put
856 # don't remove "showtags" definition, even though namespaces will put
856 # a helper function for "tags" keyword into "keywords" map automatically,
857 # a helper function for "tags" keyword into "keywords" map automatically,
857 # because online help text is built without namespaces initialization
858 # because online help text is built without namespaces initialization
858 @templatekeyword('tags')
859 @templatekeyword('tags')
859 def showtags(**args):
860 def showtags(**args):
860 """List of strings. Any tags associated with the changeset."""
861 """List of strings. Any tags associated with the changeset."""
861 return shownames('tags', **args)
862 return shownames('tags', **args)
862
863
863 @templatekeyword('termwidth')
864 @templatekeyword('termwidth')
864 def showtermwidth(repo, ctx, templ, **args):
865 def showtermwidth(repo, ctx, templ, **args):
865 """Integer. The width of the current terminal."""
866 """Integer. The width of the current terminal."""
866 return repo.ui.termwidth()
867 return repo.ui.termwidth()
867
868
868 @templatekeyword('troubles')
869 @templatekeyword('troubles')
869 def showtroubles(repo, **args):
870 def showtroubles(repo, **args):
870 """List of strings. Evolution troubles affecting the changeset.
871 """List of strings. Evolution troubles affecting the changeset.
871 (DEPRECATED)
872 (DEPRECATED)
872 """
873 """
873 msg = ("'troubles' is deprecated, "
874 msg = ("'troubles' is deprecated, "
874 "use 'instabilities'")
875 "use 'instabilities'")
875 repo.ui.deprecwarn(msg, '4.4')
876 repo.ui.deprecwarn(msg, '4.4')
876
877
877 return showinstabilities(repo=repo, **args)
878 return showinstabilities(repo=repo, **args)
878
879
879 @templatekeyword('instabilities')
880 @templatekeyword('instabilities')
880 def showinstabilities(**args):
881 def showinstabilities(**args):
881 """List of strings. Evolution instabilities affecting the changeset.
882 """List of strings. Evolution instabilities affecting the changeset.
882 (EXPERIMENTAL)
883 (EXPERIMENTAL)
883 """
884 """
884 args = pycompat.byteskwargs(args)
885 args = pycompat.byteskwargs(args)
885 return showlist('instability', args['ctx'].instabilities(), args,
886 return showlist('instability', args['ctx'].instabilities(), args,
886 plural='instabilities')
887 plural='instabilities')
887
888
888 @templatekeyword('verbosity')
889 @templatekeyword('verbosity')
889 def showverbosity(ui, **args):
890 def showverbosity(ui, **args):
890 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
891 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
891 or ''."""
892 or ''."""
892 # see cmdutil.changeset_templater for priority of these flags
893 # see cmdutil.changeset_templater for priority of these flags
893 if ui.debugflag:
894 if ui.debugflag:
894 return 'debug'
895 return 'debug'
895 elif ui.quiet:
896 elif ui.quiet:
896 return 'quiet'
897 return 'quiet'
897 elif ui.verbose:
898 elif ui.verbose:
898 return 'verbose'
899 return 'verbose'
899 return ''
900 return ''
900
901
901 def loadkeyword(ui, extname, registrarobj):
902 def loadkeyword(ui, extname, registrarobj):
902 """Load template keyword from specified registrarobj
903 """Load template keyword from specified registrarobj
903 """
904 """
904 for name, func in registrarobj._table.iteritems():
905 for name, func in registrarobj._table.iteritems():
905 keywords[name] = func
906 keywords[name] = func
906
907
907 # tell hggettext to extract docstrings from these functions:
908 # tell hggettext to extract docstrings from these functions:
908 i18nfunctions = keywords.values()
909 i18nfunctions = keywords.values()
General Comments 0
You need to be logged in to leave comments. Login now