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