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