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