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