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