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