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