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