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