##// END OF EJS Templates
py3: convert keys of kwargs in template keywords functions to bytes...
Pulkit Goyal -
r32972:26e710f0 default
parent child Browse files
Show More
@@ -1,688 +1,707 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 short,
14 short,
15 )
15 )
16
16
17 from . import (
17 from . import (
18 encoding,
18 encoding,
19 error,
19 error,
20 hbisect,
20 hbisect,
21 obsutil,
21 obsutil,
22 patch,
22 patch,
23 pycompat,
23 registrar,
24 registrar,
24 scmutil,
25 scmutil,
25 util,
26 util,
26 )
27 )
27
28
28 class _hybrid(object):
29 class _hybrid(object):
29 """Wrapper for list or dict to support legacy template
30 """Wrapper for list or dict to support legacy template
30
31
31 This class allows us to handle both:
32 This class allows us to handle both:
32 - "{files}" (legacy command-line-specific list hack) and
33 - "{files}" (legacy command-line-specific list hack) and
33 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
34 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
34 and to access raw values:
35 and to access raw values:
35 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
36 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
36 - "{get(extras, key)}"
37 - "{get(extras, key)}"
37 - "{files|json}"
38 - "{files|json}"
38 """
39 """
39
40
40 def __init__(self, gen, values, makemap, joinfmt):
41 def __init__(self, gen, values, makemap, joinfmt):
41 if gen is not None:
42 if gen is not None:
42 self.gen = gen
43 self.gen = gen
43 self._values = values
44 self._values = values
44 self._makemap = makemap
45 self._makemap = makemap
45 self.joinfmt = joinfmt
46 self.joinfmt = joinfmt
46 @util.propertycache
47 @util.propertycache
47 def gen(self):
48 def gen(self):
48 return self._defaultgen()
49 return self._defaultgen()
49 def _defaultgen(self):
50 def _defaultgen(self):
50 """Generator to stringify this as {join(self, ' ')}"""
51 """Generator to stringify this as {join(self, ' ')}"""
51 for i, d in enumerate(self.itermaps()):
52 for i, d in enumerate(self.itermaps()):
52 if i > 0:
53 if i > 0:
53 yield ' '
54 yield ' '
54 yield self.joinfmt(d)
55 yield self.joinfmt(d)
55 def itermaps(self):
56 def itermaps(self):
56 makemap = self._makemap
57 makemap = self._makemap
57 for x in self._values:
58 for x in self._values:
58 yield makemap(x)
59 yield makemap(x)
59 def __contains__(self, x):
60 def __contains__(self, x):
60 return x in self._values
61 return x in self._values
61 def __len__(self):
62 def __len__(self):
62 return len(self._values)
63 return len(self._values)
63 def __iter__(self):
64 def __iter__(self):
64 return iter(self._values)
65 return iter(self._values)
65 def __getattr__(self, name):
66 def __getattr__(self, name):
66 if name not in ('get', 'items', 'iteritems', 'iterkeys', 'itervalues',
67 if name not in ('get', 'items', 'iteritems', 'iterkeys', 'itervalues',
67 'keys', 'values'):
68 'keys', 'values'):
68 raise AttributeError(name)
69 raise AttributeError(name)
69 return getattr(self._values, name)
70 return getattr(self._values, name)
70
71
71 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
72 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
72 """Wrap data to support both dict-like and string-like operations"""
73 """Wrap data to support both dict-like and string-like operations"""
73 return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
74 return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
74 lambda d: fmt % (d[key], d[value]))
75 lambda d: fmt % (d[key], d[value]))
75
76
76 def hybridlist(data, name, fmt='%s', gen=None):
77 def hybridlist(data, name, fmt='%s', gen=None):
77 """Wrap data to support both list-like and string-like operations"""
78 """Wrap data to support both list-like and string-like operations"""
78 return _hybrid(gen, data, lambda x: {name: x}, lambda d: fmt % d[name])
79 return _hybrid(gen, data, lambda x: {name: x}, lambda d: fmt % d[name])
79
80
80 def unwraphybrid(thing):
81 def unwraphybrid(thing):
81 """Return an object which can be stringified possibly by using a legacy
82 """Return an object which can be stringified possibly by using a legacy
82 template"""
83 template"""
83 if not util.safehasattr(thing, 'gen'):
84 if not util.safehasattr(thing, 'gen'):
84 return thing
85 return thing
85 return thing.gen
86 return thing.gen
86
87
87 def showdict(name, data, mapping, plural=None, key='key', value='value',
88 def showdict(name, data, mapping, plural=None, key='key', value='value',
88 fmt='%s=%s', separator=' '):
89 fmt='%s=%s', separator=' '):
89 c = [{key: k, value: v} for k, v in data.iteritems()]
90 c = [{key: k, value: v} for k, v in data.iteritems()]
90 f = _showlist(name, c, mapping, plural, separator)
91 f = _showlist(name, c, mapping, plural, separator)
91 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
92 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
92
93
93 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
94 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
94 if not element:
95 if not element:
95 element = name
96 element = name
96 f = _showlist(name, values, mapping, plural, separator)
97 f = _showlist(name, values, mapping, plural, separator)
97 return hybridlist(values, name=element, gen=f)
98 return hybridlist(values, name=element, gen=f)
98
99
99 def _showlist(name, values, mapping, plural=None, separator=' '):
100 def _showlist(name, values, mapping, plural=None, separator=' '):
100 '''expand set of values.
101 '''expand set of values.
101 name is name of key in template map.
102 name is name of key in template map.
102 values is list of strings or dicts.
103 values is list of strings or dicts.
103 plural is plural of name, if not simply name + 's'.
104 plural is plural of name, if not simply name + 's'.
104 separator is used to join values as a string
105 separator is used to join values as a string
105
106
106 expansion works like this, given name 'foo'.
107 expansion works like this, given name 'foo'.
107
108
108 if values is empty, expand 'no_foos'.
109 if values is empty, expand 'no_foos'.
109
110
110 if 'foo' not in template map, return values as a string,
111 if 'foo' not in template map, return values as a string,
111 joined by 'separator'.
112 joined by 'separator'.
112
113
113 expand 'start_foos'.
114 expand 'start_foos'.
114
115
115 for each value, expand 'foo'. if 'last_foo' in template
116 for each value, expand 'foo'. if 'last_foo' in template
116 map, expand it instead of 'foo' for last key.
117 map, expand it instead of 'foo' for last key.
117
118
118 expand 'end_foos'.
119 expand 'end_foos'.
119 '''
120 '''
120 templ = mapping['templ']
121 templ = mapping['templ']
121 if not plural:
122 if not plural:
122 plural = name + 's'
123 plural = name + 's'
123 if not values:
124 if not values:
124 noname = 'no_' + plural
125 noname = 'no_' + plural
125 if noname in templ:
126 if noname in templ:
126 yield templ(noname, **mapping)
127 yield templ(noname, **mapping)
127 return
128 return
128 if name not in templ:
129 if name not in templ:
129 if isinstance(values[0], bytes):
130 if isinstance(values[0], bytes):
130 yield separator.join(values)
131 yield separator.join(values)
131 else:
132 else:
132 for v in values:
133 for v in values:
133 yield dict(v, **mapping)
134 yield dict(v, **mapping)
134 return
135 return
135 startname = 'start_' + plural
136 startname = 'start_' + plural
136 if startname in templ:
137 if startname in templ:
137 yield templ(startname, **mapping)
138 yield templ(startname, **mapping)
138 vmapping = mapping.copy()
139 vmapping = mapping.copy()
139 def one(v, tag=name):
140 def one(v, tag=name):
140 try:
141 try:
141 vmapping.update(v)
142 vmapping.update(v)
142 except (AttributeError, ValueError):
143 except (AttributeError, ValueError):
143 try:
144 try:
144 for a, b in v:
145 for a, b in v:
145 vmapping[a] = b
146 vmapping[a] = b
146 except ValueError:
147 except ValueError:
147 vmapping[name] = v
148 vmapping[name] = v
148 return templ(tag, **vmapping)
149 return templ(tag, **vmapping)
149 lastname = 'last_' + name
150 lastname = 'last_' + name
150 if lastname in templ:
151 if lastname in templ:
151 last = values.pop()
152 last = values.pop()
152 else:
153 else:
153 last = None
154 last = None
154 for v in values:
155 for v in values:
155 yield one(v)
156 yield one(v)
156 if last is not None:
157 if last is not None:
157 yield one(last, tag=lastname)
158 yield one(last, tag=lastname)
158 endname = 'end_' + plural
159 endname = 'end_' + plural
159 if endname in templ:
160 if endname in templ:
160 yield templ(endname, **mapping)
161 yield templ(endname, **mapping)
161
162
162 def _formatrevnode(ctx):
163 def _formatrevnode(ctx):
163 """Format changeset as '{rev}:{node|formatnode}', which is the default
164 """Format changeset as '{rev}:{node|formatnode}', which is the default
164 template provided by cmdutil.changeset_templater"""
165 template provided by cmdutil.changeset_templater"""
165 repo = ctx.repo()
166 repo = ctx.repo()
166 if repo.ui.debugflag:
167 if repo.ui.debugflag:
167 hexfunc = hex
168 hexfunc = hex
168 else:
169 else:
169 hexfunc = short
170 hexfunc = short
170 return '%d:%s' % (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
171 return '%d:%s' % (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
171
172
172 def getfiles(repo, ctx, revcache):
173 def getfiles(repo, ctx, revcache):
173 if 'files' not in revcache:
174 if 'files' not in revcache:
174 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
175 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
175 return revcache['files']
176 return revcache['files']
176
177
177 def getlatesttags(repo, ctx, cache, pattern=None):
178 def getlatesttags(repo, ctx, cache, pattern=None):
178 '''return date, distance and name for the latest tag of rev'''
179 '''return date, distance and name for the latest tag of rev'''
179
180
180 cachename = 'latesttags'
181 cachename = 'latesttags'
181 if pattern is not None:
182 if pattern is not None:
182 cachename += '-' + pattern
183 cachename += '-' + pattern
183 match = util.stringmatcher(pattern)[2]
184 match = util.stringmatcher(pattern)[2]
184 else:
185 else:
185 match = util.always
186 match = util.always
186
187
187 if cachename not in cache:
188 if cachename not in cache:
188 # Cache mapping from rev to a tuple with tag date, tag
189 # Cache mapping from rev to a tuple with tag date, tag
189 # distance and tag name
190 # distance and tag name
190 cache[cachename] = {-1: (0, 0, ['null'])}
191 cache[cachename] = {-1: (0, 0, ['null'])}
191 latesttags = cache[cachename]
192 latesttags = cache[cachename]
192
193
193 rev = ctx.rev()
194 rev = ctx.rev()
194 todo = [rev]
195 todo = [rev]
195 while todo:
196 while todo:
196 rev = todo.pop()
197 rev = todo.pop()
197 if rev in latesttags:
198 if rev in latesttags:
198 continue
199 continue
199 ctx = repo[rev]
200 ctx = repo[rev]
200 tags = [t for t in ctx.tags()
201 tags = [t for t in ctx.tags()
201 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
202 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
202 and match(t))]
203 and match(t))]
203 if tags:
204 if tags:
204 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
205 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
205 continue
206 continue
206 try:
207 try:
207 # The tuples are laid out so the right one can be found by
208 # The tuples are laid out so the right one can be found by
208 # comparison.
209 # comparison.
209 pdate, pdist, ptag = max(
210 pdate, pdist, ptag = max(
210 latesttags[p.rev()] for p in ctx.parents())
211 latesttags[p.rev()] for p in ctx.parents())
211 except KeyError:
212 except KeyError:
212 # Cache miss - recurse
213 # Cache miss - recurse
213 todo.append(rev)
214 todo.append(rev)
214 todo.extend(p.rev() for p in ctx.parents())
215 todo.extend(p.rev() for p in ctx.parents())
215 continue
216 continue
216 latesttags[rev] = pdate, pdist + 1, ptag
217 latesttags[rev] = pdate, pdist + 1, ptag
217 return latesttags[rev]
218 return latesttags[rev]
218
219
219 def getrenamedfn(repo, endrev=None):
220 def getrenamedfn(repo, endrev=None):
220 rcache = {}
221 rcache = {}
221 if endrev is None:
222 if endrev is None:
222 endrev = len(repo)
223 endrev = len(repo)
223
224
224 def getrenamed(fn, rev):
225 def getrenamed(fn, rev):
225 '''looks up all renames for a file (up to endrev) the first
226 '''looks up all renames for a file (up to endrev) the first
226 time the file is given. It indexes on the changerev and only
227 time the file is given. It indexes on the changerev and only
227 parses the manifest if linkrev != changerev.
228 parses the manifest if linkrev != changerev.
228 Returns rename info for fn at changerev rev.'''
229 Returns rename info for fn at changerev rev.'''
229 if fn not in rcache:
230 if fn not in rcache:
230 rcache[fn] = {}
231 rcache[fn] = {}
231 fl = repo.file(fn)
232 fl = repo.file(fn)
232 for i in fl:
233 for i in fl:
233 lr = fl.linkrev(i)
234 lr = fl.linkrev(i)
234 renamed = fl.renamed(fl.node(i))
235 renamed = fl.renamed(fl.node(i))
235 rcache[fn][lr] = renamed
236 rcache[fn][lr] = renamed
236 if lr >= endrev:
237 if lr >= endrev:
237 break
238 break
238 if rev in rcache[fn]:
239 if rev in rcache[fn]:
239 return rcache[fn][rev]
240 return rcache[fn][rev]
240
241
241 # If linkrev != rev (i.e. rev not found in rcache) fallback to
242 # If linkrev != rev (i.e. rev not found in rcache) fallback to
242 # filectx logic.
243 # filectx logic.
243 try:
244 try:
244 return repo[rev][fn].renamed()
245 return repo[rev][fn].renamed()
245 except error.LookupError:
246 except error.LookupError:
246 return None
247 return None
247
248
248 return getrenamed
249 return getrenamed
249
250
250 # default templates internally used for rendering of lists
251 # default templates internally used for rendering of lists
251 defaulttempl = {
252 defaulttempl = {
252 'parent': '{rev}:{node|formatnode} ',
253 'parent': '{rev}:{node|formatnode} ',
253 'manifest': '{rev}:{node|formatnode}',
254 'manifest': '{rev}:{node|formatnode}',
254 'file_copy': '{name} ({source})',
255 'file_copy': '{name} ({source})',
255 'envvar': '{key}={value}',
256 'envvar': '{key}={value}',
256 'extra': '{key}={value|stringescape}'
257 'extra': '{key}={value|stringescape}'
257 }
258 }
258 # filecopy is preserved for compatibility reasons
259 # filecopy is preserved for compatibility reasons
259 defaulttempl['filecopy'] = defaulttempl['file_copy']
260 defaulttempl['filecopy'] = defaulttempl['file_copy']
260
261
261 # keywords are callables like:
262 # keywords are callables like:
262 # fn(repo, ctx, templ, cache, revcache, **args)
263 # fn(repo, ctx, templ, cache, revcache, **args)
263 # with:
264 # with:
264 # repo - current repository instance
265 # repo - current repository instance
265 # ctx - the changectx being displayed
266 # ctx - the changectx being displayed
266 # templ - the templater instance
267 # templ - the templater instance
267 # cache - a cache dictionary for the whole templater run
268 # cache - a cache dictionary for the whole templater run
268 # revcache - a cache dictionary for the current revision
269 # revcache - a cache dictionary for the current revision
269 keywords = {}
270 keywords = {}
270
271
271 templatekeyword = registrar.templatekeyword(keywords)
272 templatekeyword = registrar.templatekeyword(keywords)
272
273
273 @templatekeyword('author')
274 @templatekeyword('author')
274 def showauthor(repo, ctx, templ, **args):
275 def showauthor(repo, ctx, templ, **args):
275 """String. The unmodified author of the changeset."""
276 """String. The unmodified author of the changeset."""
276 return ctx.user()
277 return ctx.user()
277
278
278 @templatekeyword('bisect')
279 @templatekeyword('bisect')
279 def showbisect(repo, ctx, templ, **args):
280 def showbisect(repo, ctx, templ, **args):
280 """String. The changeset bisection status."""
281 """String. The changeset bisection status."""
281 return hbisect.label(repo, ctx.node())
282 return hbisect.label(repo, ctx.node())
282
283
283 @templatekeyword('branch')
284 @templatekeyword('branch')
284 def showbranch(**args):
285 def showbranch(**args):
285 """String. The name of the branch on which the changeset was
286 """String. The name of the branch on which the changeset was
286 committed.
287 committed.
287 """
288 """
288 return args['ctx'].branch()
289 return args['ctx'].branch()
289
290
290 @templatekeyword('branches')
291 @templatekeyword('branches')
291 def showbranches(**args):
292 def showbranches(**args):
292 """List of strings. The name of the branch on which the
293 """List of strings. The name of the branch on which the
293 changeset was committed. Will be empty if the branch name was
294 changeset was committed. Will be empty if the branch name was
294 default. (DEPRECATED)
295 default. (DEPRECATED)
295 """
296 """
297 args = pycompat.byteskwargs(args)
296 branch = args['ctx'].branch()
298 branch = args['ctx'].branch()
297 if branch != 'default':
299 if branch != 'default':
298 return showlist('branch', [branch], args, plural='branches')
300 return showlist('branch', [branch], args, plural='branches')
299 return showlist('branch', [], args, plural='branches')
301 return showlist('branch', [], args, plural='branches')
300
302
301 @templatekeyword('bookmarks')
303 @templatekeyword('bookmarks')
302 def showbookmarks(**args):
304 def showbookmarks(**args):
303 """List of strings. Any bookmarks associated with the
305 """List of strings. Any bookmarks associated with the
304 changeset. Also sets 'active', the name of the active bookmark.
306 changeset. Also sets 'active', the name of the active bookmark.
305 """
307 """
308 args = pycompat.byteskwargs(args)
306 repo = args['ctx']._repo
309 repo = args['ctx']._repo
307 bookmarks = args['ctx'].bookmarks()
310 bookmarks = args['ctx'].bookmarks()
308 active = repo._activebookmark
311 active = repo._activebookmark
309 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
312 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
310 f = _showlist('bookmark', bookmarks, args)
313 f = _showlist('bookmark', bookmarks, args)
311 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
314 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
312
315
313 @templatekeyword('children')
316 @templatekeyword('children')
314 def showchildren(**args):
317 def showchildren(**args):
315 """List of strings. The children of the changeset."""
318 """List of strings. The children of the changeset."""
319 args = pycompat.byteskwargs(args)
316 ctx = args['ctx']
320 ctx = args['ctx']
317 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
321 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
318 return showlist('children', childrevs, args, element='child')
322 return showlist('children', childrevs, args, element='child')
319
323
320 # Deprecated, but kept alive for help generation a purpose.
324 # Deprecated, but kept alive for help generation a purpose.
321 @templatekeyword('currentbookmark')
325 @templatekeyword('currentbookmark')
322 def showcurrentbookmark(**args):
326 def showcurrentbookmark(**args):
323 """String. The active bookmark, if it is
327 """String. The active bookmark, if it is
324 associated with the changeset (DEPRECATED)"""
328 associated with the changeset (DEPRECATED)"""
325 return showactivebookmark(**args)
329 return showactivebookmark(**args)
326
330
327 @templatekeyword('activebookmark')
331 @templatekeyword('activebookmark')
328 def showactivebookmark(**args):
332 def showactivebookmark(**args):
329 """String. The active bookmark, if it is
333 """String. The active bookmark, if it is
330 associated with the changeset"""
334 associated with the changeset"""
331 active = args['repo']._activebookmark
335 active = args['repo']._activebookmark
332 if active and active in args['ctx'].bookmarks():
336 if active and active in args['ctx'].bookmarks():
333 return active
337 return active
334 return ''
338 return ''
335
339
336 @templatekeyword('date')
340 @templatekeyword('date')
337 def showdate(repo, ctx, templ, **args):
341 def showdate(repo, ctx, templ, **args):
338 """Date information. The date when the changeset was committed."""
342 """Date information. The date when the changeset was committed."""
339 return ctx.date()
343 return ctx.date()
340
344
341 @templatekeyword('desc')
345 @templatekeyword('desc')
342 def showdescription(repo, ctx, templ, **args):
346 def showdescription(repo, ctx, templ, **args):
343 """String. The text of the changeset description."""
347 """String. The text of the changeset description."""
344 s = ctx.description()
348 s = ctx.description()
345 if isinstance(s, encoding.localstr):
349 if isinstance(s, encoding.localstr):
346 # try hard to preserve utf-8 bytes
350 # try hard to preserve utf-8 bytes
347 return encoding.tolocal(encoding.fromlocal(s).strip())
351 return encoding.tolocal(encoding.fromlocal(s).strip())
348 else:
352 else:
349 return s.strip()
353 return s.strip()
350
354
351 @templatekeyword('diffstat')
355 @templatekeyword('diffstat')
352 def showdiffstat(repo, ctx, templ, **args):
356 def showdiffstat(repo, ctx, templ, **args):
353 """String. Statistics of changes with the following format:
357 """String. Statistics of changes with the following format:
354 "modified files: +added/-removed lines"
358 "modified files: +added/-removed lines"
355 """
359 """
356 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
360 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
357 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
361 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
358 return '%s: +%s/-%s' % (len(stats), adds, removes)
362 return '%s: +%s/-%s' % (len(stats), adds, removes)
359
363
360 @templatekeyword('envvars')
364 @templatekeyword('envvars')
361 def showenvvars(repo, **args):
365 def showenvvars(repo, **args):
362 """A dictionary of environment variables. (EXPERIMENTAL)"""
366 """A dictionary of environment variables. (EXPERIMENTAL)"""
367 args = pycompat.byteskwargs(args)
363 env = repo.ui.exportableenviron()
368 env = repo.ui.exportableenviron()
364 env = util.sortdict((k, env[k]) for k in sorted(env))
369 env = util.sortdict((k, env[k]) for k in sorted(env))
365 return showdict('envvar', env, args, plural='envvars')
370 return showdict('envvar', env, args, plural='envvars')
366
371
367 @templatekeyword('extras')
372 @templatekeyword('extras')
368 def showextras(**args):
373 def showextras(**args):
369 """List of dicts with key, value entries of the 'extras'
374 """List of dicts with key, value entries of the 'extras'
370 field of this changeset."""
375 field of this changeset."""
376 args = pycompat.byteskwargs(args)
371 extras = args['ctx'].extra()
377 extras = args['ctx'].extra()
372 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
378 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
373 makemap = lambda k: {'key': k, 'value': extras[k]}
379 makemap = lambda k: {'key': k, 'value': extras[k]}
374 c = [makemap(k) for k in extras]
380 c = [makemap(k) for k in extras]
375 f = _showlist('extra', c, args, plural='extras')
381 f = _showlist('extra', c, args, plural='extras')
376 return _hybrid(f, extras, makemap,
382 return _hybrid(f, extras, makemap,
377 lambda x: '%s=%s' % (x['key'], util.escapestr(x['value'])))
383 lambda x: '%s=%s' % (x['key'], util.escapestr(x['value'])))
378
384
379 @templatekeyword('file_adds')
385 @templatekeyword('file_adds')
380 def showfileadds(**args):
386 def showfileadds(**args):
381 """List of strings. Files added by this changeset."""
387 """List of strings. Files added by this changeset."""
388 args = pycompat.byteskwargs(args)
382 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
389 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
383 return showlist('file_add', getfiles(repo, ctx, revcache)[1], args,
390 return showlist('file_add', getfiles(repo, ctx, revcache)[1], args,
384 element='file')
391 element='file')
385
392
386 @templatekeyword('file_copies')
393 @templatekeyword('file_copies')
387 def showfilecopies(**args):
394 def showfilecopies(**args):
388 """List of strings. Files copied in this changeset with
395 """List of strings. Files copied in this changeset with
389 their sources.
396 their sources.
390 """
397 """
398 args = pycompat.byteskwargs(args)
391 cache, ctx = args['cache'], args['ctx']
399 cache, ctx = args['cache'], args['ctx']
392 copies = args['revcache'].get('copies')
400 copies = args['revcache'].get('copies')
393 if copies is None:
401 if copies is None:
394 if 'getrenamed' not in cache:
402 if 'getrenamed' not in cache:
395 cache['getrenamed'] = getrenamedfn(args['repo'])
403 cache['getrenamed'] = getrenamedfn(args['repo'])
396 copies = []
404 copies = []
397 getrenamed = cache['getrenamed']
405 getrenamed = cache['getrenamed']
398 for fn in ctx.files():
406 for fn in ctx.files():
399 rename = getrenamed(fn, ctx.rev())
407 rename = getrenamed(fn, ctx.rev())
400 if rename:
408 if rename:
401 copies.append((fn, rename[0]))
409 copies.append((fn, rename[0]))
402
410
403 copies = util.sortdict(copies)
411 copies = util.sortdict(copies)
404 return showdict('file_copy', copies, args, plural='file_copies',
412 return showdict('file_copy', copies, args, plural='file_copies',
405 key='name', value='source', fmt='%s (%s)')
413 key='name', value='source', fmt='%s (%s)')
406
414
407 # showfilecopiesswitch() displays file copies only if copy records are
415 # showfilecopiesswitch() displays file copies only if copy records are
408 # provided before calling the templater, usually with a --copies
416 # provided before calling the templater, usually with a --copies
409 # command line switch.
417 # command line switch.
410 @templatekeyword('file_copies_switch')
418 @templatekeyword('file_copies_switch')
411 def showfilecopiesswitch(**args):
419 def showfilecopiesswitch(**args):
412 """List of strings. Like "file_copies" but displayed
420 """List of strings. Like "file_copies" but displayed
413 only if the --copied switch is set.
421 only if the --copied switch is set.
414 """
422 """
423 args = pycompat.byteskwargs(args)
415 copies = args['revcache'].get('copies') or []
424 copies = args['revcache'].get('copies') or []
416 copies = util.sortdict(copies)
425 copies = util.sortdict(copies)
417 return showdict('file_copy', copies, args, plural='file_copies',
426 return showdict('file_copy', copies, args, plural='file_copies',
418 key='name', value='source', fmt='%s (%s)')
427 key='name', value='source', fmt='%s (%s)')
419
428
420 @templatekeyword('file_dels')
429 @templatekeyword('file_dels')
421 def showfiledels(**args):
430 def showfiledels(**args):
422 """List of strings. Files removed by this changeset."""
431 """List of strings. Files removed by this changeset."""
432 args = pycompat.byteskwargs(args)
423 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
433 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
424 return showlist('file_del', getfiles(repo, ctx, revcache)[2], args,
434 return showlist('file_del', getfiles(repo, ctx, revcache)[2], args,
425 element='file')
435 element='file')
426
436
427 @templatekeyword('file_mods')
437 @templatekeyword('file_mods')
428 def showfilemods(**args):
438 def showfilemods(**args):
429 """List of strings. Files modified by this changeset."""
439 """List of strings. Files modified by this changeset."""
440 args = pycompat.byteskwargs(args)
430 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
441 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
431 return showlist('file_mod', getfiles(repo, ctx, revcache)[0], args,
442 return showlist('file_mod', getfiles(repo, ctx, revcache)[0], args,
432 element='file')
443 element='file')
433
444
434 @templatekeyword('files')
445 @templatekeyword('files')
435 def showfiles(**args):
446 def showfiles(**args):
436 """List of strings. All files modified, added, or removed by this
447 """List of strings. All files modified, added, or removed by this
437 changeset.
448 changeset.
438 """
449 """
450 args = pycompat.byteskwargs(args)
439 return showlist('file', args['ctx'].files(), args)
451 return showlist('file', args['ctx'].files(), args)
440
452
441 @templatekeyword('graphnode')
453 @templatekeyword('graphnode')
442 def showgraphnode(repo, ctx, **args):
454 def showgraphnode(repo, ctx, **args):
443 """String. The character representing the changeset node in
455 """String. The character representing the changeset node in
444 an ASCII revision graph"""
456 an ASCII revision graph"""
445 wpnodes = repo.dirstate.parents()
457 wpnodes = repo.dirstate.parents()
446 if wpnodes[1] == nullid:
458 if wpnodes[1] == nullid:
447 wpnodes = wpnodes[:1]
459 wpnodes = wpnodes[:1]
448 if ctx.node() in wpnodes:
460 if ctx.node() in wpnodes:
449 return '@'
461 return '@'
450 elif ctx.obsolete():
462 elif ctx.obsolete():
451 return 'x'
463 return 'x'
452 elif ctx.closesbranch():
464 elif ctx.closesbranch():
453 return '_'
465 return '_'
454 else:
466 else:
455 return 'o'
467 return 'o'
456
468
457 @templatekeyword('index')
469 @templatekeyword('index')
458 def showindex(**args):
470 def showindex(**args):
459 """Integer. The current iteration of the loop. (0 indexed)"""
471 """Integer. The current iteration of the loop. (0 indexed)"""
460 # just hosts documentation; should be overridden by template mapping
472 # just hosts documentation; should be overridden by template mapping
461 raise error.Abort(_("can't use index in this context"))
473 raise error.Abort(_("can't use index in this context"))
462
474
463 @templatekeyword('latesttag')
475 @templatekeyword('latesttag')
464 def showlatesttag(**args):
476 def showlatesttag(**args):
465 """List of strings. The global tags on the most recent globally
477 """List of strings. The global tags on the most recent globally
466 tagged ancestor of this changeset. If no such tags exist, the list
478 tagged ancestor of this changeset. If no such tags exist, the list
467 consists of the single string "null".
479 consists of the single string "null".
468 """
480 """
469 return showlatesttags(None, **args)
481 return showlatesttags(None, **args)
470
482
471 def showlatesttags(pattern, **args):
483 def showlatesttags(pattern, **args):
472 """helper method for the latesttag keyword and function"""
484 """helper method for the latesttag keyword and function"""
485 args = pycompat.byteskwargs(args)
473 repo, ctx = args['repo'], args['ctx']
486 repo, ctx = args['repo'], args['ctx']
474 cache = args['cache']
487 cache = args['cache']
475 latesttags = getlatesttags(repo, ctx, cache, pattern)
488 latesttags = getlatesttags(repo, ctx, cache, pattern)
476
489
477 # latesttag[0] is an implementation detail for sorting csets on different
490 # latesttag[0] is an implementation detail for sorting csets on different
478 # branches in a stable manner- it is the date the tagged cset was created,
491 # branches in a stable manner- it is the date the tagged cset was created,
479 # not the date the tag was created. Therefore it isn't made visible here.
492 # not the date the tag was created. Therefore it isn't made visible here.
480 makemap = lambda v: {
493 makemap = lambda v: {
481 'changes': _showchangessincetag,
494 'changes': _showchangessincetag,
482 'distance': latesttags[1],
495 'distance': latesttags[1],
483 'latesttag': v, # BC with {latesttag % '{latesttag}'}
496 'latesttag': v, # BC with {latesttag % '{latesttag}'}
484 'tag': v
497 'tag': v
485 }
498 }
486
499
487 tags = latesttags[2]
500 tags = latesttags[2]
488 f = _showlist('latesttag', tags, args, separator=':')
501 f = _showlist('latesttag', tags, args, separator=':')
489 return _hybrid(f, tags, makemap, lambda x: x['latesttag'])
502 return _hybrid(f, tags, makemap, lambda x: x['latesttag'])
490
503
491 @templatekeyword('latesttagdistance')
504 @templatekeyword('latesttagdistance')
492 def showlatesttagdistance(repo, ctx, templ, cache, **args):
505 def showlatesttagdistance(repo, ctx, templ, cache, **args):
493 """Integer. Longest path to the latest tag."""
506 """Integer. Longest path to the latest tag."""
494 return getlatesttags(repo, ctx, cache)[1]
507 return getlatesttags(repo, ctx, cache)[1]
495
508
496 @templatekeyword('changessincelatesttag')
509 @templatekeyword('changessincelatesttag')
497 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
510 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
498 """Integer. All ancestors not in the latest tag."""
511 """Integer. All ancestors not in the latest tag."""
499 latesttag = getlatesttags(repo, ctx, cache)[2][0]
512 latesttag = getlatesttags(repo, ctx, cache)[2][0]
500
513
501 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
514 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
502
515
503 def _showchangessincetag(repo, ctx, **args):
516 def _showchangessincetag(repo, ctx, **args):
504 offset = 0
517 offset = 0
505 revs = [ctx.rev()]
518 revs = [ctx.rev()]
506 tag = args['tag']
519 tag = args['tag']
507
520
508 # The only() revset doesn't currently support wdir()
521 # The only() revset doesn't currently support wdir()
509 if ctx.rev() is None:
522 if ctx.rev() is None:
510 offset = 1
523 offset = 1
511 revs = [p.rev() for p in ctx.parents()]
524 revs = [p.rev() for p in ctx.parents()]
512
525
513 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
526 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
514
527
515 @templatekeyword('manifest')
528 @templatekeyword('manifest')
516 def showmanifest(**args):
529 def showmanifest(**args):
517 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
530 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
518 mnode = ctx.manifestnode()
531 mnode = ctx.manifestnode()
519 if mnode is None:
532 if mnode is None:
520 # just avoid crash, we might want to use the 'ff...' hash in future
533 # just avoid crash, we might want to use the 'ff...' hash in future
521 return
534 return
522 args = args.copy()
535 args = args.copy()
523 args.update({'rev': repo.manifestlog._revlog.rev(mnode),
536 args.update({'rev': repo.manifestlog._revlog.rev(mnode),
524 'node': hex(mnode)})
537 'node': hex(mnode)})
525 return templ('manifest', **args)
538 return templ('manifest', **args)
526
539
527 def shownames(namespace, **args):
540 def shownames(namespace, **args):
528 """helper method to generate a template keyword for a namespace"""
541 """helper method to generate a template keyword for a namespace"""
542 args = pycompat.byteskwargs(args)
529 ctx = args['ctx']
543 ctx = args['ctx']
530 repo = ctx.repo()
544 repo = ctx.repo()
531 ns = repo.names[namespace]
545 ns = repo.names[namespace]
532 names = ns.names(repo, ctx.node())
546 names = ns.names(repo, ctx.node())
533 return showlist(ns.templatename, names, args, plural=namespace)
547 return showlist(ns.templatename, names, args, plural=namespace)
534
548
535 @templatekeyword('namespaces')
549 @templatekeyword('namespaces')
536 def shownamespaces(**args):
550 def shownamespaces(**args):
537 """Dict of lists. Names attached to this changeset per
551 """Dict of lists. Names attached to this changeset per
538 namespace."""
552 namespace."""
553 args = pycompat.byteskwargs(args)
539 ctx = args['ctx']
554 ctx = args['ctx']
540 repo = ctx.repo()
555 repo = ctx.repo()
541 namespaces = util.sortdict((k, showlist('name', ns.names(repo, ctx.node()),
556 namespaces = util.sortdict((k, showlist('name', ns.names(repo, ctx.node()),
542 args))
557 args))
543 for k, ns in repo.names.iteritems())
558 for k, ns in repo.names.iteritems())
544 f = _showlist('namespace', list(namespaces), args)
559 f = _showlist('namespace', list(namespaces), args)
545 return _hybrid(f, namespaces,
560 return _hybrid(f, namespaces,
546 lambda k: {'namespace': k, 'names': namespaces[k]},
561 lambda k: {'namespace': k, 'names': namespaces[k]},
547 lambda x: x['namespace'])
562 lambda x: x['namespace'])
548
563
549 @templatekeyword('node')
564 @templatekeyword('node')
550 def shownode(repo, ctx, templ, **args):
565 def shownode(repo, ctx, templ, **args):
551 """String. The changeset identification hash, as a 40 hexadecimal
566 """String. The changeset identification hash, as a 40 hexadecimal
552 digit string.
567 digit string.
553 """
568 """
554 return ctx.hex()
569 return ctx.hex()
555
570
556 @templatekeyword('obsolete')
571 @templatekeyword('obsolete')
557 def showobsolete(repo, ctx, templ, **args):
572 def showobsolete(repo, ctx, templ, **args):
558 """String. Whether the changeset is obsolete.
573 """String. Whether the changeset is obsolete.
559 """
574 """
560 if ctx.obsolete():
575 if ctx.obsolete():
561 return 'obsolete'
576 return 'obsolete'
562 return ''
577 return ''
563
578
564 @templatekeyword("predecessors")
579 @templatekeyword("predecessors")
565 def showpredecessors(repo, ctx, **args):
580 def showpredecessors(repo, ctx, **args):
566 """Returns the list if the closest visible successors
581 """Returns the list if the closest visible successors
567 """
582 """
568 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
583 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
569 predecessors = map(hex, predecessors)
584 predecessors = map(hex, predecessors)
570
585
571 return _hybrid(None, predecessors,
586 return _hybrid(None, predecessors,
572 lambda x: {'ctx': repo[x], 'revcache': {}},
587 lambda x: {'ctx': repo[x], 'revcache': {}},
573 lambda d: _formatrevnode(d['ctx']))
588 lambda d: _formatrevnode(d['ctx']))
574
589
575 @templatekeyword('p1rev')
590 @templatekeyword('p1rev')
576 def showp1rev(repo, ctx, templ, **args):
591 def showp1rev(repo, ctx, templ, **args):
577 """Integer. The repository-local revision number of the changeset's
592 """Integer. The repository-local revision number of the changeset's
578 first parent, or -1 if the changeset has no parents."""
593 first parent, or -1 if the changeset has no parents."""
579 return ctx.p1().rev()
594 return ctx.p1().rev()
580
595
581 @templatekeyword('p2rev')
596 @templatekeyword('p2rev')
582 def showp2rev(repo, ctx, templ, **args):
597 def showp2rev(repo, ctx, templ, **args):
583 """Integer. The repository-local revision number of the changeset's
598 """Integer. The repository-local revision number of the changeset's
584 second parent, or -1 if the changeset has no second parent."""
599 second parent, or -1 if the changeset has no second parent."""
585 return ctx.p2().rev()
600 return ctx.p2().rev()
586
601
587 @templatekeyword('p1node')
602 @templatekeyword('p1node')
588 def showp1node(repo, ctx, templ, **args):
603 def showp1node(repo, ctx, templ, **args):
589 """String. The identification hash of the changeset's first parent,
604 """String. The identification hash of the changeset's first parent,
590 as a 40 digit hexadecimal string. If the changeset has no parents, all
605 as a 40 digit hexadecimal string. If the changeset has no parents, all
591 digits are 0."""
606 digits are 0."""
592 return ctx.p1().hex()
607 return ctx.p1().hex()
593
608
594 @templatekeyword('p2node')
609 @templatekeyword('p2node')
595 def showp2node(repo, ctx, templ, **args):
610 def showp2node(repo, ctx, templ, **args):
596 """String. The identification hash of the changeset's second
611 """String. The identification hash of the changeset's second
597 parent, as a 40 digit hexadecimal string. If the changeset has no second
612 parent, as a 40 digit hexadecimal string. If the changeset has no second
598 parent, all digits are 0."""
613 parent, all digits are 0."""
599 return ctx.p2().hex()
614 return ctx.p2().hex()
600
615
601 @templatekeyword('parents')
616 @templatekeyword('parents')
602 def showparents(**args):
617 def showparents(**args):
603 """List of strings. The parents of the changeset in "rev:node"
618 """List of strings. The parents of the changeset in "rev:node"
604 format. If the changeset has only one "natural" parent (the predecessor
619 format. If the changeset has only one "natural" parent (the predecessor
605 revision) nothing is shown."""
620 revision) nothing is shown."""
621 args = pycompat.byteskwargs(args)
606 repo = args['repo']
622 repo = args['repo']
607 ctx = args['ctx']
623 ctx = args['ctx']
608 pctxs = scmutil.meaningfulparents(repo, ctx)
624 pctxs = scmutil.meaningfulparents(repo, ctx)
609 prevs = [str(p.rev()) for p in pctxs] # ifcontains() needs a list of str
625 prevs = [str(p.rev()) for p in pctxs] # ifcontains() needs a list of str
610 parents = [[('rev', p.rev()),
626 parents = [[('rev', p.rev()),
611 ('node', p.hex()),
627 ('node', p.hex()),
612 ('phase', p.phasestr())]
628 ('phase', p.phasestr())]
613 for p in pctxs]
629 for p in pctxs]
614 f = _showlist('parent', parents, args)
630 f = _showlist('parent', parents, args)
615 return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
631 return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
616 lambda d: _formatrevnode(d['ctx']))
632 lambda d: _formatrevnode(d['ctx']))
617
633
618 @templatekeyword('phase')
634 @templatekeyword('phase')
619 def showphase(repo, ctx, templ, **args):
635 def showphase(repo, ctx, templ, **args):
620 """String. The changeset phase name."""
636 """String. The changeset phase name."""
621 return ctx.phasestr()
637 return ctx.phasestr()
622
638
623 @templatekeyword('phaseidx')
639 @templatekeyword('phaseidx')
624 def showphaseidx(repo, ctx, templ, **args):
640 def showphaseidx(repo, ctx, templ, **args):
625 """Integer. The changeset phase index."""
641 """Integer. The changeset phase index."""
626 return ctx.phase()
642 return ctx.phase()
627
643
628 @templatekeyword('rev')
644 @templatekeyword('rev')
629 def showrev(repo, ctx, templ, **args):
645 def showrev(repo, ctx, templ, **args):
630 """Integer. The repository-local changeset revision number."""
646 """Integer. The repository-local changeset revision number."""
631 return scmutil.intrev(ctx)
647 return scmutil.intrev(ctx)
632
648
633 def showrevslist(name, revs, **args):
649 def showrevslist(name, revs, **args):
634 """helper to generate a list of revisions in which a mapped template will
650 """helper to generate a list of revisions in which a mapped template will
635 be evaluated"""
651 be evaluated"""
652 args = pycompat.byteskwargs(args)
636 repo = args['ctx'].repo()
653 repo = args['ctx'].repo()
637 revs = [str(r) for r in revs] # ifcontains() needs a list of str
654 revs = [str(r) for r in revs] # ifcontains() needs a list of str
638 f = _showlist(name, revs, args)
655 f = _showlist(name, revs, args)
639 return _hybrid(f, revs,
656 return _hybrid(f, revs,
640 lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}},
657 lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}},
641 lambda d: d[name])
658 lambda d: d[name])
642
659
643 @templatekeyword('subrepos')
660 @templatekeyword('subrepos')
644 def showsubrepos(**args):
661 def showsubrepos(**args):
645 """List of strings. Updated subrepositories in the changeset."""
662 """List of strings. Updated subrepositories in the changeset."""
663 args = pycompat.byteskwargs(args)
646 ctx = args['ctx']
664 ctx = args['ctx']
647 substate = ctx.substate
665 substate = ctx.substate
648 if not substate:
666 if not substate:
649 return showlist('subrepo', [], args)
667 return showlist('subrepo', [], args)
650 psubstate = ctx.parents()[0].substate or {}
668 psubstate = ctx.parents()[0].substate or {}
651 subrepos = []
669 subrepos = []
652 for sub in substate:
670 for sub in substate:
653 if sub not in psubstate or substate[sub] != psubstate[sub]:
671 if sub not in psubstate or substate[sub] != psubstate[sub]:
654 subrepos.append(sub) # modified or newly added in ctx
672 subrepos.append(sub) # modified or newly added in ctx
655 for sub in psubstate:
673 for sub in psubstate:
656 if sub not in substate:
674 if sub not in substate:
657 subrepos.append(sub) # removed in ctx
675 subrepos.append(sub) # removed in ctx
658 return showlist('subrepo', sorted(subrepos), args)
676 return showlist('subrepo', sorted(subrepos), args)
659
677
660 # don't remove "showtags" definition, even though namespaces will put
678 # don't remove "showtags" definition, even though namespaces will put
661 # a helper function for "tags" keyword into "keywords" map automatically,
679 # a helper function for "tags" keyword into "keywords" map automatically,
662 # because online help text is built without namespaces initialization
680 # because online help text is built without namespaces initialization
663 @templatekeyword('tags')
681 @templatekeyword('tags')
664 def showtags(**args):
682 def showtags(**args):
665 """List of strings. Any tags associated with the changeset."""
683 """List of strings. Any tags associated with the changeset."""
666 return shownames('tags', **args)
684 return shownames('tags', **args)
667
685
668 def loadkeyword(ui, extname, registrarobj):
686 def loadkeyword(ui, extname, registrarobj):
669 """Load template keyword from specified registrarobj
687 """Load template keyword from specified registrarobj
670 """
688 """
671 for name, func in registrarobj._table.iteritems():
689 for name, func in registrarobj._table.iteritems():
672 keywords[name] = func
690 keywords[name] = func
673
691
674 @templatekeyword('termwidth')
692 @templatekeyword('termwidth')
675 def termwidth(repo, ctx, templ, **args):
693 def termwidth(repo, ctx, templ, **args):
676 """Integer. The width of the current terminal."""
694 """Integer. The width of the current terminal."""
677 return repo.ui.termwidth()
695 return repo.ui.termwidth()
678
696
679 @templatekeyword('troubles')
697 @templatekeyword('troubles')
680 def showtroubles(**args):
698 def showtroubles(**args):
681 """List of strings. Evolution troubles affecting the changeset.
699 """List of strings. Evolution troubles affecting the changeset.
682
700
683 (EXPERIMENTAL)
701 (EXPERIMENTAL)
684 """
702 """
703 args = pycompat.byteskwargs(args)
685 return showlist('trouble', args['ctx'].troubles(), args)
704 return showlist('trouble', args['ctx'].troubles(), args)
686
705
687 # tell hggettext to extract docstrings from these functions:
706 # tell hggettext to extract docstrings from these functions:
688 i18nfunctions = keywords.values()
707 i18nfunctions = keywords.values()
General Comments 0
You need to be logged in to leave comments. Login now