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