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