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