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