##// END OF EJS Templates
templatekw: add option to include ignored/clean/unknown files in cache...
Yuya Nishihara -
r39635:a5da9063 default
parent child Browse files
Show More
@@ -1,831 +1,834 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 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 _getfilestatus(context, mapping):
294 def _getfilestatus(context, mapping, listall=False):
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 'filestatus' not in revcache:
297 if 'filestatus' not in revcache or revcache['filestatusall'] < listall:
298 revcache['filestatus'] = ctx.p1().status(ctx)
298 stat = ctx.p1().status(ctx, listignored=listall, listclean=listall,
299 listunknown=listall)
300 revcache['filestatus'] = stat
301 revcache['filestatusall'] = listall
299 return revcache['filestatus']
302 return revcache['filestatus']
300
303
301 def _showfilesbystat(context, mapping, name, index):
304 def _showfilesbystat(context, mapping, name, index):
302 stat = _getfilestatus(context, mapping)
305 stat = _getfilestatus(context, mapping)
303 files = stat[index]
306 files = stat[index]
304 return templateutil.compatfileslist(context, mapping, name, files)
307 return templateutil.compatfileslist(context, mapping, name, files)
305
308
306 @templatekeyword('file_adds', requires={'ctx', 'revcache'})
309 @templatekeyword('file_adds', requires={'ctx', 'revcache'})
307 def showfileadds(context, mapping):
310 def showfileadds(context, mapping):
308 """List of strings. Files added by this changeset."""
311 """List of strings. Files added by this changeset."""
309 return _showfilesbystat(context, mapping, 'file_add', 1)
312 return _showfilesbystat(context, mapping, 'file_add', 1)
310
313
311 @templatekeyword('file_copies',
314 @templatekeyword('file_copies',
312 requires={'repo', 'ctx', 'cache', 'revcache'})
315 requires={'repo', 'ctx', 'cache', 'revcache'})
313 def showfilecopies(context, mapping):
316 def showfilecopies(context, mapping):
314 """List of strings. Files copied in this changeset with
317 """List of strings. Files copied in this changeset with
315 their sources.
318 their sources.
316 """
319 """
317 repo = context.resource(mapping, 'repo')
320 repo = context.resource(mapping, 'repo')
318 ctx = context.resource(mapping, 'ctx')
321 ctx = context.resource(mapping, 'ctx')
319 cache = context.resource(mapping, 'cache')
322 cache = context.resource(mapping, 'cache')
320 copies = context.resource(mapping, 'revcache').get('copies')
323 copies = context.resource(mapping, 'revcache').get('copies')
321 if copies is None:
324 if copies is None:
322 if 'getrenamed' not in cache:
325 if 'getrenamed' not in cache:
323 cache['getrenamed'] = getrenamedfn(repo)
326 cache['getrenamed'] = getrenamedfn(repo)
324 copies = []
327 copies = []
325 getrenamed = cache['getrenamed']
328 getrenamed = cache['getrenamed']
326 for fn in ctx.files():
329 for fn in ctx.files():
327 rename = getrenamed(fn, ctx.rev())
330 rename = getrenamed(fn, ctx.rev())
328 if rename:
331 if rename:
329 copies.append((fn, rename))
332 copies.append((fn, rename))
330 return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
333 return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
331 copies)
334 copies)
332
335
333 # showfilecopiesswitch() displays file copies only if copy records are
336 # showfilecopiesswitch() displays file copies only if copy records are
334 # provided before calling the templater, usually with a --copies
337 # provided before calling the templater, usually with a --copies
335 # command line switch.
338 # command line switch.
336 @templatekeyword('file_copies_switch', requires={'revcache'})
339 @templatekeyword('file_copies_switch', requires={'revcache'})
337 def showfilecopiesswitch(context, mapping):
340 def showfilecopiesswitch(context, mapping):
338 """List of strings. Like "file_copies" but displayed
341 """List of strings. Like "file_copies" but displayed
339 only if the --copied switch is set.
342 only if the --copied switch is set.
340 """
343 """
341 copies = context.resource(mapping, 'revcache').get('copies') or []
344 copies = context.resource(mapping, 'revcache').get('copies') or []
342 return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
345 return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
343 copies)
346 copies)
344
347
345 @templatekeyword('file_dels', requires={'ctx', 'revcache'})
348 @templatekeyword('file_dels', requires={'ctx', 'revcache'})
346 def showfiledels(context, mapping):
349 def showfiledels(context, mapping):
347 """List of strings. Files removed by this changeset."""
350 """List of strings. Files removed by this changeset."""
348 return _showfilesbystat(context, mapping, 'file_del', 2)
351 return _showfilesbystat(context, mapping, 'file_del', 2)
349
352
350 @templatekeyword('file_mods', requires={'ctx', 'revcache'})
353 @templatekeyword('file_mods', requires={'ctx', 'revcache'})
351 def showfilemods(context, mapping):
354 def showfilemods(context, mapping):
352 """List of strings. Files modified by this changeset."""
355 """List of strings. Files modified by this changeset."""
353 return _showfilesbystat(context, mapping, 'file_mod', 0)
356 return _showfilesbystat(context, mapping, 'file_mod', 0)
354
357
355 @templatekeyword('files', requires={'ctx'})
358 @templatekeyword('files', requires={'ctx'})
356 def showfiles(context, mapping):
359 def showfiles(context, mapping):
357 """List of strings. All files modified, added, or removed by this
360 """List of strings. All files modified, added, or removed by this
358 changeset.
361 changeset.
359 """
362 """
360 ctx = context.resource(mapping, 'ctx')
363 ctx = context.resource(mapping, 'ctx')
361 return templateutil.compatfileslist(context, mapping, 'file', ctx.files())
364 return templateutil.compatfileslist(context, mapping, 'file', ctx.files())
362
365
363 @templatekeyword('graphnode', requires={'repo', 'ctx'})
366 @templatekeyword('graphnode', requires={'repo', 'ctx'})
364 def showgraphnode(context, mapping):
367 def showgraphnode(context, mapping):
365 """String. The character representing the changeset node in an ASCII
368 """String. The character representing the changeset node in an ASCII
366 revision graph."""
369 revision graph."""
367 repo = context.resource(mapping, 'repo')
370 repo = context.resource(mapping, 'repo')
368 ctx = context.resource(mapping, 'ctx')
371 ctx = context.resource(mapping, 'ctx')
369 return getgraphnode(repo, ctx)
372 return getgraphnode(repo, ctx)
370
373
371 def getgraphnode(repo, ctx):
374 def getgraphnode(repo, ctx):
372 return getgraphnodecurrent(repo, ctx) or getgraphnodesymbol(ctx)
375 return getgraphnodecurrent(repo, ctx) or getgraphnodesymbol(ctx)
373
376
374 def getgraphnodecurrent(repo, ctx):
377 def getgraphnodecurrent(repo, ctx):
375 wpnodes = repo.dirstate.parents()
378 wpnodes = repo.dirstate.parents()
376 if wpnodes[1] == nullid:
379 if wpnodes[1] == nullid:
377 wpnodes = wpnodes[:1]
380 wpnodes = wpnodes[:1]
378 if ctx.node() in wpnodes:
381 if ctx.node() in wpnodes:
379 return '@'
382 return '@'
380 else:
383 else:
381 return ''
384 return ''
382
385
383 def getgraphnodesymbol(ctx):
386 def getgraphnodesymbol(ctx):
384 if ctx.obsolete():
387 if ctx.obsolete():
385 return 'x'
388 return 'x'
386 elif ctx.isunstable():
389 elif ctx.isunstable():
387 return '*'
390 return '*'
388 elif ctx.closesbranch():
391 elif ctx.closesbranch():
389 return '_'
392 return '_'
390 else:
393 else:
391 return 'o'
394 return 'o'
392
395
393 @templatekeyword('graphwidth', requires=())
396 @templatekeyword('graphwidth', requires=())
394 def showgraphwidth(context, mapping):
397 def showgraphwidth(context, mapping):
395 """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."""
396 # just hosts documentation; should be overridden by template mapping
399 # just hosts documentation; should be overridden by template mapping
397 return 0
400 return 0
398
401
399 @templatekeyword('index', requires=())
402 @templatekeyword('index', requires=())
400 def showindex(context, mapping):
403 def showindex(context, mapping):
401 """Integer. The current iteration of the loop. (0 indexed)"""
404 """Integer. The current iteration of the loop. (0 indexed)"""
402 # just hosts documentation; should be overridden by template mapping
405 # just hosts documentation; should be overridden by template mapping
403 raise error.Abort(_("can't use index in this context"))
406 raise error.Abort(_("can't use index in this context"))
404
407
405 @templatekeyword('latesttag', requires={'repo', 'ctx', 'cache'})
408 @templatekeyword('latesttag', requires={'repo', 'ctx', 'cache'})
406 def showlatesttag(context, mapping):
409 def showlatesttag(context, mapping):
407 """List of strings. The global tags on the most recent globally
410 """List of strings. The global tags on the most recent globally
408 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
409 consists of the single string "null".
412 consists of the single string "null".
410 """
413 """
411 return showlatesttags(context, mapping, None)
414 return showlatesttags(context, mapping, None)
412
415
413 def showlatesttags(context, mapping, pattern):
416 def showlatesttags(context, mapping, pattern):
414 """helper method for the latesttag keyword and function"""
417 """helper method for the latesttag keyword and function"""
415 latesttags = getlatesttags(context, mapping, pattern)
418 latesttags = getlatesttags(context, mapping, pattern)
416
419
417 # latesttag[0] is an implementation detail for sorting csets on different
420 # latesttag[0] is an implementation detail for sorting csets on different
418 # branches in a stable manner- it is the date the tagged cset was created,
421 # branches in a stable manner- it is the date the tagged cset was created,
419 # not the date the tag was created. Therefore it isn't made visible here.
422 # not the date the tag was created. Therefore it isn't made visible here.
420 makemap = lambda v: {
423 makemap = lambda v: {
421 'changes': _showchangessincetag,
424 'changes': _showchangessincetag,
422 'distance': latesttags[1],
425 'distance': latesttags[1],
423 'latesttag': v, # BC with {latesttag % '{latesttag}'}
426 'latesttag': v, # BC with {latesttag % '{latesttag}'}
424 'tag': v
427 'tag': v
425 }
428 }
426
429
427 tags = latesttags[2]
430 tags = latesttags[2]
428 f = _showcompatlist(context, mapping, 'latesttag', tags, separator=':')
431 f = _showcompatlist(context, mapping, 'latesttag', tags, separator=':')
429 return _hybrid(f, tags, makemap, pycompat.identity)
432 return _hybrid(f, tags, makemap, pycompat.identity)
430
433
431 @templatekeyword('latesttagdistance', requires={'repo', 'ctx', 'cache'})
434 @templatekeyword('latesttagdistance', requires={'repo', 'ctx', 'cache'})
432 def showlatesttagdistance(context, mapping):
435 def showlatesttagdistance(context, mapping):
433 """Integer. Longest path to the latest tag."""
436 """Integer. Longest path to the latest tag."""
434 return getlatesttags(context, mapping)[1]
437 return getlatesttags(context, mapping)[1]
435
438
436 @templatekeyword('changessincelatesttag', requires={'repo', 'ctx', 'cache'})
439 @templatekeyword('changessincelatesttag', requires={'repo', 'ctx', 'cache'})
437 def showchangessincelatesttag(context, mapping):
440 def showchangessincelatesttag(context, mapping):
438 """Integer. All ancestors not in the latest tag."""
441 """Integer. All ancestors not in the latest tag."""
439 tag = getlatesttags(context, mapping)[2][0]
442 tag = getlatesttags(context, mapping)[2][0]
440 mapping = context.overlaymap(mapping, {'tag': tag})
443 mapping = context.overlaymap(mapping, {'tag': tag})
441 return _showchangessincetag(context, mapping)
444 return _showchangessincetag(context, mapping)
442
445
443 def _showchangessincetag(context, mapping):
446 def _showchangessincetag(context, mapping):
444 repo = context.resource(mapping, 'repo')
447 repo = context.resource(mapping, 'repo')
445 ctx = context.resource(mapping, 'ctx')
448 ctx = context.resource(mapping, 'ctx')
446 offset = 0
449 offset = 0
447 revs = [ctx.rev()]
450 revs = [ctx.rev()]
448 tag = context.symbol(mapping, 'tag')
451 tag = context.symbol(mapping, 'tag')
449
452
450 # The only() revset doesn't currently support wdir()
453 # The only() revset doesn't currently support wdir()
451 if ctx.rev() is None:
454 if ctx.rev() is None:
452 offset = 1
455 offset = 1
453 revs = [p.rev() for p in ctx.parents()]
456 revs = [p.rev() for p in ctx.parents()]
454
457
455 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
458 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
456
459
457 # teach templater latesttags.changes is switched to (context, mapping) API
460 # teach templater latesttags.changes is switched to (context, mapping) API
458 _showchangessincetag._requires = {'repo', 'ctx'}
461 _showchangessincetag._requires = {'repo', 'ctx'}
459
462
460 @templatekeyword('manifest', requires={'repo', 'ctx'})
463 @templatekeyword('manifest', requires={'repo', 'ctx'})
461 def showmanifest(context, mapping):
464 def showmanifest(context, mapping):
462 repo = context.resource(mapping, 'repo')
465 repo = context.resource(mapping, 'repo')
463 ctx = context.resource(mapping, 'ctx')
466 ctx = context.resource(mapping, 'ctx')
464 mnode = ctx.manifestnode()
467 mnode = ctx.manifestnode()
465 if mnode is None:
468 if mnode is None:
466 # 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
467 return
470 return
468 mrev = repo.manifestlog.rev(mnode)
471 mrev = repo.manifestlog.rev(mnode)
469 mhex = hex(mnode)
472 mhex = hex(mnode)
470 mapping = context.overlaymap(mapping, {'rev': mrev, 'node': mhex})
473 mapping = context.overlaymap(mapping, {'rev': mrev, 'node': mhex})
471 f = context.process('manifest', mapping)
474 f = context.process('manifest', mapping)
472 # TODO: perhaps 'ctx' should be dropped from mapping because manifest
475 # TODO: perhaps 'ctx' should be dropped from mapping because manifest
473 # rev and node are completely different from changeset's.
476 # rev and node are completely different from changeset's.
474 return templateutil.hybriditem(f, None, f,
477 return templateutil.hybriditem(f, None, f,
475 lambda x: {'rev': mrev, 'node': mhex})
478 lambda x: {'rev': mrev, 'node': mhex})
476
479
477 @templatekeyword('obsfate', requires={'ui', 'repo', 'ctx'})
480 @templatekeyword('obsfate', requires={'ui', 'repo', 'ctx'})
478 def showobsfate(context, mapping):
481 def showobsfate(context, mapping):
479 # this function returns a list containing pre-formatted obsfate strings.
482 # this function returns a list containing pre-formatted obsfate strings.
480 #
483 #
481 # 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
482 # the verbosity templatekw available.
485 # the verbosity templatekw available.
483 succsandmarkers = showsuccsandmarkers(context, mapping)
486 succsandmarkers = showsuccsandmarkers(context, mapping)
484
487
485 ui = context.resource(mapping, 'ui')
488 ui = context.resource(mapping, 'ui')
486 repo = context.resource(mapping, 'repo')
489 repo = context.resource(mapping, 'repo')
487 values = []
490 values = []
488
491
489 for x in succsandmarkers.tovalue(context, mapping):
492 for x in succsandmarkers.tovalue(context, mapping):
490 v = obsutil.obsfateprinter(ui, repo, x['successors'], x['markers'],
493 v = obsutil.obsfateprinter(ui, repo, x['successors'], x['markers'],
491 scmutil.formatchangeid)
494 scmutil.formatchangeid)
492 values.append(v)
495 values.append(v)
493
496
494 return compatlist(context, mapping, "fate", values)
497 return compatlist(context, mapping, "fate", values)
495
498
496 def shownames(context, mapping, namespace):
499 def shownames(context, mapping, namespace):
497 """helper method to generate a template keyword for a namespace"""
500 """helper method to generate a template keyword for a namespace"""
498 repo = context.resource(mapping, 'repo')
501 repo = context.resource(mapping, 'repo')
499 ctx = context.resource(mapping, 'ctx')
502 ctx = context.resource(mapping, 'ctx')
500 ns = repo.names[namespace]
503 ns = repo.names[namespace]
501 names = ns.names(repo, ctx.node())
504 names = ns.names(repo, ctx.node())
502 return compatlist(context, mapping, ns.templatename, names,
505 return compatlist(context, mapping, ns.templatename, names,
503 plural=namespace)
506 plural=namespace)
504
507
505 @templatekeyword('namespaces', requires={'repo', 'ctx'})
508 @templatekeyword('namespaces', requires={'repo', 'ctx'})
506 def shownamespaces(context, mapping):
509 def shownamespaces(context, mapping):
507 """Dict of lists. Names attached to this changeset per
510 """Dict of lists. Names attached to this changeset per
508 namespace."""
511 namespace."""
509 repo = context.resource(mapping, 'repo')
512 repo = context.resource(mapping, 'repo')
510 ctx = context.resource(mapping, 'ctx')
513 ctx = context.resource(mapping, 'ctx')
511
514
512 namespaces = util.sortdict()
515 namespaces = util.sortdict()
513 def makensmapfn(ns):
516 def makensmapfn(ns):
514 # 'name' for iterating over namespaces, templatename for local reference
517 # 'name' for iterating over namespaces, templatename for local reference
515 return lambda v: {'name': v, ns.templatename: v}
518 return lambda v: {'name': v, ns.templatename: v}
516
519
517 for k, ns in repo.names.iteritems():
520 for k, ns in repo.names.iteritems():
518 names = ns.names(repo, ctx.node())
521 names = ns.names(repo, ctx.node())
519 f = _showcompatlist(context, mapping, 'name', names)
522 f = _showcompatlist(context, mapping, 'name', names)
520 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
523 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
521
524
522 f = _showcompatlist(context, mapping, 'namespace', list(namespaces))
525 f = _showcompatlist(context, mapping, 'namespace', list(namespaces))
523
526
524 def makemap(ns):
527 def makemap(ns):
525 return {
528 return {
526 'namespace': ns,
529 'namespace': ns,
527 'names': namespaces[ns],
530 'names': namespaces[ns],
528 'builtin': repo.names[ns].builtin,
531 'builtin': repo.names[ns].builtin,
529 'colorname': repo.names[ns].colorname,
532 'colorname': repo.names[ns].colorname,
530 }
533 }
531
534
532 return _hybrid(f, namespaces, makemap, pycompat.identity)
535 return _hybrid(f, namespaces, makemap, pycompat.identity)
533
536
534 @templatekeyword('node', requires={'ctx'})
537 @templatekeyword('node', requires={'ctx'})
535 def shownode(context, mapping):
538 def shownode(context, mapping):
536 """String. The changeset identification hash, as a 40 hexadecimal
539 """String. The changeset identification hash, as a 40 hexadecimal
537 digit string.
540 digit string.
538 """
541 """
539 ctx = context.resource(mapping, 'ctx')
542 ctx = context.resource(mapping, 'ctx')
540 return ctx.hex()
543 return ctx.hex()
541
544
542 @templatekeyword('obsolete', requires={'ctx'})
545 @templatekeyword('obsolete', requires={'ctx'})
543 def showobsolete(context, mapping):
546 def showobsolete(context, mapping):
544 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
547 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
545 ctx = context.resource(mapping, 'ctx')
548 ctx = context.resource(mapping, 'ctx')
546 if ctx.obsolete():
549 if ctx.obsolete():
547 return 'obsolete'
550 return 'obsolete'
548 return ''
551 return ''
549
552
550 @templatekeyword('path', requires={'fctx'})
553 @templatekeyword('path', requires={'fctx'})
551 def showpath(context, mapping):
554 def showpath(context, mapping):
552 """String. Repository-absolute path of the current file. (EXPERIMENTAL)"""
555 """String. Repository-absolute path of the current file. (EXPERIMENTAL)"""
553 fctx = context.resource(mapping, 'fctx')
556 fctx = context.resource(mapping, 'fctx')
554 return fctx.path()
557 return fctx.path()
555
558
556 @templatekeyword('peerurls', requires={'repo'})
559 @templatekeyword('peerurls', requires={'repo'})
557 def showpeerurls(context, mapping):
560 def showpeerurls(context, mapping):
558 """A dictionary of repository locations defined in the [paths] section
561 """A dictionary of repository locations defined in the [paths] section
559 of your configuration file."""
562 of your configuration file."""
560 repo = context.resource(mapping, 'repo')
563 repo = context.resource(mapping, 'repo')
561 # see commands.paths() for naming of dictionary keys
564 # see commands.paths() for naming of dictionary keys
562 paths = repo.ui.paths
565 paths = repo.ui.paths
563 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
566 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
564 def makemap(k):
567 def makemap(k):
565 p = paths[k]
568 p = paths[k]
566 d = {'name': k, 'url': p.rawloc}
569 d = {'name': k, 'url': p.rawloc}
567 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
570 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
568 return d
571 return d
569 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
572 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
570
573
571 @templatekeyword("predecessors", requires={'repo', 'ctx'})
574 @templatekeyword("predecessors", requires={'repo', 'ctx'})
572 def showpredecessors(context, mapping):
575 def showpredecessors(context, mapping):
573 """Returns the list if the closest visible successors. (EXPERIMENTAL)"""
576 """Returns the list if the closest visible successors. (EXPERIMENTAL)"""
574 repo = context.resource(mapping, 'repo')
577 repo = context.resource(mapping, 'repo')
575 ctx = context.resource(mapping, 'ctx')
578 ctx = context.resource(mapping, 'ctx')
576 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
579 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
577 predecessors = pycompat.maplist(hex, predecessors)
580 predecessors = pycompat.maplist(hex, predecessors)
578
581
579 return _hybrid(None, predecessors,
582 return _hybrid(None, predecessors,
580 lambda x: {'ctx': repo[x]},
583 lambda x: {'ctx': repo[x]},
581 lambda x: scmutil.formatchangeid(repo[x]))
584 lambda x: scmutil.formatchangeid(repo[x]))
582
585
583 @templatekeyword('reporoot', requires={'repo'})
586 @templatekeyword('reporoot', requires={'repo'})
584 def showreporoot(context, mapping):
587 def showreporoot(context, mapping):
585 """String. The root directory of the current repository."""
588 """String. The root directory of the current repository."""
586 repo = context.resource(mapping, 'repo')
589 repo = context.resource(mapping, 'repo')
587 return repo.root
590 return repo.root
588
591
589 @templatekeyword('size', requires={'fctx'})
592 @templatekeyword('size', requires={'fctx'})
590 def showsize(context, mapping):
593 def showsize(context, mapping):
591 """Integer. Size of the current file in bytes. (EXPERIMENTAL)"""
594 """Integer. Size of the current file in bytes. (EXPERIMENTAL)"""
592 fctx = context.resource(mapping, 'fctx')
595 fctx = context.resource(mapping, 'fctx')
593 return fctx.size()
596 return fctx.size()
594
597
595 @templatekeyword("successorssets", requires={'repo', 'ctx'})
598 @templatekeyword("successorssets", requires={'repo', 'ctx'})
596 def showsuccessorssets(context, mapping):
599 def showsuccessorssets(context, mapping):
597 """Returns a string of sets of successors for a changectx. Format used
600 """Returns a string of sets of successors for a changectx. Format used
598 is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
601 is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
599 while also diverged into ctx3. (EXPERIMENTAL)"""
602 while also diverged into ctx3. (EXPERIMENTAL)"""
600 repo = context.resource(mapping, 'repo')
603 repo = context.resource(mapping, 'repo')
601 ctx = context.resource(mapping, 'ctx')
604 ctx = context.resource(mapping, 'ctx')
602 if not ctx.obsolete():
605 if not ctx.obsolete():
603 return ''
606 return ''
604
607
605 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
608 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
606 ssets = [[hex(n) for n in ss] for ss in ssets]
609 ssets = [[hex(n) for n in ss] for ss in ssets]
607
610
608 data = []
611 data = []
609 for ss in ssets:
612 for ss in ssets:
610 h = _hybrid(None, ss, lambda x: {'ctx': repo[x]},
613 h = _hybrid(None, ss, lambda x: {'ctx': repo[x]},
611 lambda x: scmutil.formatchangeid(repo[x]))
614 lambda x: scmutil.formatchangeid(repo[x]))
612 data.append(h)
615 data.append(h)
613
616
614 # Format the successorssets
617 # Format the successorssets
615 def render(d):
618 def render(d):
616 return templateutil.stringify(context, mapping, d)
619 return templateutil.stringify(context, mapping, d)
617
620
618 def gen(data):
621 def gen(data):
619 yield "; ".join(render(d) for d in data)
622 yield "; ".join(render(d) for d in data)
620
623
621 return _hybrid(gen(data), data, lambda x: {'successorset': x},
624 return _hybrid(gen(data), data, lambda x: {'successorset': x},
622 pycompat.identity)
625 pycompat.identity)
623
626
624 @templatekeyword("succsandmarkers", requires={'repo', 'ctx'})
627 @templatekeyword("succsandmarkers", requires={'repo', 'ctx'})
625 def showsuccsandmarkers(context, mapping):
628 def showsuccsandmarkers(context, mapping):
626 """Returns a list of dict for each final successor of ctx. The dict
629 """Returns a list of dict for each final successor of ctx. The dict
627 contains successors node id in "successors" keys and the list of
630 contains successors node id in "successors" keys and the list of
628 obs-markers from ctx to the set of successors in "markers".
631 obs-markers from ctx to the set of successors in "markers".
629 (EXPERIMENTAL)
632 (EXPERIMENTAL)
630 """
633 """
631 repo = context.resource(mapping, 'repo')
634 repo = context.resource(mapping, 'repo')
632 ctx = context.resource(mapping, 'ctx')
635 ctx = context.resource(mapping, 'ctx')
633
636
634 values = obsutil.successorsandmarkers(repo, ctx)
637 values = obsutil.successorsandmarkers(repo, ctx)
635
638
636 if values is None:
639 if values is None:
637 values = []
640 values = []
638
641
639 # Format successors and markers to avoid exposing binary to templates
642 # Format successors and markers to avoid exposing binary to templates
640 data = []
643 data = []
641 for i in values:
644 for i in values:
642 # Format successors
645 # Format successors
643 successors = i['successors']
646 successors = i['successors']
644
647
645 successors = [hex(n) for n in successors]
648 successors = [hex(n) for n in successors]
646 successors = _hybrid(None, successors,
649 successors = _hybrid(None, successors,
647 lambda x: {'ctx': repo[x]},
650 lambda x: {'ctx': repo[x]},
648 lambda x: scmutil.formatchangeid(repo[x]))
651 lambda x: scmutil.formatchangeid(repo[x]))
649
652
650 # Format markers
653 # Format markers
651 finalmarkers = []
654 finalmarkers = []
652 for m in i['markers']:
655 for m in i['markers']:
653 hexprec = hex(m[0])
656 hexprec = hex(m[0])
654 hexsucs = tuple(hex(n) for n in m[1])
657 hexsucs = tuple(hex(n) for n in m[1])
655 hexparents = None
658 hexparents = None
656 if m[5] is not None:
659 if m[5] is not None:
657 hexparents = tuple(hex(n) for n in m[5])
660 hexparents = tuple(hex(n) for n in m[5])
658 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
661 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
659 finalmarkers.append(newmarker)
662 finalmarkers.append(newmarker)
660
663
661 data.append({'successors': successors, 'markers': finalmarkers})
664 data.append({'successors': successors, 'markers': finalmarkers})
662
665
663 return templateutil.mappinglist(data)
666 return templateutil.mappinglist(data)
664
667
665 @templatekeyword('p1rev', requires={'ctx'})
668 @templatekeyword('p1rev', requires={'ctx'})
666 def showp1rev(context, mapping):
669 def showp1rev(context, mapping):
667 """Integer. The repository-local revision number of the changeset's
670 """Integer. The repository-local revision number of the changeset's
668 first parent, or -1 if the changeset has no parents."""
671 first parent, or -1 if the changeset has no parents."""
669 ctx = context.resource(mapping, 'ctx')
672 ctx = context.resource(mapping, 'ctx')
670 return ctx.p1().rev()
673 return ctx.p1().rev()
671
674
672 @templatekeyword('p2rev', requires={'ctx'})
675 @templatekeyword('p2rev', requires={'ctx'})
673 def showp2rev(context, mapping):
676 def showp2rev(context, mapping):
674 """Integer. The repository-local revision number of the changeset's
677 """Integer. The repository-local revision number of the changeset's
675 second parent, or -1 if the changeset has no second parent."""
678 second parent, or -1 if the changeset has no second parent."""
676 ctx = context.resource(mapping, 'ctx')
679 ctx = context.resource(mapping, 'ctx')
677 return ctx.p2().rev()
680 return ctx.p2().rev()
678
681
679 @templatekeyword('p1node', requires={'ctx'})
682 @templatekeyword('p1node', requires={'ctx'})
680 def showp1node(context, mapping):
683 def showp1node(context, mapping):
681 """String. The identification hash of the changeset's first parent,
684 """String. The identification hash of the changeset's first parent,
682 as a 40 digit hexadecimal string. If the changeset has no parents, all
685 as a 40 digit hexadecimal string. If the changeset has no parents, all
683 digits are 0."""
686 digits are 0."""
684 ctx = context.resource(mapping, 'ctx')
687 ctx = context.resource(mapping, 'ctx')
685 return ctx.p1().hex()
688 return ctx.p1().hex()
686
689
687 @templatekeyword('p2node', requires={'ctx'})
690 @templatekeyword('p2node', requires={'ctx'})
688 def showp2node(context, mapping):
691 def showp2node(context, mapping):
689 """String. The identification hash of the changeset's second
692 """String. The identification hash of the changeset's second
690 parent, as a 40 digit hexadecimal string. If the changeset has no second
693 parent, as a 40 digit hexadecimal string. If the changeset has no second
691 parent, all digits are 0."""
694 parent, all digits are 0."""
692 ctx = context.resource(mapping, 'ctx')
695 ctx = context.resource(mapping, 'ctx')
693 return ctx.p2().hex()
696 return ctx.p2().hex()
694
697
695 @templatekeyword('parents', requires={'repo', 'ctx'})
698 @templatekeyword('parents', requires={'repo', 'ctx'})
696 def showparents(context, mapping):
699 def showparents(context, mapping):
697 """List of strings. The parents of the changeset in "rev:node"
700 """List of strings. The parents of the changeset in "rev:node"
698 format. If the changeset has only one "natural" parent (the predecessor
701 format. If the changeset has only one "natural" parent (the predecessor
699 revision) nothing is shown."""
702 revision) nothing is shown."""
700 repo = context.resource(mapping, 'repo')
703 repo = context.resource(mapping, 'repo')
701 ctx = context.resource(mapping, 'ctx')
704 ctx = context.resource(mapping, 'ctx')
702 pctxs = scmutil.meaningfulparents(repo, ctx)
705 pctxs = scmutil.meaningfulparents(repo, ctx)
703 prevs = [p.rev() for p in pctxs]
706 prevs = [p.rev() for p in pctxs]
704 parents = [[('rev', p.rev()),
707 parents = [[('rev', p.rev()),
705 ('node', p.hex()),
708 ('node', p.hex()),
706 ('phase', p.phasestr())]
709 ('phase', p.phasestr())]
707 for p in pctxs]
710 for p in pctxs]
708 f = _showcompatlist(context, mapping, 'parent', parents)
711 f = _showcompatlist(context, mapping, 'parent', parents)
709 return _hybrid(f, prevs, lambda x: {'ctx': repo[x]},
712 return _hybrid(f, prevs, lambda x: {'ctx': repo[x]},
710 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
713 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
711
714
712 @templatekeyword('phase', requires={'ctx'})
715 @templatekeyword('phase', requires={'ctx'})
713 def showphase(context, mapping):
716 def showphase(context, mapping):
714 """String. The changeset phase name."""
717 """String. The changeset phase name."""
715 ctx = context.resource(mapping, 'ctx')
718 ctx = context.resource(mapping, 'ctx')
716 return ctx.phasestr()
719 return ctx.phasestr()
717
720
718 @templatekeyword('phaseidx', requires={'ctx'})
721 @templatekeyword('phaseidx', requires={'ctx'})
719 def showphaseidx(context, mapping):
722 def showphaseidx(context, mapping):
720 """Integer. The changeset phase index. (ADVANCED)"""
723 """Integer. The changeset phase index. (ADVANCED)"""
721 ctx = context.resource(mapping, 'ctx')
724 ctx = context.resource(mapping, 'ctx')
722 return ctx.phase()
725 return ctx.phase()
723
726
724 @templatekeyword('rev', requires={'ctx'})
727 @templatekeyword('rev', requires={'ctx'})
725 def showrev(context, mapping):
728 def showrev(context, mapping):
726 """Integer. The repository-local changeset revision number."""
729 """Integer. The repository-local changeset revision number."""
727 ctx = context.resource(mapping, 'ctx')
730 ctx = context.resource(mapping, 'ctx')
728 return scmutil.intrev(ctx)
731 return scmutil.intrev(ctx)
729
732
730 def showrevslist(context, mapping, name, revs):
733 def showrevslist(context, mapping, name, revs):
731 """helper to generate a list of revisions in which a mapped template will
734 """helper to generate a list of revisions in which a mapped template will
732 be evaluated"""
735 be evaluated"""
733 repo = context.resource(mapping, 'repo')
736 repo = context.resource(mapping, 'repo')
734 f = _showcompatlist(context, mapping, name, ['%d' % r for r in revs])
737 f = _showcompatlist(context, mapping, name, ['%d' % r for r in revs])
735 return _hybrid(f, revs,
738 return _hybrid(f, revs,
736 lambda x: {name: x, 'ctx': repo[x]},
739 lambda x: {name: x, 'ctx': repo[x]},
737 pycompat.identity, keytype=int)
740 pycompat.identity, keytype=int)
738
741
739 @templatekeyword('subrepos', requires={'ctx'})
742 @templatekeyword('subrepos', requires={'ctx'})
740 def showsubrepos(context, mapping):
743 def showsubrepos(context, mapping):
741 """List of strings. Updated subrepositories in the changeset."""
744 """List of strings. Updated subrepositories in the changeset."""
742 ctx = context.resource(mapping, 'ctx')
745 ctx = context.resource(mapping, 'ctx')
743 substate = ctx.substate
746 substate = ctx.substate
744 if not substate:
747 if not substate:
745 return compatlist(context, mapping, 'subrepo', [])
748 return compatlist(context, mapping, 'subrepo', [])
746 psubstate = ctx.parents()[0].substate or {}
749 psubstate = ctx.parents()[0].substate or {}
747 subrepos = []
750 subrepos = []
748 for sub in substate:
751 for sub in substate:
749 if sub not in psubstate or substate[sub] != psubstate[sub]:
752 if sub not in psubstate or substate[sub] != psubstate[sub]:
750 subrepos.append(sub) # modified or newly added in ctx
753 subrepos.append(sub) # modified or newly added in ctx
751 for sub in psubstate:
754 for sub in psubstate:
752 if sub not in substate:
755 if sub not in substate:
753 subrepos.append(sub) # removed in ctx
756 subrepos.append(sub) # removed in ctx
754 return compatlist(context, mapping, 'subrepo', sorted(subrepos))
757 return compatlist(context, mapping, 'subrepo', sorted(subrepos))
755
758
756 # don't remove "showtags" definition, even though namespaces will put
759 # don't remove "showtags" definition, even though namespaces will put
757 # a helper function for "tags" keyword into "keywords" map automatically,
760 # a helper function for "tags" keyword into "keywords" map automatically,
758 # because online help text is built without namespaces initialization
761 # because online help text is built without namespaces initialization
759 @templatekeyword('tags', requires={'repo', 'ctx'})
762 @templatekeyword('tags', requires={'repo', 'ctx'})
760 def showtags(context, mapping):
763 def showtags(context, mapping):
761 """List of strings. Any tags associated with the changeset."""
764 """List of strings. Any tags associated with the changeset."""
762 return shownames(context, mapping, 'tags')
765 return shownames(context, mapping, 'tags')
763
766
764 @templatekeyword('termwidth', requires={'ui'})
767 @templatekeyword('termwidth', requires={'ui'})
765 def showtermwidth(context, mapping):
768 def showtermwidth(context, mapping):
766 """Integer. The width of the current terminal."""
769 """Integer. The width of the current terminal."""
767 ui = context.resource(mapping, 'ui')
770 ui = context.resource(mapping, 'ui')
768 return ui.termwidth()
771 return ui.termwidth()
769
772
770 @templatekeyword('user', requires={'ctx'})
773 @templatekeyword('user', requires={'ctx'})
771 def showuser(context, mapping):
774 def showuser(context, mapping):
772 """String. The unmodified author of the changeset."""
775 """String. The unmodified author of the changeset."""
773 ctx = context.resource(mapping, 'ctx')
776 ctx = context.resource(mapping, 'ctx')
774 return ctx.user()
777 return ctx.user()
775
778
776 @templatekeyword('instabilities', requires={'ctx'})
779 @templatekeyword('instabilities', requires={'ctx'})
777 def showinstabilities(context, mapping):
780 def showinstabilities(context, mapping):
778 """List of strings. Evolution instabilities affecting the changeset.
781 """List of strings. Evolution instabilities affecting the changeset.
779 (EXPERIMENTAL)
782 (EXPERIMENTAL)
780 """
783 """
781 ctx = context.resource(mapping, 'ctx')
784 ctx = context.resource(mapping, 'ctx')
782 return compatlist(context, mapping, 'instability', ctx.instabilities(),
785 return compatlist(context, mapping, 'instability', ctx.instabilities(),
783 plural='instabilities')
786 plural='instabilities')
784
787
785 @templatekeyword('verbosity', requires={'ui'})
788 @templatekeyword('verbosity', requires={'ui'})
786 def showverbosity(context, mapping):
789 def showverbosity(context, mapping):
787 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
790 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
788 or ''."""
791 or ''."""
789 ui = context.resource(mapping, 'ui')
792 ui = context.resource(mapping, 'ui')
790 # see logcmdutil.changesettemplater for priority of these flags
793 # see logcmdutil.changesettemplater for priority of these flags
791 if ui.debugflag:
794 if ui.debugflag:
792 return 'debug'
795 return 'debug'
793 elif ui.quiet:
796 elif ui.quiet:
794 return 'quiet'
797 return 'quiet'
795 elif ui.verbose:
798 elif ui.verbose:
796 return 'verbose'
799 return 'verbose'
797 return ''
800 return ''
798
801
799 @templatekeyword('whyunstable', requires={'repo', 'ctx'})
802 @templatekeyword('whyunstable', requires={'repo', 'ctx'})
800 def showwhyunstable(context, mapping):
803 def showwhyunstable(context, mapping):
801 """List of dicts explaining all instabilities of a changeset.
804 """List of dicts explaining all instabilities of a changeset.
802 (EXPERIMENTAL)
805 (EXPERIMENTAL)
803 """
806 """
804 repo = context.resource(mapping, 'repo')
807 repo = context.resource(mapping, 'repo')
805 ctx = context.resource(mapping, 'ctx')
808 ctx = context.resource(mapping, 'ctx')
806
809
807 def formatnode(ctx):
810 def formatnode(ctx):
808 return '%s (%s)' % (scmutil.formatchangeid(ctx), ctx.phasestr())
811 return '%s (%s)' % (scmutil.formatchangeid(ctx), ctx.phasestr())
809
812
810 entries = obsutil.whyunstable(repo, ctx)
813 entries = obsutil.whyunstable(repo, ctx)
811
814
812 for entry in entries:
815 for entry in entries:
813 if entry.get('divergentnodes'):
816 if entry.get('divergentnodes'):
814 dnodes = entry['divergentnodes']
817 dnodes = entry['divergentnodes']
815 dnhybrid = _hybrid(None, [dnode.hex() for dnode in dnodes],
818 dnhybrid = _hybrid(None, [dnode.hex() for dnode in dnodes],
816 lambda x: {'ctx': repo[x]},
819 lambda x: {'ctx': repo[x]},
817 lambda x: formatnode(repo[x]))
820 lambda x: formatnode(repo[x]))
818 entry['divergentnodes'] = dnhybrid
821 entry['divergentnodes'] = dnhybrid
819
822
820 tmpl = ('{instability}:{if(divergentnodes, " ")}{divergentnodes} '
823 tmpl = ('{instability}:{if(divergentnodes, " ")}{divergentnodes} '
821 '{reason} {node|short}')
824 '{reason} {node|short}')
822 return templateutil.mappinglist(entries, tmpl=tmpl, sep='\n')
825 return templateutil.mappinglist(entries, tmpl=tmpl, sep='\n')
823
826
824 def loadkeyword(ui, extname, registrarobj):
827 def loadkeyword(ui, extname, registrarobj):
825 """Load template keyword from specified registrarobj
828 """Load template keyword from specified registrarobj
826 """
829 """
827 for name, func in registrarobj._table.iteritems():
830 for name, func in registrarobj._table.iteritems():
828 keywords[name] = func
831 keywords[name] = func
829
832
830 # tell hggettext to extract docstrings from these functions:
833 # tell hggettext to extract docstrings from these functions:
831 i18nfunctions = keywords.values()
834 i18nfunctions = keywords.values()
General Comments 0
You need to be logged in to leave comments. Login now