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