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