##// END OF EJS Templates
templatekw: alias {name} of file copies dict to {path}...
Yuya Nishihara -
r39404:5b1d406b default
parent child Browse files
Show More
@@ -1,820 +1,815 b''
1 # templatekw.py - common changeset template keywords
1 # templatekw.py - common changeset template keywords
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from .i18n import _
10 from .i18n import _
11 from .node import (
11 from .node import (
12 hex,
12 hex,
13 nullid,
13 nullid,
14 )
14 )
15
15
16 from . import (
16 from . import (
17 diffutil,
17 diffutil,
18 encoding,
18 encoding,
19 error,
19 error,
20 hbisect,
20 hbisect,
21 i18n,
21 i18n,
22 obsutil,
22 obsutil,
23 patch,
23 patch,
24 pycompat,
24 pycompat,
25 registrar,
25 registrar,
26 scmutil,
26 scmutil,
27 templateutil,
27 templateutil,
28 util,
28 util,
29 )
29 )
30 from .utils import (
30 from .utils import (
31 stringutil,
31 stringutil,
32 )
32 )
33
33
34 _hybrid = templateutil.hybrid
34 _hybrid = templateutil.hybrid
35 hybriddict = templateutil.hybriddict
35 hybriddict = templateutil.hybriddict
36 hybridlist = templateutil.hybridlist
36 hybridlist = templateutil.hybridlist
37 compatdict = templateutil.compatdict
37 compatdict = templateutil.compatdict
38 compatlist = templateutil.compatlist
38 compatlist = templateutil.compatlist
39 _showcompatlist = templateutil._showcompatlist
39 _showcompatlist = templateutil._showcompatlist
40
40
41 def getlatesttags(context, mapping, pattern=None):
41 def getlatesttags(context, mapping, pattern=None):
42 '''return date, distance and name for the latest tag of rev'''
42 '''return date, distance and name for the latest tag of rev'''
43 repo = context.resource(mapping, 'repo')
43 repo = context.resource(mapping, 'repo')
44 ctx = context.resource(mapping, 'ctx')
44 ctx = context.resource(mapping, 'ctx')
45 cache = context.resource(mapping, 'cache')
45 cache = context.resource(mapping, 'cache')
46
46
47 cachename = 'latesttags'
47 cachename = 'latesttags'
48 if pattern is not None:
48 if pattern is not None:
49 cachename += '-' + pattern
49 cachename += '-' + pattern
50 match = stringutil.stringmatcher(pattern)[2]
50 match = stringutil.stringmatcher(pattern)[2]
51 else:
51 else:
52 match = util.always
52 match = util.always
53
53
54 if cachename not in cache:
54 if cachename not in cache:
55 # Cache mapping from rev to a tuple with tag date, tag
55 # Cache mapping from rev to a tuple with tag date, tag
56 # distance and tag name
56 # distance and tag name
57 cache[cachename] = {-1: (0, 0, ['null'])}
57 cache[cachename] = {-1: (0, 0, ['null'])}
58 latesttags = cache[cachename]
58 latesttags = cache[cachename]
59
59
60 rev = ctx.rev()
60 rev = ctx.rev()
61 todo = [rev]
61 todo = [rev]
62 while todo:
62 while todo:
63 rev = todo.pop()
63 rev = todo.pop()
64 if rev in latesttags:
64 if rev in latesttags:
65 continue
65 continue
66 ctx = repo[rev]
66 ctx = repo[rev]
67 tags = [t for t in ctx.tags()
67 tags = [t for t in ctx.tags()
68 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
68 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
69 and match(t))]
69 and match(t))]
70 if tags:
70 if tags:
71 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
71 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
72 continue
72 continue
73 try:
73 try:
74 ptags = [latesttags[p.rev()] for p in ctx.parents()]
74 ptags = [latesttags[p.rev()] for p in ctx.parents()]
75 if len(ptags) > 1:
75 if len(ptags) > 1:
76 if ptags[0][2] == ptags[1][2]:
76 if ptags[0][2] == ptags[1][2]:
77 # The tuples are laid out so the right one can be found by
77 # The tuples are laid out so the right one can be found by
78 # comparison in this case.
78 # comparison in this case.
79 pdate, pdist, ptag = max(ptags)
79 pdate, pdist, ptag = max(ptags)
80 else:
80 else:
81 def key(x):
81 def key(x):
82 changessincetag = len(repo.revs('only(%d, %s)',
82 changessincetag = len(repo.revs('only(%d, %s)',
83 ctx.rev(), x[2][0]))
83 ctx.rev(), x[2][0]))
84 # Smallest number of changes since tag wins. Date is
84 # Smallest number of changes since tag wins. Date is
85 # used as tiebreaker.
85 # used as tiebreaker.
86 return [-changessincetag, x[0]]
86 return [-changessincetag, x[0]]
87 pdate, pdist, ptag = max(ptags, key=key)
87 pdate, pdist, ptag = max(ptags, key=key)
88 else:
88 else:
89 pdate, pdist, ptag = ptags[0]
89 pdate, pdist, ptag = ptags[0]
90 except KeyError:
90 except KeyError:
91 # Cache miss - recurse
91 # Cache miss - recurse
92 todo.append(rev)
92 todo.append(rev)
93 todo.extend(p.rev() for p in ctx.parents())
93 todo.extend(p.rev() for p in ctx.parents())
94 continue
94 continue
95 latesttags[rev] = pdate, pdist + 1, ptag
95 latesttags[rev] = pdate, pdist + 1, ptag
96 return latesttags[rev]
96 return latesttags[rev]
97
97
98 def getrenamedfn(repo, endrev=None):
98 def getrenamedfn(repo, endrev=None):
99 rcache = {}
99 rcache = {}
100 if endrev is None:
100 if endrev is None:
101 endrev = len(repo)
101 endrev = len(repo)
102
102
103 def getrenamed(fn, rev):
103 def getrenamed(fn, rev):
104 '''looks up all renames for a file (up to endrev) the first
104 '''looks up all renames for a file (up to endrev) the first
105 time the file is given. It indexes on the changerev and only
105 time the file is given. It indexes on the changerev and only
106 parses the manifest if linkrev != changerev.
106 parses the manifest if linkrev != changerev.
107 Returns rename info for fn at changerev rev.'''
107 Returns rename info for fn at changerev rev.'''
108 if fn not in rcache:
108 if fn not in rcache:
109 rcache[fn] = {}
109 rcache[fn] = {}
110 fl = repo.file(fn)
110 fl = repo.file(fn)
111 for i in fl:
111 for i in fl:
112 lr = fl.linkrev(i)
112 lr = fl.linkrev(i)
113 renamed = fl.renamed(fl.node(i))
113 renamed = fl.renamed(fl.node(i))
114 rcache[fn][lr] = renamed and renamed[0]
114 rcache[fn][lr] = renamed and renamed[0]
115 if lr >= endrev:
115 if lr >= endrev:
116 break
116 break
117 if rev in rcache[fn]:
117 if rev in rcache[fn]:
118 return rcache[fn][rev]
118 return rcache[fn][rev]
119
119
120 # If linkrev != rev (i.e. rev not found in rcache) fallback to
120 # If linkrev != rev (i.e. rev not found in rcache) fallback to
121 # filectx logic.
121 # filectx logic.
122 try:
122 try:
123 renamed = repo[rev][fn].renamed()
123 renamed = repo[rev][fn].renamed()
124 return renamed and renamed[0]
124 return renamed and renamed[0]
125 except error.LookupError:
125 except error.LookupError:
126 return None
126 return None
127
127
128 return getrenamed
128 return getrenamed
129
129
130 def getlogcolumns():
130 def getlogcolumns():
131 """Return a dict of log column labels"""
131 """Return a dict of log column labels"""
132 _ = pycompat.identity # temporarily disable gettext
132 _ = pycompat.identity # temporarily disable gettext
133 # i18n: column positioning for "hg log"
133 # i18n: column positioning for "hg log"
134 columns = _('bookmark: %s\n'
134 columns = _('bookmark: %s\n'
135 'branch: %s\n'
135 'branch: %s\n'
136 'changeset: %s\n'
136 'changeset: %s\n'
137 'copies: %s\n'
137 'copies: %s\n'
138 'date: %s\n'
138 'date: %s\n'
139 'extra: %s=%s\n'
139 'extra: %s=%s\n'
140 'files+: %s\n'
140 'files+: %s\n'
141 'files-: %s\n'
141 'files-: %s\n'
142 'files: %s\n'
142 'files: %s\n'
143 'instability: %s\n'
143 'instability: %s\n'
144 'manifest: %s\n'
144 'manifest: %s\n'
145 'obsolete: %s\n'
145 'obsolete: %s\n'
146 'parent: %s\n'
146 'parent: %s\n'
147 'phase: %s\n'
147 'phase: %s\n'
148 'summary: %s\n'
148 'summary: %s\n'
149 'tag: %s\n'
149 'tag: %s\n'
150 'user: %s\n')
150 'user: %s\n')
151 return dict(zip([s.split(':', 1)[0] for s in columns.splitlines()],
151 return dict(zip([s.split(':', 1)[0] for s in columns.splitlines()],
152 i18n._(columns).splitlines(True)))
152 i18n._(columns).splitlines(True)))
153
153
154 # default templates internally used for rendering of lists
154 # default templates internally used for rendering of lists
155 defaulttempl = {
155 defaulttempl = {
156 'parent': '{rev}:{node|formatnode} ',
156 'parent': '{rev}:{node|formatnode} ',
157 'manifest': '{rev}:{node|formatnode}',
157 'manifest': '{rev}:{node|formatnode}',
158 'file_copy': '{name} ({source})',
158 'file_copy': '{name} ({source})',
159 'envvar': '{key}={value}',
159 'envvar': '{key}={value}',
160 'extra': '{key}={value|stringescape}'
160 'extra': '{key}={value|stringescape}'
161 }
161 }
162 # filecopy is preserved for compatibility reasons
162 # filecopy is preserved for compatibility reasons
163 defaulttempl['filecopy'] = defaulttempl['file_copy']
163 defaulttempl['filecopy'] = defaulttempl['file_copy']
164
164
165 # keywords are callables (see registrar.templatekeyword for details)
165 # keywords are callables (see registrar.templatekeyword for details)
166 keywords = {}
166 keywords = {}
167 templatekeyword = registrar.templatekeyword(keywords)
167 templatekeyword = registrar.templatekeyword(keywords)
168
168
169 @templatekeyword('author', requires={'ctx'})
169 @templatekeyword('author', requires={'ctx'})
170 def showauthor(context, mapping):
170 def showauthor(context, mapping):
171 """Alias for ``{user}``"""
171 """Alias for ``{user}``"""
172 return showuser(context, mapping)
172 return showuser(context, mapping)
173
173
174 @templatekeyword('bisect', requires={'repo', 'ctx'})
174 @templatekeyword('bisect', requires={'repo', 'ctx'})
175 def showbisect(context, mapping):
175 def showbisect(context, mapping):
176 """String. The changeset bisection status."""
176 """String. The changeset bisection status."""
177 repo = context.resource(mapping, 'repo')
177 repo = context.resource(mapping, 'repo')
178 ctx = context.resource(mapping, 'ctx')
178 ctx = context.resource(mapping, 'ctx')
179 return hbisect.label(repo, ctx.node())
179 return hbisect.label(repo, ctx.node())
180
180
181 @templatekeyword('branch', requires={'ctx'})
181 @templatekeyword('branch', requires={'ctx'})
182 def showbranch(context, mapping):
182 def showbranch(context, mapping):
183 """String. The name of the branch on which the changeset was
183 """String. The name of the branch on which the changeset was
184 committed.
184 committed.
185 """
185 """
186 ctx = context.resource(mapping, 'ctx')
186 ctx = context.resource(mapping, 'ctx')
187 return ctx.branch()
187 return ctx.branch()
188
188
189 @templatekeyword('branches', requires={'ctx'})
189 @templatekeyword('branches', requires={'ctx'})
190 def showbranches(context, mapping):
190 def showbranches(context, mapping):
191 """List of strings. The name of the branch on which the
191 """List of strings. The name of the branch on which the
192 changeset was committed. Will be empty if the branch name was
192 changeset was committed. Will be empty if the branch name was
193 default. (DEPRECATED)
193 default. (DEPRECATED)
194 """
194 """
195 ctx = context.resource(mapping, 'ctx')
195 ctx = context.resource(mapping, 'ctx')
196 branch = ctx.branch()
196 branch = ctx.branch()
197 if branch != 'default':
197 if branch != 'default':
198 return compatlist(context, mapping, 'branch', [branch],
198 return compatlist(context, mapping, 'branch', [branch],
199 plural='branches')
199 plural='branches')
200 return compatlist(context, mapping, 'branch', [], plural='branches')
200 return compatlist(context, mapping, 'branch', [], plural='branches')
201
201
202 @templatekeyword('bookmarks', requires={'repo', 'ctx'})
202 @templatekeyword('bookmarks', requires={'repo', 'ctx'})
203 def showbookmarks(context, mapping):
203 def showbookmarks(context, mapping):
204 """List of strings. Any bookmarks associated with the
204 """List of strings. Any bookmarks associated with the
205 changeset. Also sets 'active', the name of the active bookmark.
205 changeset. Also sets 'active', the name of the active bookmark.
206 """
206 """
207 repo = context.resource(mapping, 'repo')
207 repo = context.resource(mapping, 'repo')
208 ctx = context.resource(mapping, 'ctx')
208 ctx = context.resource(mapping, 'ctx')
209 bookmarks = ctx.bookmarks()
209 bookmarks = ctx.bookmarks()
210 active = repo._activebookmark
210 active = repo._activebookmark
211 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
211 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
212 f = _showcompatlist(context, mapping, 'bookmark', bookmarks)
212 f = _showcompatlist(context, mapping, 'bookmark', bookmarks)
213 return _hybrid(f, bookmarks, makemap, pycompat.identity)
213 return _hybrid(f, bookmarks, makemap, pycompat.identity)
214
214
215 @templatekeyword('children', requires={'ctx'})
215 @templatekeyword('children', requires={'ctx'})
216 def showchildren(context, mapping):
216 def showchildren(context, mapping):
217 """List of strings. The children of the changeset."""
217 """List of strings. The children of the changeset."""
218 ctx = context.resource(mapping, 'ctx')
218 ctx = context.resource(mapping, 'ctx')
219 childrevs = ['%d:%s' % (cctx.rev(), cctx) for cctx in ctx.children()]
219 childrevs = ['%d:%s' % (cctx.rev(), cctx) for cctx in ctx.children()]
220 return compatlist(context, mapping, 'children', childrevs, element='child')
220 return compatlist(context, mapping, 'children', childrevs, element='child')
221
221
222 # Deprecated, but kept alive for help generation a purpose.
222 # Deprecated, but kept alive for help generation a purpose.
223 @templatekeyword('currentbookmark', requires={'repo', 'ctx'})
223 @templatekeyword('currentbookmark', requires={'repo', 'ctx'})
224 def showcurrentbookmark(context, mapping):
224 def showcurrentbookmark(context, mapping):
225 """String. The active bookmark, if it is associated with the changeset.
225 """String. The active bookmark, if it is associated with the changeset.
226 (DEPRECATED)"""
226 (DEPRECATED)"""
227 return showactivebookmark(context, mapping)
227 return showactivebookmark(context, mapping)
228
228
229 @templatekeyword('activebookmark', requires={'repo', 'ctx'})
229 @templatekeyword('activebookmark', requires={'repo', 'ctx'})
230 def showactivebookmark(context, mapping):
230 def showactivebookmark(context, mapping):
231 """String. The active bookmark, if it is associated with the changeset."""
231 """String. The active bookmark, if it is associated with the changeset."""
232 repo = context.resource(mapping, 'repo')
232 repo = context.resource(mapping, 'repo')
233 ctx = context.resource(mapping, 'ctx')
233 ctx = context.resource(mapping, 'ctx')
234 active = repo._activebookmark
234 active = repo._activebookmark
235 if active and active in ctx.bookmarks():
235 if active and active in ctx.bookmarks():
236 return active
236 return active
237 return ''
237 return ''
238
238
239 @templatekeyword('date', requires={'ctx'})
239 @templatekeyword('date', requires={'ctx'})
240 def showdate(context, mapping):
240 def showdate(context, mapping):
241 """Date information. The date when the changeset was committed."""
241 """Date information. The date when the changeset was committed."""
242 ctx = context.resource(mapping, 'ctx')
242 ctx = context.resource(mapping, 'ctx')
243 # the default string format is '<float(unixtime)><tzoffset>' because
243 # the default string format is '<float(unixtime)><tzoffset>' because
244 # python-hglib splits date at decimal separator.
244 # python-hglib splits date at decimal separator.
245 return templateutil.date(ctx.date(), showfmt='%d.0%d')
245 return templateutil.date(ctx.date(), showfmt='%d.0%d')
246
246
247 @templatekeyword('desc', requires={'ctx'})
247 @templatekeyword('desc', requires={'ctx'})
248 def showdescription(context, mapping):
248 def showdescription(context, mapping):
249 """String. The text of the changeset description."""
249 """String. The text of the changeset description."""
250 ctx = context.resource(mapping, 'ctx')
250 ctx = context.resource(mapping, 'ctx')
251 s = ctx.description()
251 s = ctx.description()
252 if isinstance(s, encoding.localstr):
252 if isinstance(s, encoding.localstr):
253 # try hard to preserve utf-8 bytes
253 # try hard to preserve utf-8 bytes
254 return encoding.tolocal(encoding.fromlocal(s).strip())
254 return encoding.tolocal(encoding.fromlocal(s).strip())
255 elif isinstance(s, encoding.safelocalstr):
255 elif isinstance(s, encoding.safelocalstr):
256 return encoding.safelocalstr(s.strip())
256 return encoding.safelocalstr(s.strip())
257 else:
257 else:
258 return s.strip()
258 return s.strip()
259
259
260 @templatekeyword('diffstat', requires={'ui', 'ctx'})
260 @templatekeyword('diffstat', requires={'ui', 'ctx'})
261 def showdiffstat(context, mapping):
261 def showdiffstat(context, mapping):
262 """String. Statistics of changes with the following format:
262 """String. Statistics of changes with the following format:
263 "modified files: +added/-removed lines"
263 "modified files: +added/-removed lines"
264 """
264 """
265 ui = context.resource(mapping, 'ui')
265 ui = context.resource(mapping, 'ui')
266 ctx = context.resource(mapping, 'ctx')
266 ctx = context.resource(mapping, 'ctx')
267 diffopts = diffutil.diffallopts(ui, {'noprefix': False})
267 diffopts = diffutil.diffallopts(ui, {'noprefix': False})
268 diff = ctx.diff(opts=diffopts)
268 diff = ctx.diff(opts=diffopts)
269 stats = patch.diffstatdata(util.iterlines(diff))
269 stats = patch.diffstatdata(util.iterlines(diff))
270 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
270 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
271 return '%d: +%d/-%d' % (len(stats), adds, removes)
271 return '%d: +%d/-%d' % (len(stats), adds, removes)
272
272
273 @templatekeyword('envvars', requires={'ui'})
273 @templatekeyword('envvars', requires={'ui'})
274 def showenvvars(context, mapping):
274 def showenvvars(context, mapping):
275 """A dictionary of environment variables. (EXPERIMENTAL)"""
275 """A dictionary of environment variables. (EXPERIMENTAL)"""
276 ui = context.resource(mapping, 'ui')
276 ui = context.resource(mapping, 'ui')
277 env = ui.exportableenviron()
277 env = ui.exportableenviron()
278 env = util.sortdict((k, env[k]) for k in sorted(env))
278 env = util.sortdict((k, env[k]) for k in sorted(env))
279 return compatdict(context, mapping, 'envvar', env, plural='envvars')
279 return compatdict(context, mapping, 'envvar', env, plural='envvars')
280
280
281 @templatekeyword('extras', requires={'ctx'})
281 @templatekeyword('extras', requires={'ctx'})
282 def showextras(context, mapping):
282 def showextras(context, mapping):
283 """List of dicts with key, value entries of the 'extras'
283 """List of dicts with key, value entries of the 'extras'
284 field of this changeset."""
284 field of this changeset."""
285 ctx = context.resource(mapping, 'ctx')
285 ctx = context.resource(mapping, 'ctx')
286 extras = ctx.extra()
286 extras = ctx.extra()
287 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
287 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
288 makemap = lambda k: {'key': k, 'value': extras[k]}
288 makemap = lambda k: {'key': k, 'value': extras[k]}
289 c = [makemap(k) for k in extras]
289 c = [makemap(k) for k in extras]
290 f = _showcompatlist(context, mapping, 'extra', c, plural='extras')
290 f = _showcompatlist(context, mapping, 'extra', c, plural='extras')
291 return _hybrid(f, extras, makemap,
291 return _hybrid(f, extras, makemap,
292 lambda k: '%s=%s' % (k, stringutil.escapestr(extras[k])))
292 lambda k: '%s=%s' % (k, stringutil.escapestr(extras[k])))
293
293
294 def _showfilesbystat(context, mapping, name, index):
294 def _showfilesbystat(context, mapping, name, index):
295 ctx = context.resource(mapping, 'ctx')
295 ctx = context.resource(mapping, 'ctx')
296 revcache = context.resource(mapping, 'revcache')
296 revcache = context.resource(mapping, 'revcache')
297 if 'files' not in revcache:
297 if 'files' not in revcache:
298 revcache['files'] = ctx.p1().status(ctx)[:3]
298 revcache['files'] = ctx.p1().status(ctx)[:3]
299 files = revcache['files'][index]
299 files = revcache['files'][index]
300 return templateutil.compatfileslist(context, mapping, name, files)
300 return templateutil.compatfileslist(context, mapping, name, files)
301
301
302 @templatekeyword('file_adds', requires={'ctx', 'revcache'})
302 @templatekeyword('file_adds', requires={'ctx', 'revcache'})
303 def showfileadds(context, mapping):
303 def showfileadds(context, mapping):
304 """List of strings. Files added by this changeset."""
304 """List of strings. Files added by this changeset."""
305 return _showfilesbystat(context, mapping, 'file_add', 1)
305 return _showfilesbystat(context, mapping, 'file_add', 1)
306
306
307 @templatekeyword('file_copies',
307 @templatekeyword('file_copies',
308 requires={'repo', 'ctx', 'cache', 'revcache'})
308 requires={'repo', 'ctx', 'cache', 'revcache'})
309 def showfilecopies(context, mapping):
309 def showfilecopies(context, mapping):
310 """List of strings. Files copied in this changeset with
310 """List of strings. Files copied in this changeset with
311 their sources.
311 their sources.
312 """
312 """
313 repo = context.resource(mapping, 'repo')
313 repo = context.resource(mapping, 'repo')
314 ctx = context.resource(mapping, 'ctx')
314 ctx = context.resource(mapping, 'ctx')
315 cache = context.resource(mapping, 'cache')
315 cache = context.resource(mapping, 'cache')
316 copies = context.resource(mapping, 'revcache').get('copies')
316 copies = context.resource(mapping, 'revcache').get('copies')
317 if copies is None:
317 if copies is None:
318 if 'getrenamed' not in cache:
318 if 'getrenamed' not in cache:
319 cache['getrenamed'] = getrenamedfn(repo)
319 cache['getrenamed'] = getrenamedfn(repo)
320 copies = []
320 copies = []
321 getrenamed = cache['getrenamed']
321 getrenamed = cache['getrenamed']
322 for fn in ctx.files():
322 for fn in ctx.files():
323 rename = getrenamed(fn, ctx.rev())
323 rename = getrenamed(fn, ctx.rev())
324 if rename:
324 if rename:
325 copies.append((fn, rename))
325 copies.append((fn, rename))
326
326 return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
327 copies = util.sortdict(copies)
327 copies)
328 return compatdict(context, mapping, 'file_copy', copies,
329 key='name', value='source', fmt='%s (%s)',
330 plural='file_copies')
331
328
332 # showfilecopiesswitch() displays file copies only if copy records are
329 # showfilecopiesswitch() displays file copies only if copy records are
333 # provided before calling the templater, usually with a --copies
330 # provided before calling the templater, usually with a --copies
334 # command line switch.
331 # command line switch.
335 @templatekeyword('file_copies_switch', requires={'revcache'})
332 @templatekeyword('file_copies_switch', requires={'revcache'})
336 def showfilecopiesswitch(context, mapping):
333 def showfilecopiesswitch(context, mapping):
337 """List of strings. Like "file_copies" but displayed
334 """List of strings. Like "file_copies" but displayed
338 only if the --copied switch is set.
335 only if the --copied switch is set.
339 """
336 """
340 copies = context.resource(mapping, 'revcache').get('copies') or []
337 copies = context.resource(mapping, 'revcache').get('copies') or []
341 copies = util.sortdict(copies)
338 return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
342 return compatdict(context, mapping, 'file_copy', copies,
339 copies)
343 key='name', value='source', fmt='%s (%s)',
344 plural='file_copies')
345
340
346 @templatekeyword('file_dels', requires={'ctx', 'revcache'})
341 @templatekeyword('file_dels', requires={'ctx', 'revcache'})
347 def showfiledels(context, mapping):
342 def showfiledels(context, mapping):
348 """List of strings. Files removed by this changeset."""
343 """List of strings. Files removed by this changeset."""
349 return _showfilesbystat(context, mapping, 'file_del', 2)
344 return _showfilesbystat(context, mapping, 'file_del', 2)
350
345
351 @templatekeyword('file_mods', requires={'ctx', 'revcache'})
346 @templatekeyword('file_mods', requires={'ctx', 'revcache'})
352 def showfilemods(context, mapping):
347 def showfilemods(context, mapping):
353 """List of strings. Files modified by this changeset."""
348 """List of strings. Files modified by this changeset."""
354 return _showfilesbystat(context, mapping, 'file_mod', 0)
349 return _showfilesbystat(context, mapping, 'file_mod', 0)
355
350
356 @templatekeyword('files', requires={'ctx'})
351 @templatekeyword('files', requires={'ctx'})
357 def showfiles(context, mapping):
352 def showfiles(context, mapping):
358 """List of strings. All files modified, added, or removed by this
353 """List of strings. All files modified, added, or removed by this
359 changeset.
354 changeset.
360 """
355 """
361 ctx = context.resource(mapping, 'ctx')
356 ctx = context.resource(mapping, 'ctx')
362 return templateutil.compatfileslist(context, mapping, 'file', ctx.files())
357 return templateutil.compatfileslist(context, mapping, 'file', ctx.files())
363
358
364 @templatekeyword('graphnode', requires={'repo', 'ctx'})
359 @templatekeyword('graphnode', requires={'repo', 'ctx'})
365 def showgraphnode(context, mapping):
360 def showgraphnode(context, mapping):
366 """String. The character representing the changeset node in an ASCII
361 """String. The character representing the changeset node in an ASCII
367 revision graph."""
362 revision graph."""
368 repo = context.resource(mapping, 'repo')
363 repo = context.resource(mapping, 'repo')
369 ctx = context.resource(mapping, 'ctx')
364 ctx = context.resource(mapping, 'ctx')
370 return getgraphnode(repo, ctx)
365 return getgraphnode(repo, ctx)
371
366
372 def getgraphnode(repo, ctx):
367 def getgraphnode(repo, ctx):
373 return getgraphnodecurrent(repo, ctx) or getgraphnodesymbol(ctx)
368 return getgraphnodecurrent(repo, ctx) or getgraphnodesymbol(ctx)
374
369
375 def getgraphnodecurrent(repo, ctx):
370 def getgraphnodecurrent(repo, ctx):
376 wpnodes = repo.dirstate.parents()
371 wpnodes = repo.dirstate.parents()
377 if wpnodes[1] == nullid:
372 if wpnodes[1] == nullid:
378 wpnodes = wpnodes[:1]
373 wpnodes = wpnodes[:1]
379 if ctx.node() in wpnodes:
374 if ctx.node() in wpnodes:
380 return '@'
375 return '@'
381 else:
376 else:
382 return ''
377 return ''
383
378
384 def getgraphnodesymbol(ctx):
379 def getgraphnodesymbol(ctx):
385 if ctx.obsolete():
380 if ctx.obsolete():
386 return 'x'
381 return 'x'
387 elif ctx.isunstable():
382 elif ctx.isunstable():
388 return '*'
383 return '*'
389 elif ctx.closesbranch():
384 elif ctx.closesbranch():
390 return '_'
385 return '_'
391 else:
386 else:
392 return 'o'
387 return 'o'
393
388
394 @templatekeyword('graphwidth', requires=())
389 @templatekeyword('graphwidth', requires=())
395 def showgraphwidth(context, mapping):
390 def showgraphwidth(context, mapping):
396 """Integer. The width of the graph drawn by 'log --graph' or zero."""
391 """Integer. The width of the graph drawn by 'log --graph' or zero."""
397 # just hosts documentation; should be overridden by template mapping
392 # just hosts documentation; should be overridden by template mapping
398 return 0
393 return 0
399
394
400 @templatekeyword('index', requires=())
395 @templatekeyword('index', requires=())
401 def showindex(context, mapping):
396 def showindex(context, mapping):
402 """Integer. The current iteration of the loop. (0 indexed)"""
397 """Integer. The current iteration of the loop. (0 indexed)"""
403 # just hosts documentation; should be overridden by template mapping
398 # just hosts documentation; should be overridden by template mapping
404 raise error.Abort(_("can't use index in this context"))
399 raise error.Abort(_("can't use index in this context"))
405
400
406 @templatekeyword('latesttag', requires={'repo', 'ctx', 'cache'})
401 @templatekeyword('latesttag', requires={'repo', 'ctx', 'cache'})
407 def showlatesttag(context, mapping):
402 def showlatesttag(context, mapping):
408 """List of strings. The global tags on the most recent globally
403 """List of strings. The global tags on the most recent globally
409 tagged ancestor of this changeset. If no such tags exist, the list
404 tagged ancestor of this changeset. If no such tags exist, the list
410 consists of the single string "null".
405 consists of the single string "null".
411 """
406 """
412 return showlatesttags(context, mapping, None)
407 return showlatesttags(context, mapping, None)
413
408
414 def showlatesttags(context, mapping, pattern):
409 def showlatesttags(context, mapping, pattern):
415 """helper method for the latesttag keyword and function"""
410 """helper method for the latesttag keyword and function"""
416 latesttags = getlatesttags(context, mapping, pattern)
411 latesttags = getlatesttags(context, mapping, pattern)
417
412
418 # latesttag[0] is an implementation detail for sorting csets on different
413 # latesttag[0] is an implementation detail for sorting csets on different
419 # branches in a stable manner- it is the date the tagged cset was created,
414 # branches in a stable manner- it is the date the tagged cset was created,
420 # not the date the tag was created. Therefore it isn't made visible here.
415 # not the date the tag was created. Therefore it isn't made visible here.
421 makemap = lambda v: {
416 makemap = lambda v: {
422 'changes': _showchangessincetag,
417 'changes': _showchangessincetag,
423 'distance': latesttags[1],
418 'distance': latesttags[1],
424 'latesttag': v, # BC with {latesttag % '{latesttag}'}
419 'latesttag': v, # BC with {latesttag % '{latesttag}'}
425 'tag': v
420 'tag': v
426 }
421 }
427
422
428 tags = latesttags[2]
423 tags = latesttags[2]
429 f = _showcompatlist(context, mapping, 'latesttag', tags, separator=':')
424 f = _showcompatlist(context, mapping, 'latesttag', tags, separator=':')
430 return _hybrid(f, tags, makemap, pycompat.identity)
425 return _hybrid(f, tags, makemap, pycompat.identity)
431
426
432 @templatekeyword('latesttagdistance', requires={'repo', 'ctx', 'cache'})
427 @templatekeyword('latesttagdistance', requires={'repo', 'ctx', 'cache'})
433 def showlatesttagdistance(context, mapping):
428 def showlatesttagdistance(context, mapping):
434 """Integer. Longest path to the latest tag."""
429 """Integer. Longest path to the latest tag."""
435 return getlatesttags(context, mapping)[1]
430 return getlatesttags(context, mapping)[1]
436
431
437 @templatekeyword('changessincelatesttag', requires={'repo', 'ctx', 'cache'})
432 @templatekeyword('changessincelatesttag', requires={'repo', 'ctx', 'cache'})
438 def showchangessincelatesttag(context, mapping):
433 def showchangessincelatesttag(context, mapping):
439 """Integer. All ancestors not in the latest tag."""
434 """Integer. All ancestors not in the latest tag."""
440 tag = getlatesttags(context, mapping)[2][0]
435 tag = getlatesttags(context, mapping)[2][0]
441 mapping = context.overlaymap(mapping, {'tag': tag})
436 mapping = context.overlaymap(mapping, {'tag': tag})
442 return _showchangessincetag(context, mapping)
437 return _showchangessincetag(context, mapping)
443
438
444 def _showchangessincetag(context, mapping):
439 def _showchangessincetag(context, mapping):
445 repo = context.resource(mapping, 'repo')
440 repo = context.resource(mapping, 'repo')
446 ctx = context.resource(mapping, 'ctx')
441 ctx = context.resource(mapping, 'ctx')
447 offset = 0
442 offset = 0
448 revs = [ctx.rev()]
443 revs = [ctx.rev()]
449 tag = context.symbol(mapping, 'tag')
444 tag = context.symbol(mapping, 'tag')
450
445
451 # The only() revset doesn't currently support wdir()
446 # The only() revset doesn't currently support wdir()
452 if ctx.rev() is None:
447 if ctx.rev() is None:
453 offset = 1
448 offset = 1
454 revs = [p.rev() for p in ctx.parents()]
449 revs = [p.rev() for p in ctx.parents()]
455
450
456 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
451 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
457
452
458 # teach templater latesttags.changes is switched to (context, mapping) API
453 # teach templater latesttags.changes is switched to (context, mapping) API
459 _showchangessincetag._requires = {'repo', 'ctx'}
454 _showchangessincetag._requires = {'repo', 'ctx'}
460
455
461 @templatekeyword('manifest', requires={'repo', 'ctx'})
456 @templatekeyword('manifest', requires={'repo', 'ctx'})
462 def showmanifest(context, mapping):
457 def showmanifest(context, mapping):
463 repo = context.resource(mapping, 'repo')
458 repo = context.resource(mapping, 'repo')
464 ctx = context.resource(mapping, 'ctx')
459 ctx = context.resource(mapping, 'ctx')
465 mnode = ctx.manifestnode()
460 mnode = ctx.manifestnode()
466 if mnode is None:
461 if mnode is None:
467 # just avoid crash, we might want to use the 'ff...' hash in future
462 # just avoid crash, we might want to use the 'ff...' hash in future
468 return
463 return
469 mrev = repo.manifestlog.rev(mnode)
464 mrev = repo.manifestlog.rev(mnode)
470 mhex = hex(mnode)
465 mhex = hex(mnode)
471 mapping = context.overlaymap(mapping, {'rev': mrev, 'node': mhex})
466 mapping = context.overlaymap(mapping, {'rev': mrev, 'node': mhex})
472 f = context.process('manifest', mapping)
467 f = context.process('manifest', mapping)
473 # TODO: perhaps 'ctx' should be dropped from mapping because manifest
468 # TODO: perhaps 'ctx' should be dropped from mapping because manifest
474 # rev and node are completely different from changeset's.
469 # rev and node are completely different from changeset's.
475 return templateutil.hybriditem(f, None, f,
470 return templateutil.hybriditem(f, None, f,
476 lambda x: {'rev': mrev, 'node': mhex})
471 lambda x: {'rev': mrev, 'node': mhex})
477
472
478 @templatekeyword('obsfate', requires={'ui', 'repo', 'ctx'})
473 @templatekeyword('obsfate', requires={'ui', 'repo', 'ctx'})
479 def showobsfate(context, mapping):
474 def showobsfate(context, mapping):
480 # this function returns a list containing pre-formatted obsfate strings.
475 # this function returns a list containing pre-formatted obsfate strings.
481 #
476 #
482 # This function will be replaced by templates fragments when we will have
477 # This function will be replaced by templates fragments when we will have
483 # the verbosity templatekw available.
478 # the verbosity templatekw available.
484 succsandmarkers = showsuccsandmarkers(context, mapping)
479 succsandmarkers = showsuccsandmarkers(context, mapping)
485
480
486 ui = context.resource(mapping, 'ui')
481 ui = context.resource(mapping, 'ui')
487 repo = context.resource(mapping, 'repo')
482 repo = context.resource(mapping, 'repo')
488 values = []
483 values = []
489
484
490 for x in succsandmarkers.tovalue(context, mapping):
485 for x in succsandmarkers.tovalue(context, mapping):
491 v = obsutil.obsfateprinter(ui, repo, x['successors'], x['markers'],
486 v = obsutil.obsfateprinter(ui, repo, x['successors'], x['markers'],
492 scmutil.formatchangeid)
487 scmutil.formatchangeid)
493 values.append(v)
488 values.append(v)
494
489
495 return compatlist(context, mapping, "fate", values)
490 return compatlist(context, mapping, "fate", values)
496
491
497 def shownames(context, mapping, namespace):
492 def shownames(context, mapping, namespace):
498 """helper method to generate a template keyword for a namespace"""
493 """helper method to generate a template keyword for a namespace"""
499 repo = context.resource(mapping, 'repo')
494 repo = context.resource(mapping, 'repo')
500 ctx = context.resource(mapping, 'ctx')
495 ctx = context.resource(mapping, 'ctx')
501 ns = repo.names[namespace]
496 ns = repo.names[namespace]
502 names = ns.names(repo, ctx.node())
497 names = ns.names(repo, ctx.node())
503 return compatlist(context, mapping, ns.templatename, names,
498 return compatlist(context, mapping, ns.templatename, names,
504 plural=namespace)
499 plural=namespace)
505
500
506 @templatekeyword('namespaces', requires={'repo', 'ctx'})
501 @templatekeyword('namespaces', requires={'repo', 'ctx'})
507 def shownamespaces(context, mapping):
502 def shownamespaces(context, mapping):
508 """Dict of lists. Names attached to this changeset per
503 """Dict of lists. Names attached to this changeset per
509 namespace."""
504 namespace."""
510 repo = context.resource(mapping, 'repo')
505 repo = context.resource(mapping, 'repo')
511 ctx = context.resource(mapping, 'ctx')
506 ctx = context.resource(mapping, 'ctx')
512
507
513 namespaces = util.sortdict()
508 namespaces = util.sortdict()
514 def makensmapfn(ns):
509 def makensmapfn(ns):
515 # 'name' for iterating over namespaces, templatename for local reference
510 # 'name' for iterating over namespaces, templatename for local reference
516 return lambda v: {'name': v, ns.templatename: v}
511 return lambda v: {'name': v, ns.templatename: v}
517
512
518 for k, ns in repo.names.iteritems():
513 for k, ns in repo.names.iteritems():
519 names = ns.names(repo, ctx.node())
514 names = ns.names(repo, ctx.node())
520 f = _showcompatlist(context, mapping, 'name', names)
515 f = _showcompatlist(context, mapping, 'name', names)
521 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
516 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
522
517
523 f = _showcompatlist(context, mapping, 'namespace', list(namespaces))
518 f = _showcompatlist(context, mapping, 'namespace', list(namespaces))
524
519
525 def makemap(ns):
520 def makemap(ns):
526 return {
521 return {
527 'namespace': ns,
522 'namespace': ns,
528 'names': namespaces[ns],
523 'names': namespaces[ns],
529 'builtin': repo.names[ns].builtin,
524 'builtin': repo.names[ns].builtin,
530 'colorname': repo.names[ns].colorname,
525 'colorname': repo.names[ns].colorname,
531 }
526 }
532
527
533 return _hybrid(f, namespaces, makemap, pycompat.identity)
528 return _hybrid(f, namespaces, makemap, pycompat.identity)
534
529
535 @templatekeyword('node', requires={'ctx'})
530 @templatekeyword('node', requires={'ctx'})
536 def shownode(context, mapping):
531 def shownode(context, mapping):
537 """String. The changeset identification hash, as a 40 hexadecimal
532 """String. The changeset identification hash, as a 40 hexadecimal
538 digit string.
533 digit string.
539 """
534 """
540 ctx = context.resource(mapping, 'ctx')
535 ctx = context.resource(mapping, 'ctx')
541 return ctx.hex()
536 return ctx.hex()
542
537
543 @templatekeyword('obsolete', requires={'ctx'})
538 @templatekeyword('obsolete', requires={'ctx'})
544 def showobsolete(context, mapping):
539 def showobsolete(context, mapping):
545 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
540 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
546 ctx = context.resource(mapping, 'ctx')
541 ctx = context.resource(mapping, 'ctx')
547 if ctx.obsolete():
542 if ctx.obsolete():
548 return 'obsolete'
543 return 'obsolete'
549 return ''
544 return ''
550
545
551 @templatekeyword('peerurls', requires={'repo'})
546 @templatekeyword('peerurls', requires={'repo'})
552 def showpeerurls(context, mapping):
547 def showpeerurls(context, mapping):
553 """A dictionary of repository locations defined in the [paths] section
548 """A dictionary of repository locations defined in the [paths] section
554 of your configuration file."""
549 of your configuration file."""
555 repo = context.resource(mapping, 'repo')
550 repo = context.resource(mapping, 'repo')
556 # see commands.paths() for naming of dictionary keys
551 # see commands.paths() for naming of dictionary keys
557 paths = repo.ui.paths
552 paths = repo.ui.paths
558 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
553 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
559 def makemap(k):
554 def makemap(k):
560 p = paths[k]
555 p = paths[k]
561 d = {'name': k, 'url': p.rawloc}
556 d = {'name': k, 'url': p.rawloc}
562 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
557 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
563 return d
558 return d
564 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
559 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
565
560
566 @templatekeyword("predecessors", requires={'repo', 'ctx'})
561 @templatekeyword("predecessors", requires={'repo', 'ctx'})
567 def showpredecessors(context, mapping):
562 def showpredecessors(context, mapping):
568 """Returns the list if the closest visible successors. (EXPERIMENTAL)"""
563 """Returns the list if the closest visible successors. (EXPERIMENTAL)"""
569 repo = context.resource(mapping, 'repo')
564 repo = context.resource(mapping, 'repo')
570 ctx = context.resource(mapping, 'ctx')
565 ctx = context.resource(mapping, 'ctx')
571 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
566 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
572 predecessors = pycompat.maplist(hex, predecessors)
567 predecessors = pycompat.maplist(hex, predecessors)
573
568
574 return _hybrid(None, predecessors,
569 return _hybrid(None, predecessors,
575 lambda x: {'ctx': repo[x]},
570 lambda x: {'ctx': repo[x]},
576 lambda x: scmutil.formatchangeid(repo[x]))
571 lambda x: scmutil.formatchangeid(repo[x]))
577
572
578 @templatekeyword('reporoot', requires={'repo'})
573 @templatekeyword('reporoot', requires={'repo'})
579 def showreporoot(context, mapping):
574 def showreporoot(context, mapping):
580 """String. The root directory of the current repository."""
575 """String. The root directory of the current repository."""
581 repo = context.resource(mapping, 'repo')
576 repo = context.resource(mapping, 'repo')
582 return repo.root
577 return repo.root
583
578
584 @templatekeyword("successorssets", requires={'repo', 'ctx'})
579 @templatekeyword("successorssets", requires={'repo', 'ctx'})
585 def showsuccessorssets(context, mapping):
580 def showsuccessorssets(context, mapping):
586 """Returns a string of sets of successors for a changectx. Format used
581 """Returns a string of sets of successors for a changectx. Format used
587 is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
582 is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
588 while also diverged into ctx3. (EXPERIMENTAL)"""
583 while also diverged into ctx3. (EXPERIMENTAL)"""
589 repo = context.resource(mapping, 'repo')
584 repo = context.resource(mapping, 'repo')
590 ctx = context.resource(mapping, 'ctx')
585 ctx = context.resource(mapping, 'ctx')
591 if not ctx.obsolete():
586 if not ctx.obsolete():
592 return ''
587 return ''
593
588
594 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
589 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
595 ssets = [[hex(n) for n in ss] for ss in ssets]
590 ssets = [[hex(n) for n in ss] for ss in ssets]
596
591
597 data = []
592 data = []
598 for ss in ssets:
593 for ss in ssets:
599 h = _hybrid(None, ss, lambda x: {'ctx': repo[x]},
594 h = _hybrid(None, ss, lambda x: {'ctx': repo[x]},
600 lambda x: scmutil.formatchangeid(repo[x]))
595 lambda x: scmutil.formatchangeid(repo[x]))
601 data.append(h)
596 data.append(h)
602
597
603 # Format the successorssets
598 # Format the successorssets
604 def render(d):
599 def render(d):
605 return templateutil.stringify(context, mapping, d)
600 return templateutil.stringify(context, mapping, d)
606
601
607 def gen(data):
602 def gen(data):
608 yield "; ".join(render(d) for d in data)
603 yield "; ".join(render(d) for d in data)
609
604
610 return _hybrid(gen(data), data, lambda x: {'successorset': x},
605 return _hybrid(gen(data), data, lambda x: {'successorset': x},
611 pycompat.identity)
606 pycompat.identity)
612
607
613 @templatekeyword("succsandmarkers", requires={'repo', 'ctx'})
608 @templatekeyword("succsandmarkers", requires={'repo', 'ctx'})
614 def showsuccsandmarkers(context, mapping):
609 def showsuccsandmarkers(context, mapping):
615 """Returns a list of dict for each final successor of ctx. The dict
610 """Returns a list of dict for each final successor of ctx. The dict
616 contains successors node id in "successors" keys and the list of
611 contains successors node id in "successors" keys and the list of
617 obs-markers from ctx to the set of successors in "markers".
612 obs-markers from ctx to the set of successors in "markers".
618 (EXPERIMENTAL)
613 (EXPERIMENTAL)
619 """
614 """
620 repo = context.resource(mapping, 'repo')
615 repo = context.resource(mapping, 'repo')
621 ctx = context.resource(mapping, 'ctx')
616 ctx = context.resource(mapping, 'ctx')
622
617
623 values = obsutil.successorsandmarkers(repo, ctx)
618 values = obsutil.successorsandmarkers(repo, ctx)
624
619
625 if values is None:
620 if values is None:
626 values = []
621 values = []
627
622
628 # Format successors and markers to avoid exposing binary to templates
623 # Format successors and markers to avoid exposing binary to templates
629 data = []
624 data = []
630 for i in values:
625 for i in values:
631 # Format successors
626 # Format successors
632 successors = i['successors']
627 successors = i['successors']
633
628
634 successors = [hex(n) for n in successors]
629 successors = [hex(n) for n in successors]
635 successors = _hybrid(None, successors,
630 successors = _hybrid(None, successors,
636 lambda x: {'ctx': repo[x]},
631 lambda x: {'ctx': repo[x]},
637 lambda x: scmutil.formatchangeid(repo[x]))
632 lambda x: scmutil.formatchangeid(repo[x]))
638
633
639 # Format markers
634 # Format markers
640 finalmarkers = []
635 finalmarkers = []
641 for m in i['markers']:
636 for m in i['markers']:
642 hexprec = hex(m[0])
637 hexprec = hex(m[0])
643 hexsucs = tuple(hex(n) for n in m[1])
638 hexsucs = tuple(hex(n) for n in m[1])
644 hexparents = None
639 hexparents = None
645 if m[5] is not None:
640 if m[5] is not None:
646 hexparents = tuple(hex(n) for n in m[5])
641 hexparents = tuple(hex(n) for n in m[5])
647 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
642 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
648 finalmarkers.append(newmarker)
643 finalmarkers.append(newmarker)
649
644
650 data.append({'successors': successors, 'markers': finalmarkers})
645 data.append({'successors': successors, 'markers': finalmarkers})
651
646
652 return templateutil.mappinglist(data)
647 return templateutil.mappinglist(data)
653
648
654 @templatekeyword('p1rev', requires={'ctx'})
649 @templatekeyword('p1rev', requires={'ctx'})
655 def showp1rev(context, mapping):
650 def showp1rev(context, mapping):
656 """Integer. The repository-local revision number of the changeset's
651 """Integer. The repository-local revision number of the changeset's
657 first parent, or -1 if the changeset has no parents."""
652 first parent, or -1 if the changeset has no parents."""
658 ctx = context.resource(mapping, 'ctx')
653 ctx = context.resource(mapping, 'ctx')
659 return ctx.p1().rev()
654 return ctx.p1().rev()
660
655
661 @templatekeyword('p2rev', requires={'ctx'})
656 @templatekeyword('p2rev', requires={'ctx'})
662 def showp2rev(context, mapping):
657 def showp2rev(context, mapping):
663 """Integer. The repository-local revision number of the changeset's
658 """Integer. The repository-local revision number of the changeset's
664 second parent, or -1 if the changeset has no second parent."""
659 second parent, or -1 if the changeset has no second parent."""
665 ctx = context.resource(mapping, 'ctx')
660 ctx = context.resource(mapping, 'ctx')
666 return ctx.p2().rev()
661 return ctx.p2().rev()
667
662
668 @templatekeyword('p1node', requires={'ctx'})
663 @templatekeyword('p1node', requires={'ctx'})
669 def showp1node(context, mapping):
664 def showp1node(context, mapping):
670 """String. The identification hash of the changeset's first parent,
665 """String. The identification hash of the changeset's first parent,
671 as a 40 digit hexadecimal string. If the changeset has no parents, all
666 as a 40 digit hexadecimal string. If the changeset has no parents, all
672 digits are 0."""
667 digits are 0."""
673 ctx = context.resource(mapping, 'ctx')
668 ctx = context.resource(mapping, 'ctx')
674 return ctx.p1().hex()
669 return ctx.p1().hex()
675
670
676 @templatekeyword('p2node', requires={'ctx'})
671 @templatekeyword('p2node', requires={'ctx'})
677 def showp2node(context, mapping):
672 def showp2node(context, mapping):
678 """String. The identification hash of the changeset's second
673 """String. The identification hash of the changeset's second
679 parent, as a 40 digit hexadecimal string. If the changeset has no second
674 parent, as a 40 digit hexadecimal string. If the changeset has no second
680 parent, all digits are 0."""
675 parent, all digits are 0."""
681 ctx = context.resource(mapping, 'ctx')
676 ctx = context.resource(mapping, 'ctx')
682 return ctx.p2().hex()
677 return ctx.p2().hex()
683
678
684 @templatekeyword('parents', requires={'repo', 'ctx'})
679 @templatekeyword('parents', requires={'repo', 'ctx'})
685 def showparents(context, mapping):
680 def showparents(context, mapping):
686 """List of strings. The parents of the changeset in "rev:node"
681 """List of strings. The parents of the changeset in "rev:node"
687 format. If the changeset has only one "natural" parent (the predecessor
682 format. If the changeset has only one "natural" parent (the predecessor
688 revision) nothing is shown."""
683 revision) nothing is shown."""
689 repo = context.resource(mapping, 'repo')
684 repo = context.resource(mapping, 'repo')
690 ctx = context.resource(mapping, 'ctx')
685 ctx = context.resource(mapping, 'ctx')
691 pctxs = scmutil.meaningfulparents(repo, ctx)
686 pctxs = scmutil.meaningfulparents(repo, ctx)
692 prevs = [p.rev() for p in pctxs]
687 prevs = [p.rev() for p in pctxs]
693 parents = [[('rev', p.rev()),
688 parents = [[('rev', p.rev()),
694 ('node', p.hex()),
689 ('node', p.hex()),
695 ('phase', p.phasestr())]
690 ('phase', p.phasestr())]
696 for p in pctxs]
691 for p in pctxs]
697 f = _showcompatlist(context, mapping, 'parent', parents)
692 f = _showcompatlist(context, mapping, 'parent', parents)
698 return _hybrid(f, prevs, lambda x: {'ctx': repo[x]},
693 return _hybrid(f, prevs, lambda x: {'ctx': repo[x]},
699 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
694 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
700
695
701 @templatekeyword('phase', requires={'ctx'})
696 @templatekeyword('phase', requires={'ctx'})
702 def showphase(context, mapping):
697 def showphase(context, mapping):
703 """String. The changeset phase name."""
698 """String. The changeset phase name."""
704 ctx = context.resource(mapping, 'ctx')
699 ctx = context.resource(mapping, 'ctx')
705 return ctx.phasestr()
700 return ctx.phasestr()
706
701
707 @templatekeyword('phaseidx', requires={'ctx'})
702 @templatekeyword('phaseidx', requires={'ctx'})
708 def showphaseidx(context, mapping):
703 def showphaseidx(context, mapping):
709 """Integer. The changeset phase index. (ADVANCED)"""
704 """Integer. The changeset phase index. (ADVANCED)"""
710 ctx = context.resource(mapping, 'ctx')
705 ctx = context.resource(mapping, 'ctx')
711 return ctx.phase()
706 return ctx.phase()
712
707
713 @templatekeyword('rev', requires={'ctx'})
708 @templatekeyword('rev', requires={'ctx'})
714 def showrev(context, mapping):
709 def showrev(context, mapping):
715 """Integer. The repository-local changeset revision number."""
710 """Integer. The repository-local changeset revision number."""
716 ctx = context.resource(mapping, 'ctx')
711 ctx = context.resource(mapping, 'ctx')
717 return scmutil.intrev(ctx)
712 return scmutil.intrev(ctx)
718
713
719 def showrevslist(context, mapping, name, revs):
714 def showrevslist(context, mapping, name, revs):
720 """helper to generate a list of revisions in which a mapped template will
715 """helper to generate a list of revisions in which a mapped template will
721 be evaluated"""
716 be evaluated"""
722 repo = context.resource(mapping, 'repo')
717 repo = context.resource(mapping, 'repo')
723 f = _showcompatlist(context, mapping, name, ['%d' % r for r in revs])
718 f = _showcompatlist(context, mapping, name, ['%d' % r for r in revs])
724 return _hybrid(f, revs,
719 return _hybrid(f, revs,
725 lambda x: {name: x, 'ctx': repo[x]},
720 lambda x: {name: x, 'ctx': repo[x]},
726 pycompat.identity, keytype=int)
721 pycompat.identity, keytype=int)
727
722
728 @templatekeyword('subrepos', requires={'ctx'})
723 @templatekeyword('subrepos', requires={'ctx'})
729 def showsubrepos(context, mapping):
724 def showsubrepos(context, mapping):
730 """List of strings. Updated subrepositories in the changeset."""
725 """List of strings. Updated subrepositories in the changeset."""
731 ctx = context.resource(mapping, 'ctx')
726 ctx = context.resource(mapping, 'ctx')
732 substate = ctx.substate
727 substate = ctx.substate
733 if not substate:
728 if not substate:
734 return compatlist(context, mapping, 'subrepo', [])
729 return compatlist(context, mapping, 'subrepo', [])
735 psubstate = ctx.parents()[0].substate or {}
730 psubstate = ctx.parents()[0].substate or {}
736 subrepos = []
731 subrepos = []
737 for sub in substate:
732 for sub in substate:
738 if sub not in psubstate or substate[sub] != psubstate[sub]:
733 if sub not in psubstate or substate[sub] != psubstate[sub]:
739 subrepos.append(sub) # modified or newly added in ctx
734 subrepos.append(sub) # modified or newly added in ctx
740 for sub in psubstate:
735 for sub in psubstate:
741 if sub not in substate:
736 if sub not in substate:
742 subrepos.append(sub) # removed in ctx
737 subrepos.append(sub) # removed in ctx
743 return compatlist(context, mapping, 'subrepo', sorted(subrepos))
738 return compatlist(context, mapping, 'subrepo', sorted(subrepos))
744
739
745 # don't remove "showtags" definition, even though namespaces will put
740 # don't remove "showtags" definition, even though namespaces will put
746 # a helper function for "tags" keyword into "keywords" map automatically,
741 # a helper function for "tags" keyword into "keywords" map automatically,
747 # because online help text is built without namespaces initialization
742 # because online help text is built without namespaces initialization
748 @templatekeyword('tags', requires={'repo', 'ctx'})
743 @templatekeyword('tags', requires={'repo', 'ctx'})
749 def showtags(context, mapping):
744 def showtags(context, mapping):
750 """List of strings. Any tags associated with the changeset."""
745 """List of strings. Any tags associated with the changeset."""
751 return shownames(context, mapping, 'tags')
746 return shownames(context, mapping, 'tags')
752
747
753 @templatekeyword('termwidth', requires={'ui'})
748 @templatekeyword('termwidth', requires={'ui'})
754 def showtermwidth(context, mapping):
749 def showtermwidth(context, mapping):
755 """Integer. The width of the current terminal."""
750 """Integer. The width of the current terminal."""
756 ui = context.resource(mapping, 'ui')
751 ui = context.resource(mapping, 'ui')
757 return ui.termwidth()
752 return ui.termwidth()
758
753
759 @templatekeyword('user', requires={'ctx'})
754 @templatekeyword('user', requires={'ctx'})
760 def showuser(context, mapping):
755 def showuser(context, mapping):
761 """String. The unmodified author of the changeset."""
756 """String. The unmodified author of the changeset."""
762 ctx = context.resource(mapping, 'ctx')
757 ctx = context.resource(mapping, 'ctx')
763 return ctx.user()
758 return ctx.user()
764
759
765 @templatekeyword('instabilities', requires={'ctx'})
760 @templatekeyword('instabilities', requires={'ctx'})
766 def showinstabilities(context, mapping):
761 def showinstabilities(context, mapping):
767 """List of strings. Evolution instabilities affecting the changeset.
762 """List of strings. Evolution instabilities affecting the changeset.
768 (EXPERIMENTAL)
763 (EXPERIMENTAL)
769 """
764 """
770 ctx = context.resource(mapping, 'ctx')
765 ctx = context.resource(mapping, 'ctx')
771 return compatlist(context, mapping, 'instability', ctx.instabilities(),
766 return compatlist(context, mapping, 'instability', ctx.instabilities(),
772 plural='instabilities')
767 plural='instabilities')
773
768
774 @templatekeyword('verbosity', requires={'ui'})
769 @templatekeyword('verbosity', requires={'ui'})
775 def showverbosity(context, mapping):
770 def showverbosity(context, mapping):
776 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
771 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
777 or ''."""
772 or ''."""
778 ui = context.resource(mapping, 'ui')
773 ui = context.resource(mapping, 'ui')
779 # see logcmdutil.changesettemplater for priority of these flags
774 # see logcmdutil.changesettemplater for priority of these flags
780 if ui.debugflag:
775 if ui.debugflag:
781 return 'debug'
776 return 'debug'
782 elif ui.quiet:
777 elif ui.quiet:
783 return 'quiet'
778 return 'quiet'
784 elif ui.verbose:
779 elif ui.verbose:
785 return 'verbose'
780 return 'verbose'
786 return ''
781 return ''
787
782
788 @templatekeyword('whyunstable', requires={'repo', 'ctx'})
783 @templatekeyword('whyunstable', requires={'repo', 'ctx'})
789 def showwhyunstable(context, mapping):
784 def showwhyunstable(context, mapping):
790 """List of dicts explaining all instabilities of a changeset.
785 """List of dicts explaining all instabilities of a changeset.
791 (EXPERIMENTAL)
786 (EXPERIMENTAL)
792 """
787 """
793 repo = context.resource(mapping, 'repo')
788 repo = context.resource(mapping, 'repo')
794 ctx = context.resource(mapping, 'ctx')
789 ctx = context.resource(mapping, 'ctx')
795
790
796 def formatnode(ctx):
791 def formatnode(ctx):
797 return '%s (%s)' % (scmutil.formatchangeid(ctx), ctx.phasestr())
792 return '%s (%s)' % (scmutil.formatchangeid(ctx), ctx.phasestr())
798
793
799 entries = obsutil.whyunstable(repo, ctx)
794 entries = obsutil.whyunstable(repo, ctx)
800
795
801 for entry in entries:
796 for entry in entries:
802 if entry.get('divergentnodes'):
797 if entry.get('divergentnodes'):
803 dnodes = entry['divergentnodes']
798 dnodes = entry['divergentnodes']
804 dnhybrid = _hybrid(None, [dnode.hex() for dnode in dnodes],
799 dnhybrid = _hybrid(None, [dnode.hex() for dnode in dnodes],
805 lambda x: {'ctx': repo[x]},
800 lambda x: {'ctx': repo[x]},
806 lambda x: formatnode(repo[x]))
801 lambda x: formatnode(repo[x]))
807 entry['divergentnodes'] = dnhybrid
802 entry['divergentnodes'] = dnhybrid
808
803
809 tmpl = ('{instability}:{if(divergentnodes, " ")}{divergentnodes} '
804 tmpl = ('{instability}:{if(divergentnodes, " ")}{divergentnodes} '
810 '{reason} {node|short}')
805 '{reason} {node|short}')
811 return templateutil.mappinglist(entries, tmpl=tmpl, sep='\n')
806 return templateutil.mappinglist(entries, tmpl=tmpl, sep='\n')
812
807
813 def loadkeyword(ui, extname, registrarobj):
808 def loadkeyword(ui, extname, registrarobj):
814 """Load template keyword from specified registrarobj
809 """Load template keyword from specified registrarobj
815 """
810 """
816 for name, func in registrarobj._table.iteritems():
811 for name, func in registrarobj._table.iteritems():
817 keywords[name] = func
812 keywords[name] = func
818
813
819 # tell hggettext to extract docstrings from these functions:
814 # tell hggettext to extract docstrings from these functions:
820 i18nfunctions = keywords.values()
815 i18nfunctions = keywords.values()
@@ -1,948 +1,963 b''
1 # templateutil.py - utility for template evaluation
1 # templateutil.py - utility for template evaluation
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 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 import abc
10 import abc
11 import types
11 import types
12
12
13 from .i18n import _
13 from .i18n import _
14 from . import (
14 from . import (
15 error,
15 error,
16 pycompat,
16 pycompat,
17 util,
17 util,
18 )
18 )
19 from .utils import (
19 from .utils import (
20 dateutil,
20 dateutil,
21 stringutil,
21 stringutil,
22 )
22 )
23
23
24 class ResourceUnavailable(error.Abort):
24 class ResourceUnavailable(error.Abort):
25 pass
25 pass
26
26
27 class TemplateNotFound(error.Abort):
27 class TemplateNotFound(error.Abort):
28 pass
28 pass
29
29
30 class wrapped(object):
30 class wrapped(object):
31 """Object requiring extra conversion prior to displaying or processing
31 """Object requiring extra conversion prior to displaying or processing
32 as value
32 as value
33
33
34 Use unwrapvalue() or unwrapastype() to obtain the inner object.
34 Use unwrapvalue() or unwrapastype() to obtain the inner object.
35 """
35 """
36
36
37 __metaclass__ = abc.ABCMeta
37 __metaclass__ = abc.ABCMeta
38
38
39 @abc.abstractmethod
39 @abc.abstractmethod
40 def contains(self, context, mapping, item):
40 def contains(self, context, mapping, item):
41 """Test if the specified item is in self
41 """Test if the specified item is in self
42
42
43 The item argument may be a wrapped object.
43 The item argument may be a wrapped object.
44 """
44 """
45
45
46 @abc.abstractmethod
46 @abc.abstractmethod
47 def getmember(self, context, mapping, key):
47 def getmember(self, context, mapping, key):
48 """Return a member item for the specified key
48 """Return a member item for the specified key
49
49
50 The key argument may be a wrapped object.
50 The key argument may be a wrapped object.
51 A returned object may be either a wrapped object or a pure value
51 A returned object may be either a wrapped object or a pure value
52 depending on the self type.
52 depending on the self type.
53 """
53 """
54
54
55 @abc.abstractmethod
55 @abc.abstractmethod
56 def getmin(self, context, mapping):
56 def getmin(self, context, mapping):
57 """Return the smallest item, which may be either a wrapped or a pure
57 """Return the smallest item, which may be either a wrapped or a pure
58 value depending on the self type"""
58 value depending on the self type"""
59
59
60 @abc.abstractmethod
60 @abc.abstractmethod
61 def getmax(self, context, mapping):
61 def getmax(self, context, mapping):
62 """Return the largest item, which may be either a wrapped or a pure
62 """Return the largest item, which may be either a wrapped or a pure
63 value depending on the self type"""
63 value depending on the self type"""
64
64
65 @abc.abstractmethod
65 @abc.abstractmethod
66 def filter(self, context, mapping, select):
66 def filter(self, context, mapping, select):
67 """Return new container of the same type which includes only the
67 """Return new container of the same type which includes only the
68 selected elements
68 selected elements
69
69
70 select() takes each item as a wrapped object and returns True/False.
70 select() takes each item as a wrapped object and returns True/False.
71 """
71 """
72
72
73 @abc.abstractmethod
73 @abc.abstractmethod
74 def itermaps(self, context):
74 def itermaps(self, context):
75 """Yield each template mapping"""
75 """Yield each template mapping"""
76
76
77 @abc.abstractmethod
77 @abc.abstractmethod
78 def join(self, context, mapping, sep):
78 def join(self, context, mapping, sep):
79 """Join items with the separator; Returns a bytes or (possibly nested)
79 """Join items with the separator; Returns a bytes or (possibly nested)
80 generator of bytes
80 generator of bytes
81
81
82 A pre-configured template may be rendered per item if this container
82 A pre-configured template may be rendered per item if this container
83 holds unprintable items.
83 holds unprintable items.
84 """
84 """
85
85
86 @abc.abstractmethod
86 @abc.abstractmethod
87 def show(self, context, mapping):
87 def show(self, context, mapping):
88 """Return a bytes or (possibly nested) generator of bytes representing
88 """Return a bytes or (possibly nested) generator of bytes representing
89 the underlying object
89 the underlying object
90
90
91 A pre-configured template may be rendered if the underlying object is
91 A pre-configured template may be rendered if the underlying object is
92 not printable.
92 not printable.
93 """
93 """
94
94
95 @abc.abstractmethod
95 @abc.abstractmethod
96 def tobool(self, context, mapping):
96 def tobool(self, context, mapping):
97 """Return a boolean representation of the inner value"""
97 """Return a boolean representation of the inner value"""
98
98
99 @abc.abstractmethod
99 @abc.abstractmethod
100 def tovalue(self, context, mapping):
100 def tovalue(self, context, mapping):
101 """Move the inner value object out or create a value representation
101 """Move the inner value object out or create a value representation
102
102
103 A returned value must be serializable by templaterfilters.json().
103 A returned value must be serializable by templaterfilters.json().
104 """
104 """
105
105
106 class mappable(object):
106 class mappable(object):
107 """Object which can be converted to a single template mapping"""
107 """Object which can be converted to a single template mapping"""
108
108
109 def itermaps(self, context):
109 def itermaps(self, context):
110 yield self.tomap(context)
110 yield self.tomap(context)
111
111
112 @abc.abstractmethod
112 @abc.abstractmethod
113 def tomap(self, context):
113 def tomap(self, context):
114 """Create a single template mapping representing this"""
114 """Create a single template mapping representing this"""
115
115
116 class wrappedbytes(wrapped):
116 class wrappedbytes(wrapped):
117 """Wrapper for byte string"""
117 """Wrapper for byte string"""
118
118
119 def __init__(self, value):
119 def __init__(self, value):
120 self._value = value
120 self._value = value
121
121
122 def contains(self, context, mapping, item):
122 def contains(self, context, mapping, item):
123 item = stringify(context, mapping, item)
123 item = stringify(context, mapping, item)
124 return item in self._value
124 return item in self._value
125
125
126 def getmember(self, context, mapping, key):
126 def getmember(self, context, mapping, key):
127 raise error.ParseError(_('%r is not a dictionary')
127 raise error.ParseError(_('%r is not a dictionary')
128 % pycompat.bytestr(self._value))
128 % pycompat.bytestr(self._value))
129
129
130 def getmin(self, context, mapping):
130 def getmin(self, context, mapping):
131 return self._getby(context, mapping, min)
131 return self._getby(context, mapping, min)
132
132
133 def getmax(self, context, mapping):
133 def getmax(self, context, mapping):
134 return self._getby(context, mapping, max)
134 return self._getby(context, mapping, max)
135
135
136 def _getby(self, context, mapping, func):
136 def _getby(self, context, mapping, func):
137 if not self._value:
137 if not self._value:
138 raise error.ParseError(_('empty string'))
138 raise error.ParseError(_('empty string'))
139 return func(pycompat.iterbytestr(self._value))
139 return func(pycompat.iterbytestr(self._value))
140
140
141 def filter(self, context, mapping, select):
141 def filter(self, context, mapping, select):
142 raise error.ParseError(_('%r is not filterable')
142 raise error.ParseError(_('%r is not filterable')
143 % pycompat.bytestr(self._value))
143 % pycompat.bytestr(self._value))
144
144
145 def itermaps(self, context):
145 def itermaps(self, context):
146 raise error.ParseError(_('%r is not iterable of mappings')
146 raise error.ParseError(_('%r is not iterable of mappings')
147 % pycompat.bytestr(self._value))
147 % pycompat.bytestr(self._value))
148
148
149 def join(self, context, mapping, sep):
149 def join(self, context, mapping, sep):
150 return joinitems(pycompat.iterbytestr(self._value), sep)
150 return joinitems(pycompat.iterbytestr(self._value), sep)
151
151
152 def show(self, context, mapping):
152 def show(self, context, mapping):
153 return self._value
153 return self._value
154
154
155 def tobool(self, context, mapping):
155 def tobool(self, context, mapping):
156 return bool(self._value)
156 return bool(self._value)
157
157
158 def tovalue(self, context, mapping):
158 def tovalue(self, context, mapping):
159 return self._value
159 return self._value
160
160
161 class wrappedvalue(wrapped):
161 class wrappedvalue(wrapped):
162 """Generic wrapper for pure non-list/dict/bytes value"""
162 """Generic wrapper for pure non-list/dict/bytes value"""
163
163
164 def __init__(self, value):
164 def __init__(self, value):
165 self._value = value
165 self._value = value
166
166
167 def contains(self, context, mapping, item):
167 def contains(self, context, mapping, item):
168 raise error.ParseError(_("%r is not iterable") % self._value)
168 raise error.ParseError(_("%r is not iterable") % self._value)
169
169
170 def getmember(self, context, mapping, key):
170 def getmember(self, context, mapping, key):
171 raise error.ParseError(_('%r is not a dictionary') % self._value)
171 raise error.ParseError(_('%r is not a dictionary') % self._value)
172
172
173 def getmin(self, context, mapping):
173 def getmin(self, context, mapping):
174 raise error.ParseError(_("%r is not iterable") % self._value)
174 raise error.ParseError(_("%r is not iterable") % self._value)
175
175
176 def getmax(self, context, mapping):
176 def getmax(self, context, mapping):
177 raise error.ParseError(_("%r is not iterable") % self._value)
177 raise error.ParseError(_("%r is not iterable") % self._value)
178
178
179 def filter(self, context, mapping, select):
179 def filter(self, context, mapping, select):
180 raise error.ParseError(_("%r is not iterable") % self._value)
180 raise error.ParseError(_("%r is not iterable") % self._value)
181
181
182 def itermaps(self, context):
182 def itermaps(self, context):
183 raise error.ParseError(_('%r is not iterable of mappings')
183 raise error.ParseError(_('%r is not iterable of mappings')
184 % self._value)
184 % self._value)
185
185
186 def join(self, context, mapping, sep):
186 def join(self, context, mapping, sep):
187 raise error.ParseError(_('%r is not iterable') % self._value)
187 raise error.ParseError(_('%r is not iterable') % self._value)
188
188
189 def show(self, context, mapping):
189 def show(self, context, mapping):
190 if self._value is None:
190 if self._value is None:
191 return b''
191 return b''
192 return pycompat.bytestr(self._value)
192 return pycompat.bytestr(self._value)
193
193
194 def tobool(self, context, mapping):
194 def tobool(self, context, mapping):
195 if self._value is None:
195 if self._value is None:
196 return False
196 return False
197 if isinstance(self._value, bool):
197 if isinstance(self._value, bool):
198 return self._value
198 return self._value
199 # otherwise evaluate as string, which means 0 is True
199 # otherwise evaluate as string, which means 0 is True
200 return bool(pycompat.bytestr(self._value))
200 return bool(pycompat.bytestr(self._value))
201
201
202 def tovalue(self, context, mapping):
202 def tovalue(self, context, mapping):
203 return self._value
203 return self._value
204
204
205 class date(mappable, wrapped):
205 class date(mappable, wrapped):
206 """Wrapper for date tuple"""
206 """Wrapper for date tuple"""
207
207
208 def __init__(self, value, showfmt='%d %d'):
208 def __init__(self, value, showfmt='%d %d'):
209 # value may be (float, int), but public interface shouldn't support
209 # value may be (float, int), but public interface shouldn't support
210 # floating-point timestamp
210 # floating-point timestamp
211 self._unixtime, self._tzoffset = map(int, value)
211 self._unixtime, self._tzoffset = map(int, value)
212 self._showfmt = showfmt
212 self._showfmt = showfmt
213
213
214 def contains(self, context, mapping, item):
214 def contains(self, context, mapping, item):
215 raise error.ParseError(_('date is not iterable'))
215 raise error.ParseError(_('date is not iterable'))
216
216
217 def getmember(self, context, mapping, key):
217 def getmember(self, context, mapping, key):
218 raise error.ParseError(_('date is not a dictionary'))
218 raise error.ParseError(_('date is not a dictionary'))
219
219
220 def getmin(self, context, mapping):
220 def getmin(self, context, mapping):
221 raise error.ParseError(_('date is not iterable'))
221 raise error.ParseError(_('date is not iterable'))
222
222
223 def getmax(self, context, mapping):
223 def getmax(self, context, mapping):
224 raise error.ParseError(_('date is not iterable'))
224 raise error.ParseError(_('date is not iterable'))
225
225
226 def filter(self, context, mapping, select):
226 def filter(self, context, mapping, select):
227 raise error.ParseError(_('date is not iterable'))
227 raise error.ParseError(_('date is not iterable'))
228
228
229 def join(self, context, mapping, sep):
229 def join(self, context, mapping, sep):
230 raise error.ParseError(_("date is not iterable"))
230 raise error.ParseError(_("date is not iterable"))
231
231
232 def show(self, context, mapping):
232 def show(self, context, mapping):
233 return self._showfmt % (self._unixtime, self._tzoffset)
233 return self._showfmt % (self._unixtime, self._tzoffset)
234
234
235 def tomap(self, context):
235 def tomap(self, context):
236 return {'unixtime': self._unixtime, 'tzoffset': self._tzoffset}
236 return {'unixtime': self._unixtime, 'tzoffset': self._tzoffset}
237
237
238 def tobool(self, context, mapping):
238 def tobool(self, context, mapping):
239 return True
239 return True
240
240
241 def tovalue(self, context, mapping):
241 def tovalue(self, context, mapping):
242 return (self._unixtime, self._tzoffset)
242 return (self._unixtime, self._tzoffset)
243
243
244 class hybrid(wrapped):
244 class hybrid(wrapped):
245 """Wrapper for list or dict to support legacy template
245 """Wrapper for list or dict to support legacy template
246
246
247 This class allows us to handle both:
247 This class allows us to handle both:
248 - "{files}" (legacy command-line-specific list hack) and
248 - "{files}" (legacy command-line-specific list hack) and
249 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
249 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
250 and to access raw values:
250 and to access raw values:
251 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
251 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
252 - "{get(extras, key)}"
252 - "{get(extras, key)}"
253 - "{files|json}"
253 - "{files|json}"
254 """
254 """
255
255
256 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
256 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
257 self._gen = gen # generator or function returning generator
257 self._gen = gen # generator or function returning generator
258 self._values = values
258 self._values = values
259 self._makemap = makemap
259 self._makemap = makemap
260 self._joinfmt = joinfmt
260 self._joinfmt = joinfmt
261 self._keytype = keytype # hint for 'x in y' where type(x) is unresolved
261 self._keytype = keytype # hint for 'x in y' where type(x) is unresolved
262
262
263 def contains(self, context, mapping, item):
263 def contains(self, context, mapping, item):
264 item = unwrapastype(context, mapping, item, self._keytype)
264 item = unwrapastype(context, mapping, item, self._keytype)
265 return item in self._values
265 return item in self._values
266
266
267 def getmember(self, context, mapping, key):
267 def getmember(self, context, mapping, key):
268 # TODO: maybe split hybrid list/dict types?
268 # TODO: maybe split hybrid list/dict types?
269 if not util.safehasattr(self._values, 'get'):
269 if not util.safehasattr(self._values, 'get'):
270 raise error.ParseError(_('not a dictionary'))
270 raise error.ParseError(_('not a dictionary'))
271 key = unwrapastype(context, mapping, key, self._keytype)
271 key = unwrapastype(context, mapping, key, self._keytype)
272 return self._wrapvalue(key, self._values.get(key))
272 return self._wrapvalue(key, self._values.get(key))
273
273
274 def getmin(self, context, mapping):
274 def getmin(self, context, mapping):
275 return self._getby(context, mapping, min)
275 return self._getby(context, mapping, min)
276
276
277 def getmax(self, context, mapping):
277 def getmax(self, context, mapping):
278 return self._getby(context, mapping, max)
278 return self._getby(context, mapping, max)
279
279
280 def _getby(self, context, mapping, func):
280 def _getby(self, context, mapping, func):
281 if not self._values:
281 if not self._values:
282 raise error.ParseError(_('empty sequence'))
282 raise error.ParseError(_('empty sequence'))
283 val = func(self._values)
283 val = func(self._values)
284 return self._wrapvalue(val, val)
284 return self._wrapvalue(val, val)
285
285
286 def _wrapvalue(self, key, val):
286 def _wrapvalue(self, key, val):
287 if val is None:
287 if val is None:
288 return
288 return
289 if util.safehasattr(val, '_makemap'):
289 if util.safehasattr(val, '_makemap'):
290 # a nested hybrid list/dict, which has its own way of map operation
290 # a nested hybrid list/dict, which has its own way of map operation
291 return val
291 return val
292 return hybriditem(None, key, val, self._makemap)
292 return hybriditem(None, key, val, self._makemap)
293
293
294 def filter(self, context, mapping, select):
294 def filter(self, context, mapping, select):
295 if util.safehasattr(self._values, 'get'):
295 if util.safehasattr(self._values, 'get'):
296 values = {k: v for k, v in self._values.iteritems()
296 values = {k: v for k, v in self._values.iteritems()
297 if select(self._wrapvalue(k, v))}
297 if select(self._wrapvalue(k, v))}
298 else:
298 else:
299 values = [v for v in self._values if select(self._wrapvalue(v, v))]
299 values = [v for v in self._values if select(self._wrapvalue(v, v))]
300 return hybrid(None, values, self._makemap, self._joinfmt, self._keytype)
300 return hybrid(None, values, self._makemap, self._joinfmt, self._keytype)
301
301
302 def itermaps(self, context):
302 def itermaps(self, context):
303 makemap = self._makemap
303 makemap = self._makemap
304 for x in self._values:
304 for x in self._values:
305 yield makemap(x)
305 yield makemap(x)
306
306
307 def join(self, context, mapping, sep):
307 def join(self, context, mapping, sep):
308 # TODO: switch gen to (context, mapping) API?
308 # TODO: switch gen to (context, mapping) API?
309 return joinitems((self._joinfmt(x) for x in self._values), sep)
309 return joinitems((self._joinfmt(x) for x in self._values), sep)
310
310
311 def show(self, context, mapping):
311 def show(self, context, mapping):
312 # TODO: switch gen to (context, mapping) API?
312 # TODO: switch gen to (context, mapping) API?
313 gen = self._gen
313 gen = self._gen
314 if gen is None:
314 if gen is None:
315 return self.join(context, mapping, ' ')
315 return self.join(context, mapping, ' ')
316 if callable(gen):
316 if callable(gen):
317 return gen()
317 return gen()
318 return gen
318 return gen
319
319
320 def tobool(self, context, mapping):
320 def tobool(self, context, mapping):
321 return bool(self._values)
321 return bool(self._values)
322
322
323 def tovalue(self, context, mapping):
323 def tovalue(self, context, mapping):
324 # TODO: make it non-recursive for trivial lists/dicts
324 # TODO: make it non-recursive for trivial lists/dicts
325 xs = self._values
325 xs = self._values
326 if util.safehasattr(xs, 'get'):
326 if util.safehasattr(xs, 'get'):
327 return {k: unwrapvalue(context, mapping, v)
327 return {k: unwrapvalue(context, mapping, v)
328 for k, v in xs.iteritems()}
328 for k, v in xs.iteritems()}
329 return [unwrapvalue(context, mapping, x) for x in xs]
329 return [unwrapvalue(context, mapping, x) for x in xs]
330
330
331 class hybriditem(mappable, wrapped):
331 class hybriditem(mappable, wrapped):
332 """Wrapper for non-list/dict object to support map operation
332 """Wrapper for non-list/dict object to support map operation
333
333
334 This class allows us to handle both:
334 This class allows us to handle both:
335 - "{manifest}"
335 - "{manifest}"
336 - "{manifest % '{rev}:{node}'}"
336 - "{manifest % '{rev}:{node}'}"
337 - "{manifest.rev}"
337 - "{manifest.rev}"
338 """
338 """
339
339
340 def __init__(self, gen, key, value, makemap):
340 def __init__(self, gen, key, value, makemap):
341 self._gen = gen # generator or function returning generator
341 self._gen = gen # generator or function returning generator
342 self._key = key
342 self._key = key
343 self._value = value # may be generator of strings
343 self._value = value # may be generator of strings
344 self._makemap = makemap
344 self._makemap = makemap
345
345
346 def tomap(self, context):
346 def tomap(self, context):
347 return self._makemap(self._key)
347 return self._makemap(self._key)
348
348
349 def contains(self, context, mapping, item):
349 def contains(self, context, mapping, item):
350 w = makewrapped(context, mapping, self._value)
350 w = makewrapped(context, mapping, self._value)
351 return w.contains(context, mapping, item)
351 return w.contains(context, mapping, item)
352
352
353 def getmember(self, context, mapping, key):
353 def getmember(self, context, mapping, key):
354 w = makewrapped(context, mapping, self._value)
354 w = makewrapped(context, mapping, self._value)
355 return w.getmember(context, mapping, key)
355 return w.getmember(context, mapping, key)
356
356
357 def getmin(self, context, mapping):
357 def getmin(self, context, mapping):
358 w = makewrapped(context, mapping, self._value)
358 w = makewrapped(context, mapping, self._value)
359 return w.getmin(context, mapping)
359 return w.getmin(context, mapping)
360
360
361 def getmax(self, context, mapping):
361 def getmax(self, context, mapping):
362 w = makewrapped(context, mapping, self._value)
362 w = makewrapped(context, mapping, self._value)
363 return w.getmax(context, mapping)
363 return w.getmax(context, mapping)
364
364
365 def filter(self, context, mapping, select):
365 def filter(self, context, mapping, select):
366 w = makewrapped(context, mapping, self._value)
366 w = makewrapped(context, mapping, self._value)
367 return w.filter(context, mapping, select)
367 return w.filter(context, mapping, select)
368
368
369 def join(self, context, mapping, sep):
369 def join(self, context, mapping, sep):
370 w = makewrapped(context, mapping, self._value)
370 w = makewrapped(context, mapping, self._value)
371 return w.join(context, mapping, sep)
371 return w.join(context, mapping, sep)
372
372
373 def show(self, context, mapping):
373 def show(self, context, mapping):
374 # TODO: switch gen to (context, mapping) API?
374 # TODO: switch gen to (context, mapping) API?
375 gen = self._gen
375 gen = self._gen
376 if gen is None:
376 if gen is None:
377 return pycompat.bytestr(self._value)
377 return pycompat.bytestr(self._value)
378 if callable(gen):
378 if callable(gen):
379 return gen()
379 return gen()
380 return gen
380 return gen
381
381
382 def tobool(self, context, mapping):
382 def tobool(self, context, mapping):
383 w = makewrapped(context, mapping, self._value)
383 w = makewrapped(context, mapping, self._value)
384 return w.tobool(context, mapping)
384 return w.tobool(context, mapping)
385
385
386 def tovalue(self, context, mapping):
386 def tovalue(self, context, mapping):
387 return _unthunk(context, mapping, self._value)
387 return _unthunk(context, mapping, self._value)
388
388
389 class _mappingsequence(wrapped):
389 class _mappingsequence(wrapped):
390 """Wrapper for sequence of template mappings
390 """Wrapper for sequence of template mappings
391
391
392 This represents an inner template structure (i.e. a list of dicts),
392 This represents an inner template structure (i.e. a list of dicts),
393 which can also be rendered by the specified named/literal template.
393 which can also be rendered by the specified named/literal template.
394
394
395 Template mappings may be nested.
395 Template mappings may be nested.
396 """
396 """
397
397
398 def __init__(self, name=None, tmpl=None, sep=''):
398 def __init__(self, name=None, tmpl=None, sep=''):
399 if name is not None and tmpl is not None:
399 if name is not None and tmpl is not None:
400 raise error.ProgrammingError('name and tmpl are mutually exclusive')
400 raise error.ProgrammingError('name and tmpl are mutually exclusive')
401 self._name = name
401 self._name = name
402 self._tmpl = tmpl
402 self._tmpl = tmpl
403 self._defaultsep = sep
403 self._defaultsep = sep
404
404
405 def contains(self, context, mapping, item):
405 def contains(self, context, mapping, item):
406 raise error.ParseError(_('not comparable'))
406 raise error.ParseError(_('not comparable'))
407
407
408 def getmember(self, context, mapping, key):
408 def getmember(self, context, mapping, key):
409 raise error.ParseError(_('not a dictionary'))
409 raise error.ParseError(_('not a dictionary'))
410
410
411 def getmin(self, context, mapping):
411 def getmin(self, context, mapping):
412 raise error.ParseError(_('not comparable'))
412 raise error.ParseError(_('not comparable'))
413
413
414 def getmax(self, context, mapping):
414 def getmax(self, context, mapping):
415 raise error.ParseError(_('not comparable'))
415 raise error.ParseError(_('not comparable'))
416
416
417 def filter(self, context, mapping, select):
417 def filter(self, context, mapping, select):
418 # implement if necessary; we'll need a wrapped type for a mapping dict
418 # implement if necessary; we'll need a wrapped type for a mapping dict
419 raise error.ParseError(_('not filterable without template'))
419 raise error.ParseError(_('not filterable without template'))
420
420
421 def join(self, context, mapping, sep):
421 def join(self, context, mapping, sep):
422 mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context))
422 mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context))
423 if self._name:
423 if self._name:
424 itemiter = (context.process(self._name, m) for m in mapsiter)
424 itemiter = (context.process(self._name, m) for m in mapsiter)
425 elif self._tmpl:
425 elif self._tmpl:
426 itemiter = (context.expand(self._tmpl, m) for m in mapsiter)
426 itemiter = (context.expand(self._tmpl, m) for m in mapsiter)
427 else:
427 else:
428 raise error.ParseError(_('not displayable without template'))
428 raise error.ParseError(_('not displayable without template'))
429 return joinitems(itemiter, sep)
429 return joinitems(itemiter, sep)
430
430
431 def show(self, context, mapping):
431 def show(self, context, mapping):
432 return self.join(context, mapping, self._defaultsep)
432 return self.join(context, mapping, self._defaultsep)
433
433
434 def tovalue(self, context, mapping):
434 def tovalue(self, context, mapping):
435 knownres = context.knownresourcekeys()
435 knownres = context.knownresourcekeys()
436 items = []
436 items = []
437 for nm in self.itermaps(context):
437 for nm in self.itermaps(context):
438 # drop internal resources (recursively) which shouldn't be displayed
438 # drop internal resources (recursively) which shouldn't be displayed
439 lm = context.overlaymap(mapping, nm)
439 lm = context.overlaymap(mapping, nm)
440 items.append({k: unwrapvalue(context, lm, v)
440 items.append({k: unwrapvalue(context, lm, v)
441 for k, v in nm.iteritems() if k not in knownres})
441 for k, v in nm.iteritems() if k not in knownres})
442 return items
442 return items
443
443
444 class mappinggenerator(_mappingsequence):
444 class mappinggenerator(_mappingsequence):
445 """Wrapper for generator of template mappings
445 """Wrapper for generator of template mappings
446
446
447 The function ``make(context, *args)`` should return a generator of
447 The function ``make(context, *args)`` should return a generator of
448 mapping dicts.
448 mapping dicts.
449 """
449 """
450
450
451 def __init__(self, make, args=(), name=None, tmpl=None, sep=''):
451 def __init__(self, make, args=(), name=None, tmpl=None, sep=''):
452 super(mappinggenerator, self).__init__(name, tmpl, sep)
452 super(mappinggenerator, self).__init__(name, tmpl, sep)
453 self._make = make
453 self._make = make
454 self._args = args
454 self._args = args
455
455
456 def itermaps(self, context):
456 def itermaps(self, context):
457 return self._make(context, *self._args)
457 return self._make(context, *self._args)
458
458
459 def tobool(self, context, mapping):
459 def tobool(self, context, mapping):
460 return _nonempty(self.itermaps(context))
460 return _nonempty(self.itermaps(context))
461
461
462 class mappinglist(_mappingsequence):
462 class mappinglist(_mappingsequence):
463 """Wrapper for list of template mappings"""
463 """Wrapper for list of template mappings"""
464
464
465 def __init__(self, mappings, name=None, tmpl=None, sep=''):
465 def __init__(self, mappings, name=None, tmpl=None, sep=''):
466 super(mappinglist, self).__init__(name, tmpl, sep)
466 super(mappinglist, self).__init__(name, tmpl, sep)
467 self._mappings = mappings
467 self._mappings = mappings
468
468
469 def itermaps(self, context):
469 def itermaps(self, context):
470 return iter(self._mappings)
470 return iter(self._mappings)
471
471
472 def tobool(self, context, mapping):
472 def tobool(self, context, mapping):
473 return bool(self._mappings)
473 return bool(self._mappings)
474
474
475 class mappedgenerator(wrapped):
475 class mappedgenerator(wrapped):
476 """Wrapper for generator of strings which acts as a list
476 """Wrapper for generator of strings which acts as a list
477
477
478 The function ``make(context, *args)`` should return a generator of
478 The function ``make(context, *args)`` should return a generator of
479 byte strings, or a generator of (possibly nested) generators of byte
479 byte strings, or a generator of (possibly nested) generators of byte
480 strings (i.e. a generator for a list of byte strings.)
480 strings (i.e. a generator for a list of byte strings.)
481 """
481 """
482
482
483 def __init__(self, make, args=()):
483 def __init__(self, make, args=()):
484 self._make = make
484 self._make = make
485 self._args = args
485 self._args = args
486
486
487 def contains(self, context, mapping, item):
487 def contains(self, context, mapping, item):
488 item = stringify(context, mapping, item)
488 item = stringify(context, mapping, item)
489 return item in self.tovalue(context, mapping)
489 return item in self.tovalue(context, mapping)
490
490
491 def _gen(self, context):
491 def _gen(self, context):
492 return self._make(context, *self._args)
492 return self._make(context, *self._args)
493
493
494 def getmember(self, context, mapping, key):
494 def getmember(self, context, mapping, key):
495 raise error.ParseError(_('not a dictionary'))
495 raise error.ParseError(_('not a dictionary'))
496
496
497 def getmin(self, context, mapping):
497 def getmin(self, context, mapping):
498 return self._getby(context, mapping, min)
498 return self._getby(context, mapping, min)
499
499
500 def getmax(self, context, mapping):
500 def getmax(self, context, mapping):
501 return self._getby(context, mapping, max)
501 return self._getby(context, mapping, max)
502
502
503 def _getby(self, context, mapping, func):
503 def _getby(self, context, mapping, func):
504 xs = self.tovalue(context, mapping)
504 xs = self.tovalue(context, mapping)
505 if not xs:
505 if not xs:
506 raise error.ParseError(_('empty sequence'))
506 raise error.ParseError(_('empty sequence'))
507 return func(xs)
507 return func(xs)
508
508
509 @staticmethod
509 @staticmethod
510 def _filteredgen(context, mapping, make, args, select):
510 def _filteredgen(context, mapping, make, args, select):
511 for x in make(context, *args):
511 for x in make(context, *args):
512 s = stringify(context, mapping, x)
512 s = stringify(context, mapping, x)
513 if select(wrappedbytes(s)):
513 if select(wrappedbytes(s)):
514 yield s
514 yield s
515
515
516 def filter(self, context, mapping, select):
516 def filter(self, context, mapping, select):
517 args = (mapping, self._make, self._args, select)
517 args = (mapping, self._make, self._args, select)
518 return mappedgenerator(self._filteredgen, args)
518 return mappedgenerator(self._filteredgen, args)
519
519
520 def itermaps(self, context):
520 def itermaps(self, context):
521 raise error.ParseError(_('list of strings is not mappable'))
521 raise error.ParseError(_('list of strings is not mappable'))
522
522
523 def join(self, context, mapping, sep):
523 def join(self, context, mapping, sep):
524 return joinitems(self._gen(context), sep)
524 return joinitems(self._gen(context), sep)
525
525
526 def show(self, context, mapping):
526 def show(self, context, mapping):
527 return self.join(context, mapping, '')
527 return self.join(context, mapping, '')
528
528
529 def tobool(self, context, mapping):
529 def tobool(self, context, mapping):
530 return _nonempty(self._gen(context))
530 return _nonempty(self._gen(context))
531
531
532 def tovalue(self, context, mapping):
532 def tovalue(self, context, mapping):
533 return [stringify(context, mapping, x) for x in self._gen(context)]
533 return [stringify(context, mapping, x) for x in self._gen(context)]
534
534
535 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
535 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
536 """Wrap data to support both dict-like and string-like operations"""
536 """Wrap data to support both dict-like and string-like operations"""
537 prefmt = pycompat.identity
537 prefmt = pycompat.identity
538 if fmt is None:
538 if fmt is None:
539 fmt = '%s=%s'
539 fmt = '%s=%s'
540 prefmt = pycompat.bytestr
540 prefmt = pycompat.bytestr
541 return hybrid(gen, data, lambda k: {key: k, value: data[k]},
541 return hybrid(gen, data, lambda k: {key: k, value: data[k]},
542 lambda k: fmt % (prefmt(k), prefmt(data[k])))
542 lambda k: fmt % (prefmt(k), prefmt(data[k])))
543
543
544 def hybridlist(data, name, fmt=None, gen=None):
544 def hybridlist(data, name, fmt=None, gen=None):
545 """Wrap data to support both list-like and string-like operations"""
545 """Wrap data to support both list-like and string-like operations"""
546 prefmt = pycompat.identity
546 prefmt = pycompat.identity
547 if fmt is None:
547 if fmt is None:
548 fmt = '%s'
548 fmt = '%s'
549 prefmt = pycompat.bytestr
549 prefmt = pycompat.bytestr
550 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
550 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
551
551
552 def compatdict(context, mapping, name, data, key='key', value='value',
552 def compatdict(context, mapping, name, data, key='key', value='value',
553 fmt=None, plural=None, separator=' '):
553 fmt=None, plural=None, separator=' '):
554 """Wrap data like hybriddict(), but also supports old-style list template
554 """Wrap data like hybriddict(), but also supports old-style list template
555
555
556 This exists for backward compatibility with the old-style template. Use
556 This exists for backward compatibility with the old-style template. Use
557 hybriddict() for new template keywords.
557 hybriddict() for new template keywords.
558 """
558 """
559 c = [{key: k, value: v} for k, v in data.iteritems()]
559 c = [{key: k, value: v} for k, v in data.iteritems()]
560 f = _showcompatlist(context, mapping, name, c, plural, separator)
560 f = _showcompatlist(context, mapping, name, c, plural, separator)
561 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
561 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
562
562
563 def compatlist(context, mapping, name, data, element=None, fmt=None,
563 def compatlist(context, mapping, name, data, element=None, fmt=None,
564 plural=None, separator=' '):
564 plural=None, separator=' '):
565 """Wrap data like hybridlist(), but also supports old-style list template
565 """Wrap data like hybridlist(), but also supports old-style list template
566
566
567 This exists for backward compatibility with the old-style template. Use
567 This exists for backward compatibility with the old-style template. Use
568 hybridlist() for new template keywords.
568 hybridlist() for new template keywords.
569 """
569 """
570 f = _showcompatlist(context, mapping, name, data, plural, separator)
570 f = _showcompatlist(context, mapping, name, data, plural, separator)
571 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
571 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
572
572
573 def compatfilecopiesdict(context, mapping, name, copies):
574 """Wrap list of (dest, source) file names to support old-style list
575 template and field names
576
577 This exists for backward compatibility. Use hybriddict for new template
578 keywords.
579 """
580 # no need to provide {path} to old-style list template
581 c = [{'name': k, 'source': v} for k, v in copies]
582 f = _showcompatlist(context, mapping, name, c, plural='file_copies')
583 copies = util.sortdict(copies)
584 return hybrid(f, copies,
585 lambda k: {'name': k, 'path': k, 'source': copies[k]},
586 lambda k: '%s (%s)' % (k, copies[k]))
587
573 def compatfileslist(context, mapping, name, files):
588 def compatfileslist(context, mapping, name, files):
574 """Wrap list of file names to support old-style list template and field
589 """Wrap list of file names to support old-style list template and field
575 names
590 names
576
591
577 This exists for backward compatibility. Use hybridlist for new template
592 This exists for backward compatibility. Use hybridlist for new template
578 keywords.
593 keywords.
579 """
594 """
580 f = _showcompatlist(context, mapping, name, files)
595 f = _showcompatlist(context, mapping, name, files)
581 return hybrid(f, files, lambda x: {'file': x, 'path': x},
596 return hybrid(f, files, lambda x: {'file': x, 'path': x},
582 pycompat.identity)
597 pycompat.identity)
583
598
584 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
599 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
585 """Return a generator that renders old-style list template
600 """Return a generator that renders old-style list template
586
601
587 name is name of key in template map.
602 name is name of key in template map.
588 values is list of strings or dicts.
603 values is list of strings or dicts.
589 plural is plural of name, if not simply name + 's'.
604 plural is plural of name, if not simply name + 's'.
590 separator is used to join values as a string
605 separator is used to join values as a string
591
606
592 expansion works like this, given name 'foo'.
607 expansion works like this, given name 'foo'.
593
608
594 if values is empty, expand 'no_foos'.
609 if values is empty, expand 'no_foos'.
595
610
596 if 'foo' not in template map, return values as a string,
611 if 'foo' not in template map, return values as a string,
597 joined by 'separator'.
612 joined by 'separator'.
598
613
599 expand 'start_foos'.
614 expand 'start_foos'.
600
615
601 for each value, expand 'foo'. if 'last_foo' in template
616 for each value, expand 'foo'. if 'last_foo' in template
602 map, expand it instead of 'foo' for last key.
617 map, expand it instead of 'foo' for last key.
603
618
604 expand 'end_foos'.
619 expand 'end_foos'.
605 """
620 """
606 if not plural:
621 if not plural:
607 plural = name + 's'
622 plural = name + 's'
608 if not values:
623 if not values:
609 noname = 'no_' + plural
624 noname = 'no_' + plural
610 if context.preload(noname):
625 if context.preload(noname):
611 yield context.process(noname, mapping)
626 yield context.process(noname, mapping)
612 return
627 return
613 if not context.preload(name):
628 if not context.preload(name):
614 if isinstance(values[0], bytes):
629 if isinstance(values[0], bytes):
615 yield separator.join(values)
630 yield separator.join(values)
616 else:
631 else:
617 for v in values:
632 for v in values:
618 r = dict(v)
633 r = dict(v)
619 r.update(mapping)
634 r.update(mapping)
620 yield r
635 yield r
621 return
636 return
622 startname = 'start_' + plural
637 startname = 'start_' + plural
623 if context.preload(startname):
638 if context.preload(startname):
624 yield context.process(startname, mapping)
639 yield context.process(startname, mapping)
625 def one(v, tag=name):
640 def one(v, tag=name):
626 vmapping = {}
641 vmapping = {}
627 try:
642 try:
628 vmapping.update(v)
643 vmapping.update(v)
629 # Python 2 raises ValueError if the type of v is wrong. Python
644 # Python 2 raises ValueError if the type of v is wrong. Python
630 # 3 raises TypeError.
645 # 3 raises TypeError.
631 except (AttributeError, TypeError, ValueError):
646 except (AttributeError, TypeError, ValueError):
632 try:
647 try:
633 # Python 2 raises ValueError trying to destructure an e.g.
648 # Python 2 raises ValueError trying to destructure an e.g.
634 # bytes. Python 3 raises TypeError.
649 # bytes. Python 3 raises TypeError.
635 for a, b in v:
650 for a, b in v:
636 vmapping[a] = b
651 vmapping[a] = b
637 except (TypeError, ValueError):
652 except (TypeError, ValueError):
638 vmapping[name] = v
653 vmapping[name] = v
639 vmapping = context.overlaymap(mapping, vmapping)
654 vmapping = context.overlaymap(mapping, vmapping)
640 return context.process(tag, vmapping)
655 return context.process(tag, vmapping)
641 lastname = 'last_' + name
656 lastname = 'last_' + name
642 if context.preload(lastname):
657 if context.preload(lastname):
643 last = values.pop()
658 last = values.pop()
644 else:
659 else:
645 last = None
660 last = None
646 for v in values:
661 for v in values:
647 yield one(v)
662 yield one(v)
648 if last is not None:
663 if last is not None:
649 yield one(last, tag=lastname)
664 yield one(last, tag=lastname)
650 endname = 'end_' + plural
665 endname = 'end_' + plural
651 if context.preload(endname):
666 if context.preload(endname):
652 yield context.process(endname, mapping)
667 yield context.process(endname, mapping)
653
668
654 def flatten(context, mapping, thing):
669 def flatten(context, mapping, thing):
655 """Yield a single stream from a possibly nested set of iterators"""
670 """Yield a single stream from a possibly nested set of iterators"""
656 if isinstance(thing, wrapped):
671 if isinstance(thing, wrapped):
657 thing = thing.show(context, mapping)
672 thing = thing.show(context, mapping)
658 if isinstance(thing, bytes):
673 if isinstance(thing, bytes):
659 yield thing
674 yield thing
660 elif isinstance(thing, str):
675 elif isinstance(thing, str):
661 # We can only hit this on Python 3, and it's here to guard
676 # We can only hit this on Python 3, and it's here to guard
662 # against infinite recursion.
677 # against infinite recursion.
663 raise error.ProgrammingError('Mercurial IO including templates is done'
678 raise error.ProgrammingError('Mercurial IO including templates is done'
664 ' with bytes, not strings, got %r' % thing)
679 ' with bytes, not strings, got %r' % thing)
665 elif thing is None:
680 elif thing is None:
666 pass
681 pass
667 elif not util.safehasattr(thing, '__iter__'):
682 elif not util.safehasattr(thing, '__iter__'):
668 yield pycompat.bytestr(thing)
683 yield pycompat.bytestr(thing)
669 else:
684 else:
670 for i in thing:
685 for i in thing:
671 if isinstance(i, wrapped):
686 if isinstance(i, wrapped):
672 i = i.show(context, mapping)
687 i = i.show(context, mapping)
673 if isinstance(i, bytes):
688 if isinstance(i, bytes):
674 yield i
689 yield i
675 elif i is None:
690 elif i is None:
676 pass
691 pass
677 elif not util.safehasattr(i, '__iter__'):
692 elif not util.safehasattr(i, '__iter__'):
678 yield pycompat.bytestr(i)
693 yield pycompat.bytestr(i)
679 else:
694 else:
680 for j in flatten(context, mapping, i):
695 for j in flatten(context, mapping, i):
681 yield j
696 yield j
682
697
683 def stringify(context, mapping, thing):
698 def stringify(context, mapping, thing):
684 """Turn values into bytes by converting into text and concatenating them"""
699 """Turn values into bytes by converting into text and concatenating them"""
685 if isinstance(thing, bytes):
700 if isinstance(thing, bytes):
686 return thing # retain localstr to be round-tripped
701 return thing # retain localstr to be round-tripped
687 return b''.join(flatten(context, mapping, thing))
702 return b''.join(flatten(context, mapping, thing))
688
703
689 def findsymbolicname(arg):
704 def findsymbolicname(arg):
690 """Find symbolic name for the given compiled expression; returns None
705 """Find symbolic name for the given compiled expression; returns None
691 if nothing found reliably"""
706 if nothing found reliably"""
692 while True:
707 while True:
693 func, data = arg
708 func, data = arg
694 if func is runsymbol:
709 if func is runsymbol:
695 return data
710 return data
696 elif func is runfilter:
711 elif func is runfilter:
697 arg = data[0]
712 arg = data[0]
698 else:
713 else:
699 return None
714 return None
700
715
701 def _nonempty(xiter):
716 def _nonempty(xiter):
702 try:
717 try:
703 next(xiter)
718 next(xiter)
704 return True
719 return True
705 except StopIteration:
720 except StopIteration:
706 return False
721 return False
707
722
708 def _unthunk(context, mapping, thing):
723 def _unthunk(context, mapping, thing):
709 """Evaluate a lazy byte string into value"""
724 """Evaluate a lazy byte string into value"""
710 if not isinstance(thing, types.GeneratorType):
725 if not isinstance(thing, types.GeneratorType):
711 return thing
726 return thing
712 return stringify(context, mapping, thing)
727 return stringify(context, mapping, thing)
713
728
714 def evalrawexp(context, mapping, arg):
729 def evalrawexp(context, mapping, arg):
715 """Evaluate given argument as a bare template object which may require
730 """Evaluate given argument as a bare template object which may require
716 further processing (such as folding generator of strings)"""
731 further processing (such as folding generator of strings)"""
717 func, data = arg
732 func, data = arg
718 return func(context, mapping, data)
733 return func(context, mapping, data)
719
734
720 def evalwrapped(context, mapping, arg):
735 def evalwrapped(context, mapping, arg):
721 """Evaluate given argument to wrapped object"""
736 """Evaluate given argument to wrapped object"""
722 thing = evalrawexp(context, mapping, arg)
737 thing = evalrawexp(context, mapping, arg)
723 return makewrapped(context, mapping, thing)
738 return makewrapped(context, mapping, thing)
724
739
725 def makewrapped(context, mapping, thing):
740 def makewrapped(context, mapping, thing):
726 """Lift object to a wrapped type"""
741 """Lift object to a wrapped type"""
727 if isinstance(thing, wrapped):
742 if isinstance(thing, wrapped):
728 return thing
743 return thing
729 thing = _unthunk(context, mapping, thing)
744 thing = _unthunk(context, mapping, thing)
730 if isinstance(thing, bytes):
745 if isinstance(thing, bytes):
731 return wrappedbytes(thing)
746 return wrappedbytes(thing)
732 return wrappedvalue(thing)
747 return wrappedvalue(thing)
733
748
734 def evalfuncarg(context, mapping, arg):
749 def evalfuncarg(context, mapping, arg):
735 """Evaluate given argument as value type"""
750 """Evaluate given argument as value type"""
736 return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
751 return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
737
752
738 def unwrapvalue(context, mapping, thing):
753 def unwrapvalue(context, mapping, thing):
739 """Move the inner value object out of the wrapper"""
754 """Move the inner value object out of the wrapper"""
740 if isinstance(thing, wrapped):
755 if isinstance(thing, wrapped):
741 return thing.tovalue(context, mapping)
756 return thing.tovalue(context, mapping)
742 # evalrawexp() may return string, generator of strings or arbitrary object
757 # evalrawexp() may return string, generator of strings or arbitrary object
743 # such as date tuple, but filter does not want generator.
758 # such as date tuple, but filter does not want generator.
744 return _unthunk(context, mapping, thing)
759 return _unthunk(context, mapping, thing)
745
760
746 def evalboolean(context, mapping, arg):
761 def evalboolean(context, mapping, arg):
747 """Evaluate given argument as boolean, but also takes boolean literals"""
762 """Evaluate given argument as boolean, but also takes boolean literals"""
748 func, data = arg
763 func, data = arg
749 if func is runsymbol:
764 if func is runsymbol:
750 thing = func(context, mapping, data, default=None)
765 thing = func(context, mapping, data, default=None)
751 if thing is None:
766 if thing is None:
752 # not a template keyword, takes as a boolean literal
767 # not a template keyword, takes as a boolean literal
753 thing = stringutil.parsebool(data)
768 thing = stringutil.parsebool(data)
754 else:
769 else:
755 thing = func(context, mapping, data)
770 thing = func(context, mapping, data)
756 return makewrapped(context, mapping, thing).tobool(context, mapping)
771 return makewrapped(context, mapping, thing).tobool(context, mapping)
757
772
758 def evaldate(context, mapping, arg, err=None):
773 def evaldate(context, mapping, arg, err=None):
759 """Evaluate given argument as a date tuple or a date string; returns
774 """Evaluate given argument as a date tuple or a date string; returns
760 a (unixtime, offset) tuple"""
775 a (unixtime, offset) tuple"""
761 thing = evalrawexp(context, mapping, arg)
776 thing = evalrawexp(context, mapping, arg)
762 return unwrapdate(context, mapping, thing, err)
777 return unwrapdate(context, mapping, thing, err)
763
778
764 def unwrapdate(context, mapping, thing, err=None):
779 def unwrapdate(context, mapping, thing, err=None):
765 if isinstance(thing, date):
780 if isinstance(thing, date):
766 return thing.tovalue(context, mapping)
781 return thing.tovalue(context, mapping)
767 # TODO: update hgweb to not return bare tuple; then just stringify 'thing'
782 # TODO: update hgweb to not return bare tuple; then just stringify 'thing'
768 thing = unwrapvalue(context, mapping, thing)
783 thing = unwrapvalue(context, mapping, thing)
769 try:
784 try:
770 return dateutil.parsedate(thing)
785 return dateutil.parsedate(thing)
771 except AttributeError:
786 except AttributeError:
772 raise error.ParseError(err or _('not a date tuple nor a string'))
787 raise error.ParseError(err or _('not a date tuple nor a string'))
773 except error.ParseError:
788 except error.ParseError:
774 if not err:
789 if not err:
775 raise
790 raise
776 raise error.ParseError(err)
791 raise error.ParseError(err)
777
792
778 def evalinteger(context, mapping, arg, err=None):
793 def evalinteger(context, mapping, arg, err=None):
779 thing = evalrawexp(context, mapping, arg)
794 thing = evalrawexp(context, mapping, arg)
780 return unwrapinteger(context, mapping, thing, err)
795 return unwrapinteger(context, mapping, thing, err)
781
796
782 def unwrapinteger(context, mapping, thing, err=None):
797 def unwrapinteger(context, mapping, thing, err=None):
783 thing = unwrapvalue(context, mapping, thing)
798 thing = unwrapvalue(context, mapping, thing)
784 try:
799 try:
785 return int(thing)
800 return int(thing)
786 except (TypeError, ValueError):
801 except (TypeError, ValueError):
787 raise error.ParseError(err or _('not an integer'))
802 raise error.ParseError(err or _('not an integer'))
788
803
789 def evalstring(context, mapping, arg):
804 def evalstring(context, mapping, arg):
790 return stringify(context, mapping, evalrawexp(context, mapping, arg))
805 return stringify(context, mapping, evalrawexp(context, mapping, arg))
791
806
792 def evalstringliteral(context, mapping, arg):
807 def evalstringliteral(context, mapping, arg):
793 """Evaluate given argument as string template, but returns symbol name
808 """Evaluate given argument as string template, but returns symbol name
794 if it is unknown"""
809 if it is unknown"""
795 func, data = arg
810 func, data = arg
796 if func is runsymbol:
811 if func is runsymbol:
797 thing = func(context, mapping, data, default=data)
812 thing = func(context, mapping, data, default=data)
798 else:
813 else:
799 thing = func(context, mapping, data)
814 thing = func(context, mapping, data)
800 return stringify(context, mapping, thing)
815 return stringify(context, mapping, thing)
801
816
802 _unwrapfuncbytype = {
817 _unwrapfuncbytype = {
803 None: unwrapvalue,
818 None: unwrapvalue,
804 bytes: stringify,
819 bytes: stringify,
805 date: unwrapdate,
820 date: unwrapdate,
806 int: unwrapinteger,
821 int: unwrapinteger,
807 }
822 }
808
823
809 def unwrapastype(context, mapping, thing, typ):
824 def unwrapastype(context, mapping, thing, typ):
810 """Move the inner value object out of the wrapper and coerce its type"""
825 """Move the inner value object out of the wrapper and coerce its type"""
811 try:
826 try:
812 f = _unwrapfuncbytype[typ]
827 f = _unwrapfuncbytype[typ]
813 except KeyError:
828 except KeyError:
814 raise error.ProgrammingError('invalid type specified: %r' % typ)
829 raise error.ProgrammingError('invalid type specified: %r' % typ)
815 return f(context, mapping, thing)
830 return f(context, mapping, thing)
816
831
817 def runinteger(context, mapping, data):
832 def runinteger(context, mapping, data):
818 return int(data)
833 return int(data)
819
834
820 def runstring(context, mapping, data):
835 def runstring(context, mapping, data):
821 return data
836 return data
822
837
823 def _recursivesymbolblocker(key):
838 def _recursivesymbolblocker(key):
824 def showrecursion(context, mapping):
839 def showrecursion(context, mapping):
825 raise error.Abort(_("recursive reference '%s' in template") % key)
840 raise error.Abort(_("recursive reference '%s' in template") % key)
826 showrecursion._requires = () # mark as new-style templatekw
841 showrecursion._requires = () # mark as new-style templatekw
827 return showrecursion
842 return showrecursion
828
843
829 def runsymbol(context, mapping, key, default=''):
844 def runsymbol(context, mapping, key, default=''):
830 v = context.symbol(mapping, key)
845 v = context.symbol(mapping, key)
831 if v is None:
846 if v is None:
832 # put poison to cut recursion. we can't move this to parsing phase
847 # put poison to cut recursion. we can't move this to parsing phase
833 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
848 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
834 safemapping = mapping.copy()
849 safemapping = mapping.copy()
835 safemapping[key] = _recursivesymbolblocker(key)
850 safemapping[key] = _recursivesymbolblocker(key)
836 try:
851 try:
837 v = context.process(key, safemapping)
852 v = context.process(key, safemapping)
838 except TemplateNotFound:
853 except TemplateNotFound:
839 v = default
854 v = default
840 if callable(v) and getattr(v, '_requires', None) is None:
855 if callable(v) and getattr(v, '_requires', None) is None:
841 # old templatekw: expand all keywords and resources
856 # old templatekw: expand all keywords and resources
842 # (TODO: drop support for old-style functions. 'f._requires = ()'
857 # (TODO: drop support for old-style functions. 'f._requires = ()'
843 # can be removed.)
858 # can be removed.)
844 props = {k: context._resources.lookup(context, mapping, k)
859 props = {k: context._resources.lookup(context, mapping, k)
845 for k in context._resources.knownkeys()}
860 for k in context._resources.knownkeys()}
846 # pass context to _showcompatlist() through templatekw._showlist()
861 # pass context to _showcompatlist() through templatekw._showlist()
847 props['templ'] = context
862 props['templ'] = context
848 props.update(mapping)
863 props.update(mapping)
849 ui = props.get('ui')
864 ui = props.get('ui')
850 if ui:
865 if ui:
851 ui.deprecwarn("old-style template keyword '%s'" % key, '4.8')
866 ui.deprecwarn("old-style template keyword '%s'" % key, '4.8')
852 return v(**pycompat.strkwargs(props))
867 return v(**pycompat.strkwargs(props))
853 if callable(v):
868 if callable(v):
854 # new templatekw
869 # new templatekw
855 try:
870 try:
856 return v(context, mapping)
871 return v(context, mapping)
857 except ResourceUnavailable:
872 except ResourceUnavailable:
858 # unsupported keyword is mapped to empty just like unknown keyword
873 # unsupported keyword is mapped to empty just like unknown keyword
859 return None
874 return None
860 return v
875 return v
861
876
862 def runtemplate(context, mapping, template):
877 def runtemplate(context, mapping, template):
863 for arg in template:
878 for arg in template:
864 yield evalrawexp(context, mapping, arg)
879 yield evalrawexp(context, mapping, arg)
865
880
866 def runfilter(context, mapping, data):
881 def runfilter(context, mapping, data):
867 arg, filt = data
882 arg, filt = data
868 thing = evalrawexp(context, mapping, arg)
883 thing = evalrawexp(context, mapping, arg)
869 intype = getattr(filt, '_intype', None)
884 intype = getattr(filt, '_intype', None)
870 try:
885 try:
871 thing = unwrapastype(context, mapping, thing, intype)
886 thing = unwrapastype(context, mapping, thing, intype)
872 return filt(thing)
887 return filt(thing)
873 except error.ParseError as e:
888 except error.ParseError as e:
874 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
889 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
875
890
876 def _formatfiltererror(arg, filt):
891 def _formatfiltererror(arg, filt):
877 fn = pycompat.sysbytes(filt.__name__)
892 fn = pycompat.sysbytes(filt.__name__)
878 sym = findsymbolicname(arg)
893 sym = findsymbolicname(arg)
879 if not sym:
894 if not sym:
880 return _("incompatible use of template filter '%s'") % fn
895 return _("incompatible use of template filter '%s'") % fn
881 return (_("template filter '%s' is not compatible with keyword '%s'")
896 return (_("template filter '%s' is not compatible with keyword '%s'")
882 % (fn, sym))
897 % (fn, sym))
883
898
884 def _iteroverlaymaps(context, origmapping, newmappings):
899 def _iteroverlaymaps(context, origmapping, newmappings):
885 """Generate combined mappings from the original mapping and an iterable
900 """Generate combined mappings from the original mapping and an iterable
886 of partial mappings to override the original"""
901 of partial mappings to override the original"""
887 for i, nm in enumerate(newmappings):
902 for i, nm in enumerate(newmappings):
888 lm = context.overlaymap(origmapping, nm)
903 lm = context.overlaymap(origmapping, nm)
889 lm['index'] = i
904 lm['index'] = i
890 yield lm
905 yield lm
891
906
892 def _applymap(context, mapping, d, darg, targ):
907 def _applymap(context, mapping, d, darg, targ):
893 try:
908 try:
894 diter = d.itermaps(context)
909 diter = d.itermaps(context)
895 except error.ParseError as err:
910 except error.ParseError as err:
896 sym = findsymbolicname(darg)
911 sym = findsymbolicname(darg)
897 if not sym:
912 if not sym:
898 raise
913 raise
899 hint = _("keyword '%s' does not support map operation") % sym
914 hint = _("keyword '%s' does not support map operation") % sym
900 raise error.ParseError(bytes(err), hint=hint)
915 raise error.ParseError(bytes(err), hint=hint)
901 for lm in _iteroverlaymaps(context, mapping, diter):
916 for lm in _iteroverlaymaps(context, mapping, diter):
902 yield evalrawexp(context, lm, targ)
917 yield evalrawexp(context, lm, targ)
903
918
904 def runmap(context, mapping, data):
919 def runmap(context, mapping, data):
905 darg, targ = data
920 darg, targ = data
906 d = evalwrapped(context, mapping, darg)
921 d = evalwrapped(context, mapping, darg)
907 return mappedgenerator(_applymap, args=(mapping, d, darg, targ))
922 return mappedgenerator(_applymap, args=(mapping, d, darg, targ))
908
923
909 def runmember(context, mapping, data):
924 def runmember(context, mapping, data):
910 darg, memb = data
925 darg, memb = data
911 d = evalwrapped(context, mapping, darg)
926 d = evalwrapped(context, mapping, darg)
912 if isinstance(d, mappable):
927 if isinstance(d, mappable):
913 lm = context.overlaymap(mapping, d.tomap(context))
928 lm = context.overlaymap(mapping, d.tomap(context))
914 return runsymbol(context, lm, memb)
929 return runsymbol(context, lm, memb)
915 try:
930 try:
916 return d.getmember(context, mapping, memb)
931 return d.getmember(context, mapping, memb)
917 except error.ParseError as err:
932 except error.ParseError as err:
918 sym = findsymbolicname(darg)
933 sym = findsymbolicname(darg)
919 if not sym:
934 if not sym:
920 raise
935 raise
921 hint = _("keyword '%s' does not support member operation") % sym
936 hint = _("keyword '%s' does not support member operation") % sym
922 raise error.ParseError(bytes(err), hint=hint)
937 raise error.ParseError(bytes(err), hint=hint)
923
938
924 def runnegate(context, mapping, data):
939 def runnegate(context, mapping, data):
925 data = evalinteger(context, mapping, data,
940 data = evalinteger(context, mapping, data,
926 _('negation needs an integer argument'))
941 _('negation needs an integer argument'))
927 return -data
942 return -data
928
943
929 def runarithmetic(context, mapping, data):
944 def runarithmetic(context, mapping, data):
930 func, left, right = data
945 func, left, right = data
931 left = evalinteger(context, mapping, left,
946 left = evalinteger(context, mapping, left,
932 _('arithmetic only defined on integers'))
947 _('arithmetic only defined on integers'))
933 right = evalinteger(context, mapping, right,
948 right = evalinteger(context, mapping, right,
934 _('arithmetic only defined on integers'))
949 _('arithmetic only defined on integers'))
935 try:
950 try:
936 return func(left, right)
951 return func(left, right)
937 except ZeroDivisionError:
952 except ZeroDivisionError:
938 raise error.Abort(_('division by zero is not defined'))
953 raise error.Abort(_('division by zero is not defined'))
939
954
940 def joinitems(itemiter, sep):
955 def joinitems(itemiter, sep):
941 """Join items with the separator; Returns generator of bytes"""
956 """Join items with the separator; Returns generator of bytes"""
942 first = True
957 first = True
943 for x in itemiter:
958 for x in itemiter:
944 if first:
959 if first:
945 first = False
960 first = False
946 elif sep:
961 elif sep:
947 yield sep
962 yield sep
948 yield x
963 yield x
@@ -1,1246 +1,1264 b''
1 Test template keywords
1 Test template keywords
2 ======================
2 ======================
3
3
4 $ hg init a
4 $ hg init a
5 $ cd a
5 $ cd a
6 $ echo a > a
6 $ echo a > a
7 $ hg add a
7 $ hg add a
8 $ echo line 1 > b
8 $ echo line 1 > b
9 $ echo line 2 >> b
9 $ echo line 2 >> b
10 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
10 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
11
11
12 $ hg add b
12 $ hg add b
13 $ echo other 1 > c
13 $ echo other 1 > c
14 $ echo other 2 >> c
14 $ echo other 2 >> c
15 $ echo >> c
15 $ echo >> c
16 $ echo other 3 >> c
16 $ echo other 3 >> c
17 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
17 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
18
18
19 $ hg add c
19 $ hg add c
20 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
20 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
21 $ echo c >> c
21 $ echo c >> c
22 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
22 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
23
23
24 $ echo foo > .hg/branch
24 $ echo foo > .hg/branch
25 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
25 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
26
26
27 $ hg co -q 3
27 $ hg co -q 3
28 $ echo other 4 >> d
28 $ echo other 4 >> d
29 $ hg add d
29 $ hg add d
30 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
30 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
31
31
32 $ hg merge -q foo
32 $ hg merge -q foo
33 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
33 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
34
34
35 Second branch starting at nullrev:
35 Second branch starting at nullrev:
36
36
37 $ hg update null
37 $ hg update null
38 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
38 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
39 $ echo second > second
39 $ echo second > second
40 $ hg add second
40 $ hg add second
41 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
41 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
42 created new head
42 created new head
43
43
44 $ echo third > third
44 $ echo third > third
45 $ hg add third
45 $ hg add third
46 $ hg mv second fourth
46 $ hg mv second fourth
47 $ hg commit -m third -d "2020-01-01 10:01"
47 $ hg commit -m third -d "2020-01-01 10:01"
48
48
49 Working-directory revision has special identifiers, though they are still
49 Working-directory revision has special identifiers, though they are still
50 experimental:
50 experimental:
51
51
52 $ hg log -r 'wdir()' -T '{rev}:{node}\n'
52 $ hg log -r 'wdir()' -T '{rev}:{node}\n'
53 2147483647:ffffffffffffffffffffffffffffffffffffffff
53 2147483647:ffffffffffffffffffffffffffffffffffffffff
54
54
55 Some keywords are invalid for working-directory revision, but they should
55 Some keywords are invalid for working-directory revision, but they should
56 never cause crash:
56 never cause crash:
57
57
58 $ hg log -r 'wdir()' -T '{manifest}\n'
58 $ hg log -r 'wdir()' -T '{manifest}\n'
59
59
60
60
61 Check that {phase} works correctly on parents:
61 Check that {phase} works correctly on parents:
62
62
63 $ cat << EOF > parentphase
63 $ cat << EOF > parentphase
64 > changeset_debug = '{rev} ({phase}):{parents}\n'
64 > changeset_debug = '{rev} ({phase}):{parents}\n'
65 > parent = ' {rev} ({phase})'
65 > parent = ' {rev} ({phase})'
66 > EOF
66 > EOF
67 $ hg phase -r 5 --public
67 $ hg phase -r 5 --public
68 $ hg phase -r 7 --secret --force
68 $ hg phase -r 7 --secret --force
69 $ hg log --debug -G --style ./parentphase
69 $ hg log --debug -G --style ./parentphase
70 @ 8 (secret): 7 (secret) -1 (public)
70 @ 8 (secret): 7 (secret) -1 (public)
71 |
71 |
72 o 7 (secret): -1 (public) -1 (public)
72 o 7 (secret): -1 (public) -1 (public)
73
73
74 o 6 (draft): 5 (public) 4 (draft)
74 o 6 (draft): 5 (public) 4 (draft)
75 |\
75 |\
76 | o 5 (public): 3 (public) -1 (public)
76 | o 5 (public): 3 (public) -1 (public)
77 | |
77 | |
78 o | 4 (draft): 3 (public) -1 (public)
78 o | 4 (draft): 3 (public) -1 (public)
79 |/
79 |/
80 o 3 (public): 2 (public) -1 (public)
80 o 3 (public): 2 (public) -1 (public)
81 |
81 |
82 o 2 (public): 1 (public) -1 (public)
82 o 2 (public): 1 (public) -1 (public)
83 |
83 |
84 o 1 (public): 0 (public) -1 (public)
84 o 1 (public): 0 (public) -1 (public)
85 |
85 |
86 o 0 (public): -1 (public) -1 (public)
86 o 0 (public): -1 (public) -1 (public)
87
87
88
88
89 Keys work:
89 Keys work:
90
90
91 $ for key in author branch branches date desc file_adds file_dels file_mods \
91 $ for key in author branch branches date desc file_adds file_dels file_mods \
92 > file_copies file_copies_switch files \
92 > file_copies file_copies_switch files \
93 > manifest node parents rev tags diffstat extras \
93 > manifest node parents rev tags diffstat extras \
94 > p1rev p2rev p1node p2node user; do
94 > p1rev p2rev p1node p2node user; do
95 > for mode in '' --verbose --debug; do
95 > for mode in '' --verbose --debug; do
96 > hg log $mode --template "$key$mode: {$key}\n"
96 > hg log $mode --template "$key$mode: {$key}\n"
97 > done
97 > done
98 > done
98 > done
99 author: test
99 author: test
100 author: User Name <user@hostname>
100 author: User Name <user@hostname>
101 author: person
101 author: person
102 author: person
102 author: person
103 author: person
103 author: person
104 author: person
104 author: person
105 author: other@place
105 author: other@place
106 author: A. N. Other <other@place>
106 author: A. N. Other <other@place>
107 author: User Name <user@hostname>
107 author: User Name <user@hostname>
108 author--verbose: test
108 author--verbose: test
109 author--verbose: User Name <user@hostname>
109 author--verbose: User Name <user@hostname>
110 author--verbose: person
110 author--verbose: person
111 author--verbose: person
111 author--verbose: person
112 author--verbose: person
112 author--verbose: person
113 author--verbose: person
113 author--verbose: person
114 author--verbose: other@place
114 author--verbose: other@place
115 author--verbose: A. N. Other <other@place>
115 author--verbose: A. N. Other <other@place>
116 author--verbose: User Name <user@hostname>
116 author--verbose: User Name <user@hostname>
117 author--debug: test
117 author--debug: test
118 author--debug: User Name <user@hostname>
118 author--debug: User Name <user@hostname>
119 author--debug: person
119 author--debug: person
120 author--debug: person
120 author--debug: person
121 author--debug: person
121 author--debug: person
122 author--debug: person
122 author--debug: person
123 author--debug: other@place
123 author--debug: other@place
124 author--debug: A. N. Other <other@place>
124 author--debug: A. N. Other <other@place>
125 author--debug: User Name <user@hostname>
125 author--debug: User Name <user@hostname>
126 branch: default
126 branch: default
127 branch: default
127 branch: default
128 branch: default
128 branch: default
129 branch: default
129 branch: default
130 branch: foo
130 branch: foo
131 branch: default
131 branch: default
132 branch: default
132 branch: default
133 branch: default
133 branch: default
134 branch: default
134 branch: default
135 branch--verbose: default
135 branch--verbose: default
136 branch--verbose: default
136 branch--verbose: default
137 branch--verbose: default
137 branch--verbose: default
138 branch--verbose: default
138 branch--verbose: default
139 branch--verbose: foo
139 branch--verbose: foo
140 branch--verbose: default
140 branch--verbose: default
141 branch--verbose: default
141 branch--verbose: default
142 branch--verbose: default
142 branch--verbose: default
143 branch--verbose: default
143 branch--verbose: default
144 branch--debug: default
144 branch--debug: default
145 branch--debug: default
145 branch--debug: default
146 branch--debug: default
146 branch--debug: default
147 branch--debug: default
147 branch--debug: default
148 branch--debug: foo
148 branch--debug: foo
149 branch--debug: default
149 branch--debug: default
150 branch--debug: default
150 branch--debug: default
151 branch--debug: default
151 branch--debug: default
152 branch--debug: default
152 branch--debug: default
153 branches:
153 branches:
154 branches:
154 branches:
155 branches:
155 branches:
156 branches:
156 branches:
157 branches: foo
157 branches: foo
158 branches:
158 branches:
159 branches:
159 branches:
160 branches:
160 branches:
161 branches:
161 branches:
162 branches--verbose:
162 branches--verbose:
163 branches--verbose:
163 branches--verbose:
164 branches--verbose:
164 branches--verbose:
165 branches--verbose:
165 branches--verbose:
166 branches--verbose: foo
166 branches--verbose: foo
167 branches--verbose:
167 branches--verbose:
168 branches--verbose:
168 branches--verbose:
169 branches--verbose:
169 branches--verbose:
170 branches--verbose:
170 branches--verbose:
171 branches--debug:
171 branches--debug:
172 branches--debug:
172 branches--debug:
173 branches--debug:
173 branches--debug:
174 branches--debug:
174 branches--debug:
175 branches--debug: foo
175 branches--debug: foo
176 branches--debug:
176 branches--debug:
177 branches--debug:
177 branches--debug:
178 branches--debug:
178 branches--debug:
179 branches--debug:
179 branches--debug:
180 date: 1577872860.00
180 date: 1577872860.00
181 date: 1000000.00
181 date: 1000000.00
182 date: 1500001.00
182 date: 1500001.00
183 date: 1500000.00
183 date: 1500000.00
184 date: 1400000.00
184 date: 1400000.00
185 date: 1300000.00
185 date: 1300000.00
186 date: 1200000.00
186 date: 1200000.00
187 date: 1100000.00
187 date: 1100000.00
188 date: 1000000.00
188 date: 1000000.00
189 date--verbose: 1577872860.00
189 date--verbose: 1577872860.00
190 date--verbose: 1000000.00
190 date--verbose: 1000000.00
191 date--verbose: 1500001.00
191 date--verbose: 1500001.00
192 date--verbose: 1500000.00
192 date--verbose: 1500000.00
193 date--verbose: 1400000.00
193 date--verbose: 1400000.00
194 date--verbose: 1300000.00
194 date--verbose: 1300000.00
195 date--verbose: 1200000.00
195 date--verbose: 1200000.00
196 date--verbose: 1100000.00
196 date--verbose: 1100000.00
197 date--verbose: 1000000.00
197 date--verbose: 1000000.00
198 date--debug: 1577872860.00
198 date--debug: 1577872860.00
199 date--debug: 1000000.00
199 date--debug: 1000000.00
200 date--debug: 1500001.00
200 date--debug: 1500001.00
201 date--debug: 1500000.00
201 date--debug: 1500000.00
202 date--debug: 1400000.00
202 date--debug: 1400000.00
203 date--debug: 1300000.00
203 date--debug: 1300000.00
204 date--debug: 1200000.00
204 date--debug: 1200000.00
205 date--debug: 1100000.00
205 date--debug: 1100000.00
206 date--debug: 1000000.00
206 date--debug: 1000000.00
207 desc: third
207 desc: third
208 desc: second
208 desc: second
209 desc: merge
209 desc: merge
210 desc: new head
210 desc: new head
211 desc: new branch
211 desc: new branch
212 desc: no user, no domain
212 desc: no user, no domain
213 desc: no person
213 desc: no person
214 desc: other 1
214 desc: other 1
215 other 2
215 other 2
216
216
217 other 3
217 other 3
218 desc: line 1
218 desc: line 1
219 line 2
219 line 2
220 desc--verbose: third
220 desc--verbose: third
221 desc--verbose: second
221 desc--verbose: second
222 desc--verbose: merge
222 desc--verbose: merge
223 desc--verbose: new head
223 desc--verbose: new head
224 desc--verbose: new branch
224 desc--verbose: new branch
225 desc--verbose: no user, no domain
225 desc--verbose: no user, no domain
226 desc--verbose: no person
226 desc--verbose: no person
227 desc--verbose: other 1
227 desc--verbose: other 1
228 other 2
228 other 2
229
229
230 other 3
230 other 3
231 desc--verbose: line 1
231 desc--verbose: line 1
232 line 2
232 line 2
233 desc--debug: third
233 desc--debug: third
234 desc--debug: second
234 desc--debug: second
235 desc--debug: merge
235 desc--debug: merge
236 desc--debug: new head
236 desc--debug: new head
237 desc--debug: new branch
237 desc--debug: new branch
238 desc--debug: no user, no domain
238 desc--debug: no user, no domain
239 desc--debug: no person
239 desc--debug: no person
240 desc--debug: other 1
240 desc--debug: other 1
241 other 2
241 other 2
242
242
243 other 3
243 other 3
244 desc--debug: line 1
244 desc--debug: line 1
245 line 2
245 line 2
246 file_adds: fourth third
246 file_adds: fourth third
247 file_adds: second
247 file_adds: second
248 file_adds:
248 file_adds:
249 file_adds: d
249 file_adds: d
250 file_adds:
250 file_adds:
251 file_adds:
251 file_adds:
252 file_adds: c
252 file_adds: c
253 file_adds: b
253 file_adds: b
254 file_adds: a
254 file_adds: a
255 file_adds--verbose: fourth third
255 file_adds--verbose: fourth third
256 file_adds--verbose: second
256 file_adds--verbose: second
257 file_adds--verbose:
257 file_adds--verbose:
258 file_adds--verbose: d
258 file_adds--verbose: d
259 file_adds--verbose:
259 file_adds--verbose:
260 file_adds--verbose:
260 file_adds--verbose:
261 file_adds--verbose: c
261 file_adds--verbose: c
262 file_adds--verbose: b
262 file_adds--verbose: b
263 file_adds--verbose: a
263 file_adds--verbose: a
264 file_adds--debug: fourth third
264 file_adds--debug: fourth third
265 file_adds--debug: second
265 file_adds--debug: second
266 file_adds--debug:
266 file_adds--debug:
267 file_adds--debug: d
267 file_adds--debug: d
268 file_adds--debug:
268 file_adds--debug:
269 file_adds--debug:
269 file_adds--debug:
270 file_adds--debug: c
270 file_adds--debug: c
271 file_adds--debug: b
271 file_adds--debug: b
272 file_adds--debug: a
272 file_adds--debug: a
273 file_dels: second
273 file_dels: second
274 file_dels:
274 file_dels:
275 file_dels:
275 file_dels:
276 file_dels:
276 file_dels:
277 file_dels:
277 file_dels:
278 file_dels:
278 file_dels:
279 file_dels:
279 file_dels:
280 file_dels:
280 file_dels:
281 file_dels:
281 file_dels:
282 file_dels--verbose: second
282 file_dels--verbose: second
283 file_dels--verbose:
283 file_dels--verbose:
284 file_dels--verbose:
284 file_dels--verbose:
285 file_dels--verbose:
285 file_dels--verbose:
286 file_dels--verbose:
286 file_dels--verbose:
287 file_dels--verbose:
287 file_dels--verbose:
288 file_dels--verbose:
288 file_dels--verbose:
289 file_dels--verbose:
289 file_dels--verbose:
290 file_dels--verbose:
290 file_dels--verbose:
291 file_dels--debug: second
291 file_dels--debug: second
292 file_dels--debug:
292 file_dels--debug:
293 file_dels--debug:
293 file_dels--debug:
294 file_dels--debug:
294 file_dels--debug:
295 file_dels--debug:
295 file_dels--debug:
296 file_dels--debug:
296 file_dels--debug:
297 file_dels--debug:
297 file_dels--debug:
298 file_dels--debug:
298 file_dels--debug:
299 file_dels--debug:
299 file_dels--debug:
300 file_mods:
300 file_mods:
301 file_mods:
301 file_mods:
302 file_mods:
302 file_mods:
303 file_mods:
303 file_mods:
304 file_mods:
304 file_mods:
305 file_mods: c
305 file_mods: c
306 file_mods:
306 file_mods:
307 file_mods:
307 file_mods:
308 file_mods:
308 file_mods:
309 file_mods--verbose:
309 file_mods--verbose:
310 file_mods--verbose:
310 file_mods--verbose:
311 file_mods--verbose:
311 file_mods--verbose:
312 file_mods--verbose:
312 file_mods--verbose:
313 file_mods--verbose:
313 file_mods--verbose:
314 file_mods--verbose: c
314 file_mods--verbose: c
315 file_mods--verbose:
315 file_mods--verbose:
316 file_mods--verbose:
316 file_mods--verbose:
317 file_mods--verbose:
317 file_mods--verbose:
318 file_mods--debug:
318 file_mods--debug:
319 file_mods--debug:
319 file_mods--debug:
320 file_mods--debug:
320 file_mods--debug:
321 file_mods--debug:
321 file_mods--debug:
322 file_mods--debug:
322 file_mods--debug:
323 file_mods--debug: c
323 file_mods--debug: c
324 file_mods--debug:
324 file_mods--debug:
325 file_mods--debug:
325 file_mods--debug:
326 file_mods--debug:
326 file_mods--debug:
327 file_copies: fourth (second)
327 file_copies: fourth (second)
328 file_copies:
328 file_copies:
329 file_copies:
329 file_copies:
330 file_copies:
330 file_copies:
331 file_copies:
331 file_copies:
332 file_copies:
332 file_copies:
333 file_copies:
333 file_copies:
334 file_copies:
334 file_copies:
335 file_copies:
335 file_copies:
336 file_copies--verbose: fourth (second)
336 file_copies--verbose: fourth (second)
337 file_copies--verbose:
337 file_copies--verbose:
338 file_copies--verbose:
338 file_copies--verbose:
339 file_copies--verbose:
339 file_copies--verbose:
340 file_copies--verbose:
340 file_copies--verbose:
341 file_copies--verbose:
341 file_copies--verbose:
342 file_copies--verbose:
342 file_copies--verbose:
343 file_copies--verbose:
343 file_copies--verbose:
344 file_copies--verbose:
344 file_copies--verbose:
345 file_copies--debug: fourth (second)
345 file_copies--debug: fourth (second)
346 file_copies--debug:
346 file_copies--debug:
347 file_copies--debug:
347 file_copies--debug:
348 file_copies--debug:
348 file_copies--debug:
349 file_copies--debug:
349 file_copies--debug:
350 file_copies--debug:
350 file_copies--debug:
351 file_copies--debug:
351 file_copies--debug:
352 file_copies--debug:
352 file_copies--debug:
353 file_copies--debug:
353 file_copies--debug:
354 file_copies_switch:
354 file_copies_switch:
355 file_copies_switch:
355 file_copies_switch:
356 file_copies_switch:
356 file_copies_switch:
357 file_copies_switch:
357 file_copies_switch:
358 file_copies_switch:
358 file_copies_switch:
359 file_copies_switch:
359 file_copies_switch:
360 file_copies_switch:
360 file_copies_switch:
361 file_copies_switch:
361 file_copies_switch:
362 file_copies_switch:
362 file_copies_switch:
363 file_copies_switch--verbose:
363 file_copies_switch--verbose:
364 file_copies_switch--verbose:
364 file_copies_switch--verbose:
365 file_copies_switch--verbose:
365 file_copies_switch--verbose:
366 file_copies_switch--verbose:
366 file_copies_switch--verbose:
367 file_copies_switch--verbose:
367 file_copies_switch--verbose:
368 file_copies_switch--verbose:
368 file_copies_switch--verbose:
369 file_copies_switch--verbose:
369 file_copies_switch--verbose:
370 file_copies_switch--verbose:
370 file_copies_switch--verbose:
371 file_copies_switch--verbose:
371 file_copies_switch--verbose:
372 file_copies_switch--debug:
372 file_copies_switch--debug:
373 file_copies_switch--debug:
373 file_copies_switch--debug:
374 file_copies_switch--debug:
374 file_copies_switch--debug:
375 file_copies_switch--debug:
375 file_copies_switch--debug:
376 file_copies_switch--debug:
376 file_copies_switch--debug:
377 file_copies_switch--debug:
377 file_copies_switch--debug:
378 file_copies_switch--debug:
378 file_copies_switch--debug:
379 file_copies_switch--debug:
379 file_copies_switch--debug:
380 file_copies_switch--debug:
380 file_copies_switch--debug:
381 files: fourth second third
381 files: fourth second third
382 files: second
382 files: second
383 files:
383 files:
384 files: d
384 files: d
385 files:
385 files:
386 files: c
386 files: c
387 files: c
387 files: c
388 files: b
388 files: b
389 files: a
389 files: a
390 files--verbose: fourth second third
390 files--verbose: fourth second third
391 files--verbose: second
391 files--verbose: second
392 files--verbose:
392 files--verbose:
393 files--verbose: d
393 files--verbose: d
394 files--verbose:
394 files--verbose:
395 files--verbose: c
395 files--verbose: c
396 files--verbose: c
396 files--verbose: c
397 files--verbose: b
397 files--verbose: b
398 files--verbose: a
398 files--verbose: a
399 files--debug: fourth second third
399 files--debug: fourth second third
400 files--debug: second
400 files--debug: second
401 files--debug:
401 files--debug:
402 files--debug: d
402 files--debug: d
403 files--debug:
403 files--debug:
404 files--debug: c
404 files--debug: c
405 files--debug: c
405 files--debug: c
406 files--debug: b
406 files--debug: b
407 files--debug: a
407 files--debug: a
408 manifest: 6:94961b75a2da
408 manifest: 6:94961b75a2da
409 manifest: 5:f2dbc354b94e
409 manifest: 5:f2dbc354b94e
410 manifest: 4:4dc3def4f9b4
410 manifest: 4:4dc3def4f9b4
411 manifest: 4:4dc3def4f9b4
411 manifest: 4:4dc3def4f9b4
412 manifest: 3:cb5a1327723b
412 manifest: 3:cb5a1327723b
413 manifest: 3:cb5a1327723b
413 manifest: 3:cb5a1327723b
414 manifest: 2:6e0e82995c35
414 manifest: 2:6e0e82995c35
415 manifest: 1:4e8d705b1e53
415 manifest: 1:4e8d705b1e53
416 manifest: 0:a0c8bcbbb45c
416 manifest: 0:a0c8bcbbb45c
417 manifest--verbose: 6:94961b75a2da
417 manifest--verbose: 6:94961b75a2da
418 manifest--verbose: 5:f2dbc354b94e
418 manifest--verbose: 5:f2dbc354b94e
419 manifest--verbose: 4:4dc3def4f9b4
419 manifest--verbose: 4:4dc3def4f9b4
420 manifest--verbose: 4:4dc3def4f9b4
420 manifest--verbose: 4:4dc3def4f9b4
421 manifest--verbose: 3:cb5a1327723b
421 manifest--verbose: 3:cb5a1327723b
422 manifest--verbose: 3:cb5a1327723b
422 manifest--verbose: 3:cb5a1327723b
423 manifest--verbose: 2:6e0e82995c35
423 manifest--verbose: 2:6e0e82995c35
424 manifest--verbose: 1:4e8d705b1e53
424 manifest--verbose: 1:4e8d705b1e53
425 manifest--verbose: 0:a0c8bcbbb45c
425 manifest--verbose: 0:a0c8bcbbb45c
426 manifest--debug: 6:94961b75a2da554b4df6fb599e5bfc7d48de0c64
426 manifest--debug: 6:94961b75a2da554b4df6fb599e5bfc7d48de0c64
427 manifest--debug: 5:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
427 manifest--debug: 5:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
428 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
428 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
429 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
429 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
430 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
430 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
431 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
431 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
432 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
432 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
433 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
433 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
434 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
434 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
435 node: 95c24699272ef57d062b8bccc32c878bf841784a
435 node: 95c24699272ef57d062b8bccc32c878bf841784a
436 node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
436 node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
437 node: d41e714fe50d9e4a5f11b4d595d543481b5f980b
437 node: d41e714fe50d9e4a5f11b4d595d543481b5f980b
438 node: 13207e5a10d9fd28ec424934298e176197f2c67f
438 node: 13207e5a10d9fd28ec424934298e176197f2c67f
439 node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
439 node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
440 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
440 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
441 node: 97054abb4ab824450e9164180baf491ae0078465
441 node: 97054abb4ab824450e9164180baf491ae0078465
442 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
442 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
443 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
443 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
444 node--verbose: 95c24699272ef57d062b8bccc32c878bf841784a
444 node--verbose: 95c24699272ef57d062b8bccc32c878bf841784a
445 node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
445 node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
446 node--verbose: d41e714fe50d9e4a5f11b4d595d543481b5f980b
446 node--verbose: d41e714fe50d9e4a5f11b4d595d543481b5f980b
447 node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
447 node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
448 node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
448 node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
449 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
449 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
450 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
450 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
451 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
451 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
452 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
452 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
453 node--debug: 95c24699272ef57d062b8bccc32c878bf841784a
453 node--debug: 95c24699272ef57d062b8bccc32c878bf841784a
454 node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
454 node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
455 node--debug: d41e714fe50d9e4a5f11b4d595d543481b5f980b
455 node--debug: d41e714fe50d9e4a5f11b4d595d543481b5f980b
456 node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
456 node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
457 node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
457 node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
458 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
458 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
459 node--debug: 97054abb4ab824450e9164180baf491ae0078465
459 node--debug: 97054abb4ab824450e9164180baf491ae0078465
460 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
460 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
461 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
461 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
462 parents:
462 parents:
463 parents: -1:000000000000
463 parents: -1:000000000000
464 parents: 5:13207e5a10d9 4:bbe44766e73d
464 parents: 5:13207e5a10d9 4:bbe44766e73d
465 parents: 3:10e46f2dcbf4
465 parents: 3:10e46f2dcbf4
466 parents:
466 parents:
467 parents:
467 parents:
468 parents:
468 parents:
469 parents:
469 parents:
470 parents:
470 parents:
471 parents--verbose:
471 parents--verbose:
472 parents--verbose: -1:000000000000
472 parents--verbose: -1:000000000000
473 parents--verbose: 5:13207e5a10d9 4:bbe44766e73d
473 parents--verbose: 5:13207e5a10d9 4:bbe44766e73d
474 parents--verbose: 3:10e46f2dcbf4
474 parents--verbose: 3:10e46f2dcbf4
475 parents--verbose:
475 parents--verbose:
476 parents--verbose:
476 parents--verbose:
477 parents--verbose:
477 parents--verbose:
478 parents--verbose:
478 parents--verbose:
479 parents--verbose:
479 parents--verbose:
480 parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000
480 parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000
481 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
481 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
482 parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
482 parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
483 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
483 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
484 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
484 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
485 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
485 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
486 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
486 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
487 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
487 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
488 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
488 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
489 rev: 8
489 rev: 8
490 rev: 7
490 rev: 7
491 rev: 6
491 rev: 6
492 rev: 5
492 rev: 5
493 rev: 4
493 rev: 4
494 rev: 3
494 rev: 3
495 rev: 2
495 rev: 2
496 rev: 1
496 rev: 1
497 rev: 0
497 rev: 0
498 rev--verbose: 8
498 rev--verbose: 8
499 rev--verbose: 7
499 rev--verbose: 7
500 rev--verbose: 6
500 rev--verbose: 6
501 rev--verbose: 5
501 rev--verbose: 5
502 rev--verbose: 4
502 rev--verbose: 4
503 rev--verbose: 3
503 rev--verbose: 3
504 rev--verbose: 2
504 rev--verbose: 2
505 rev--verbose: 1
505 rev--verbose: 1
506 rev--verbose: 0
506 rev--verbose: 0
507 rev--debug: 8
507 rev--debug: 8
508 rev--debug: 7
508 rev--debug: 7
509 rev--debug: 6
509 rev--debug: 6
510 rev--debug: 5
510 rev--debug: 5
511 rev--debug: 4
511 rev--debug: 4
512 rev--debug: 3
512 rev--debug: 3
513 rev--debug: 2
513 rev--debug: 2
514 rev--debug: 1
514 rev--debug: 1
515 rev--debug: 0
515 rev--debug: 0
516 tags: tip
516 tags: tip
517 tags:
517 tags:
518 tags:
518 tags:
519 tags:
519 tags:
520 tags:
520 tags:
521 tags:
521 tags:
522 tags:
522 tags:
523 tags:
523 tags:
524 tags:
524 tags:
525 tags--verbose: tip
525 tags--verbose: tip
526 tags--verbose:
526 tags--verbose:
527 tags--verbose:
527 tags--verbose:
528 tags--verbose:
528 tags--verbose:
529 tags--verbose:
529 tags--verbose:
530 tags--verbose:
530 tags--verbose:
531 tags--verbose:
531 tags--verbose:
532 tags--verbose:
532 tags--verbose:
533 tags--verbose:
533 tags--verbose:
534 tags--debug: tip
534 tags--debug: tip
535 tags--debug:
535 tags--debug:
536 tags--debug:
536 tags--debug:
537 tags--debug:
537 tags--debug:
538 tags--debug:
538 tags--debug:
539 tags--debug:
539 tags--debug:
540 tags--debug:
540 tags--debug:
541 tags--debug:
541 tags--debug:
542 tags--debug:
542 tags--debug:
543 diffstat: 3: +2/-1
543 diffstat: 3: +2/-1
544 diffstat: 1: +1/-0
544 diffstat: 1: +1/-0
545 diffstat: 0: +0/-0
545 diffstat: 0: +0/-0
546 diffstat: 1: +1/-0
546 diffstat: 1: +1/-0
547 diffstat: 0: +0/-0
547 diffstat: 0: +0/-0
548 diffstat: 1: +1/-0
548 diffstat: 1: +1/-0
549 diffstat: 1: +4/-0
549 diffstat: 1: +4/-0
550 diffstat: 1: +2/-0
550 diffstat: 1: +2/-0
551 diffstat: 1: +1/-0
551 diffstat: 1: +1/-0
552 diffstat--verbose: 3: +2/-1
552 diffstat--verbose: 3: +2/-1
553 diffstat--verbose: 1: +1/-0
553 diffstat--verbose: 1: +1/-0
554 diffstat--verbose: 0: +0/-0
554 diffstat--verbose: 0: +0/-0
555 diffstat--verbose: 1: +1/-0
555 diffstat--verbose: 1: +1/-0
556 diffstat--verbose: 0: +0/-0
556 diffstat--verbose: 0: +0/-0
557 diffstat--verbose: 1: +1/-0
557 diffstat--verbose: 1: +1/-0
558 diffstat--verbose: 1: +4/-0
558 diffstat--verbose: 1: +4/-0
559 diffstat--verbose: 1: +2/-0
559 diffstat--verbose: 1: +2/-0
560 diffstat--verbose: 1: +1/-0
560 diffstat--verbose: 1: +1/-0
561 diffstat--debug: 3: +2/-1
561 diffstat--debug: 3: +2/-1
562 diffstat--debug: 1: +1/-0
562 diffstat--debug: 1: +1/-0
563 diffstat--debug: 0: +0/-0
563 diffstat--debug: 0: +0/-0
564 diffstat--debug: 1: +1/-0
564 diffstat--debug: 1: +1/-0
565 diffstat--debug: 0: +0/-0
565 diffstat--debug: 0: +0/-0
566 diffstat--debug: 1: +1/-0
566 diffstat--debug: 1: +1/-0
567 diffstat--debug: 1: +4/-0
567 diffstat--debug: 1: +4/-0
568 diffstat--debug: 1: +2/-0
568 diffstat--debug: 1: +2/-0
569 diffstat--debug: 1: +1/-0
569 diffstat--debug: 1: +1/-0
570 extras: branch=default
570 extras: branch=default
571 extras: branch=default
571 extras: branch=default
572 extras: branch=default
572 extras: branch=default
573 extras: branch=default
573 extras: branch=default
574 extras: branch=foo
574 extras: branch=foo
575 extras: branch=default
575 extras: branch=default
576 extras: branch=default
576 extras: branch=default
577 extras: branch=default
577 extras: branch=default
578 extras: branch=default
578 extras: branch=default
579 extras--verbose: branch=default
579 extras--verbose: branch=default
580 extras--verbose: branch=default
580 extras--verbose: branch=default
581 extras--verbose: branch=default
581 extras--verbose: branch=default
582 extras--verbose: branch=default
582 extras--verbose: branch=default
583 extras--verbose: branch=foo
583 extras--verbose: branch=foo
584 extras--verbose: branch=default
584 extras--verbose: branch=default
585 extras--verbose: branch=default
585 extras--verbose: branch=default
586 extras--verbose: branch=default
586 extras--verbose: branch=default
587 extras--verbose: branch=default
587 extras--verbose: branch=default
588 extras--debug: branch=default
588 extras--debug: branch=default
589 extras--debug: branch=default
589 extras--debug: branch=default
590 extras--debug: branch=default
590 extras--debug: branch=default
591 extras--debug: branch=default
591 extras--debug: branch=default
592 extras--debug: branch=foo
592 extras--debug: branch=foo
593 extras--debug: branch=default
593 extras--debug: branch=default
594 extras--debug: branch=default
594 extras--debug: branch=default
595 extras--debug: branch=default
595 extras--debug: branch=default
596 extras--debug: branch=default
596 extras--debug: branch=default
597 p1rev: 7
597 p1rev: 7
598 p1rev: -1
598 p1rev: -1
599 p1rev: 5
599 p1rev: 5
600 p1rev: 3
600 p1rev: 3
601 p1rev: 3
601 p1rev: 3
602 p1rev: 2
602 p1rev: 2
603 p1rev: 1
603 p1rev: 1
604 p1rev: 0
604 p1rev: 0
605 p1rev: -1
605 p1rev: -1
606 p1rev--verbose: 7
606 p1rev--verbose: 7
607 p1rev--verbose: -1
607 p1rev--verbose: -1
608 p1rev--verbose: 5
608 p1rev--verbose: 5
609 p1rev--verbose: 3
609 p1rev--verbose: 3
610 p1rev--verbose: 3
610 p1rev--verbose: 3
611 p1rev--verbose: 2
611 p1rev--verbose: 2
612 p1rev--verbose: 1
612 p1rev--verbose: 1
613 p1rev--verbose: 0
613 p1rev--verbose: 0
614 p1rev--verbose: -1
614 p1rev--verbose: -1
615 p1rev--debug: 7
615 p1rev--debug: 7
616 p1rev--debug: -1
616 p1rev--debug: -1
617 p1rev--debug: 5
617 p1rev--debug: 5
618 p1rev--debug: 3
618 p1rev--debug: 3
619 p1rev--debug: 3
619 p1rev--debug: 3
620 p1rev--debug: 2
620 p1rev--debug: 2
621 p1rev--debug: 1
621 p1rev--debug: 1
622 p1rev--debug: 0
622 p1rev--debug: 0
623 p1rev--debug: -1
623 p1rev--debug: -1
624 p2rev: -1
624 p2rev: -1
625 p2rev: -1
625 p2rev: -1
626 p2rev: 4
626 p2rev: 4
627 p2rev: -1
627 p2rev: -1
628 p2rev: -1
628 p2rev: -1
629 p2rev: -1
629 p2rev: -1
630 p2rev: -1
630 p2rev: -1
631 p2rev: -1
631 p2rev: -1
632 p2rev: -1
632 p2rev: -1
633 p2rev--verbose: -1
633 p2rev--verbose: -1
634 p2rev--verbose: -1
634 p2rev--verbose: -1
635 p2rev--verbose: 4
635 p2rev--verbose: 4
636 p2rev--verbose: -1
636 p2rev--verbose: -1
637 p2rev--verbose: -1
637 p2rev--verbose: -1
638 p2rev--verbose: -1
638 p2rev--verbose: -1
639 p2rev--verbose: -1
639 p2rev--verbose: -1
640 p2rev--verbose: -1
640 p2rev--verbose: -1
641 p2rev--verbose: -1
641 p2rev--verbose: -1
642 p2rev--debug: -1
642 p2rev--debug: -1
643 p2rev--debug: -1
643 p2rev--debug: -1
644 p2rev--debug: 4
644 p2rev--debug: 4
645 p2rev--debug: -1
645 p2rev--debug: -1
646 p2rev--debug: -1
646 p2rev--debug: -1
647 p2rev--debug: -1
647 p2rev--debug: -1
648 p2rev--debug: -1
648 p2rev--debug: -1
649 p2rev--debug: -1
649 p2rev--debug: -1
650 p2rev--debug: -1
650 p2rev--debug: -1
651 p1node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
651 p1node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
652 p1node: 0000000000000000000000000000000000000000
652 p1node: 0000000000000000000000000000000000000000
653 p1node: 13207e5a10d9fd28ec424934298e176197f2c67f
653 p1node: 13207e5a10d9fd28ec424934298e176197f2c67f
654 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
654 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
655 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
655 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
656 p1node: 97054abb4ab824450e9164180baf491ae0078465
656 p1node: 97054abb4ab824450e9164180baf491ae0078465
657 p1node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
657 p1node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
658 p1node: 1e4e1b8f71e05681d422154f5421e385fec3454f
658 p1node: 1e4e1b8f71e05681d422154f5421e385fec3454f
659 p1node: 0000000000000000000000000000000000000000
659 p1node: 0000000000000000000000000000000000000000
660 p1node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
660 p1node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
661 p1node--verbose: 0000000000000000000000000000000000000000
661 p1node--verbose: 0000000000000000000000000000000000000000
662 p1node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
662 p1node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
663 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
663 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
664 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
664 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
665 p1node--verbose: 97054abb4ab824450e9164180baf491ae0078465
665 p1node--verbose: 97054abb4ab824450e9164180baf491ae0078465
666 p1node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
666 p1node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
667 p1node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
667 p1node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
668 p1node--verbose: 0000000000000000000000000000000000000000
668 p1node--verbose: 0000000000000000000000000000000000000000
669 p1node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
669 p1node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
670 p1node--debug: 0000000000000000000000000000000000000000
670 p1node--debug: 0000000000000000000000000000000000000000
671 p1node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
671 p1node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
672 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
672 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
673 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
673 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
674 p1node--debug: 97054abb4ab824450e9164180baf491ae0078465
674 p1node--debug: 97054abb4ab824450e9164180baf491ae0078465
675 p1node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
675 p1node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
676 p1node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
676 p1node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
677 p1node--debug: 0000000000000000000000000000000000000000
677 p1node--debug: 0000000000000000000000000000000000000000
678 p2node: 0000000000000000000000000000000000000000
678 p2node: 0000000000000000000000000000000000000000
679 p2node: 0000000000000000000000000000000000000000
679 p2node: 0000000000000000000000000000000000000000
680 p2node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
680 p2node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
681 p2node: 0000000000000000000000000000000000000000
681 p2node: 0000000000000000000000000000000000000000
682 p2node: 0000000000000000000000000000000000000000
682 p2node: 0000000000000000000000000000000000000000
683 p2node: 0000000000000000000000000000000000000000
683 p2node: 0000000000000000000000000000000000000000
684 p2node: 0000000000000000000000000000000000000000
684 p2node: 0000000000000000000000000000000000000000
685 p2node: 0000000000000000000000000000000000000000
685 p2node: 0000000000000000000000000000000000000000
686 p2node: 0000000000000000000000000000000000000000
686 p2node: 0000000000000000000000000000000000000000
687 p2node--verbose: 0000000000000000000000000000000000000000
687 p2node--verbose: 0000000000000000000000000000000000000000
688 p2node--verbose: 0000000000000000000000000000000000000000
688 p2node--verbose: 0000000000000000000000000000000000000000
689 p2node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
689 p2node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
690 p2node--verbose: 0000000000000000000000000000000000000000
690 p2node--verbose: 0000000000000000000000000000000000000000
691 p2node--verbose: 0000000000000000000000000000000000000000
691 p2node--verbose: 0000000000000000000000000000000000000000
692 p2node--verbose: 0000000000000000000000000000000000000000
692 p2node--verbose: 0000000000000000000000000000000000000000
693 p2node--verbose: 0000000000000000000000000000000000000000
693 p2node--verbose: 0000000000000000000000000000000000000000
694 p2node--verbose: 0000000000000000000000000000000000000000
694 p2node--verbose: 0000000000000000000000000000000000000000
695 p2node--verbose: 0000000000000000000000000000000000000000
695 p2node--verbose: 0000000000000000000000000000000000000000
696 p2node--debug: 0000000000000000000000000000000000000000
696 p2node--debug: 0000000000000000000000000000000000000000
697 p2node--debug: 0000000000000000000000000000000000000000
697 p2node--debug: 0000000000000000000000000000000000000000
698 p2node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
698 p2node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
699 p2node--debug: 0000000000000000000000000000000000000000
699 p2node--debug: 0000000000000000000000000000000000000000
700 p2node--debug: 0000000000000000000000000000000000000000
700 p2node--debug: 0000000000000000000000000000000000000000
701 p2node--debug: 0000000000000000000000000000000000000000
701 p2node--debug: 0000000000000000000000000000000000000000
702 p2node--debug: 0000000000000000000000000000000000000000
702 p2node--debug: 0000000000000000000000000000000000000000
703 p2node--debug: 0000000000000000000000000000000000000000
703 p2node--debug: 0000000000000000000000000000000000000000
704 p2node--debug: 0000000000000000000000000000000000000000
704 p2node--debug: 0000000000000000000000000000000000000000
705 user: test
705 user: test
706 user: User Name <user@hostname>
706 user: User Name <user@hostname>
707 user: person
707 user: person
708 user: person
708 user: person
709 user: person
709 user: person
710 user: person
710 user: person
711 user: other@place
711 user: other@place
712 user: A. N. Other <other@place>
712 user: A. N. Other <other@place>
713 user: User Name <user@hostname>
713 user: User Name <user@hostname>
714 user--verbose: test
714 user--verbose: test
715 user--verbose: User Name <user@hostname>
715 user--verbose: User Name <user@hostname>
716 user--verbose: person
716 user--verbose: person
717 user--verbose: person
717 user--verbose: person
718 user--verbose: person
718 user--verbose: person
719 user--verbose: person
719 user--verbose: person
720 user--verbose: other@place
720 user--verbose: other@place
721 user--verbose: A. N. Other <other@place>
721 user--verbose: A. N. Other <other@place>
722 user--verbose: User Name <user@hostname>
722 user--verbose: User Name <user@hostname>
723 user--debug: test
723 user--debug: test
724 user--debug: User Name <user@hostname>
724 user--debug: User Name <user@hostname>
725 user--debug: person
725 user--debug: person
726 user--debug: person
726 user--debug: person
727 user--debug: person
727 user--debug: person
728 user--debug: person
728 user--debug: person
729 user--debug: other@place
729 user--debug: other@place
730 user--debug: A. N. Other <other@place>
730 user--debug: A. N. Other <other@place>
731 user--debug: User Name <user@hostname>
731 user--debug: User Name <user@hostname>
732
732
733 Add a dummy commit to make up for the instability of the above:
733 Add a dummy commit to make up for the instability of the above:
734
734
735 $ echo a > a
735 $ echo a > a
736 $ hg add a
736 $ hg add a
737 $ hg ci -m future
737 $ hg ci -m future
738
738
739 Add a commit that does all possible modifications at once
739 Add a commit that does all possible modifications at once
740
740
741 $ echo modify >> third
741 $ echo modify >> third
742 $ touch b
742 $ touch b
743 $ hg add b
743 $ hg add b
744 $ hg mv fourth fifth
744 $ hg mv fourth fifth
745 $ hg rm a
745 $ hg rm a
746 $ hg ci -m "Modify, add, remove, rename"
746 $ hg ci -m "Modify, add, remove, rename"
747
747
748 Test files list:
748 Test files list:
749
749
750 $ hg log -l1 -T '{join(file_mods, " ")}\n'
750 $ hg log -l1 -T '{join(file_mods, " ")}\n'
751 third
751 third
752 $ hg log -l1 -T '{file_mods % "{file}\n"}'
752 $ hg log -l1 -T '{file_mods % "{file}\n"}'
753 third
753 third
754 $ hg log -l1 -T '{file_mods % "{path}\n"}'
754 $ hg log -l1 -T '{file_mods % "{path}\n"}'
755 third
755 third
756
756
757 $ hg log -l1 -T '{join(files, " ")}\n'
757 $ hg log -l1 -T '{join(files, " ")}\n'
758 a b fifth fourth third
758 a b fifth fourth third
759 $ hg log -l1 -T '{files % "{file}\n"}'
759 $ hg log -l1 -T '{files % "{file}\n"}'
760 a
760 a
761 b
761 b
762 fifth
762 fifth
763 fourth
763 fourth
764 third
764 third
765 $ hg log -l1 -T '{files % "{path}\n"}'
765 $ hg log -l1 -T '{files % "{path}\n"}'
766 a
766 a
767 b
767 b
768 fifth
768 fifth
769 fourth
769 fourth
770 third
770 third
771
771
772 Test file copies dict:
773
774 $ hg log -r8 -T '{join(file_copies, " ")}\n'
775 fourth (second)
776 $ hg log -r8 -T '{file_copies % "{name} <- {source}\n"}'
777 fourth <- second
778 $ hg log -r8 -T '{file_copies % "{path} <- {source}\n"}'
779 fourth <- second
780
781 $ hg log -r8 -T '{join(file_copies_switch, " ")}\n'
782
783 $ hg log -r8 -C -T '{join(file_copies_switch, " ")}\n'
784 fourth (second)
785 $ hg log -r8 -C -T '{file_copies_switch % "{name} <- {source}\n"}'
786 fourth <- second
787 $ hg log -r8 -C -T '{file_copies_switch % "{path} <- {source}\n"}'
788 fourth <- second
789
772 Test index keyword:
790 Test index keyword:
773
791
774 $ hg log -l 2 -T '{index + 10}{files % " {index}:{file}"}\n'
792 $ hg log -l 2 -T '{index + 10}{files % " {index}:{file}"}\n'
775 10 0:a 1:b 2:fifth 3:fourth 4:third
793 10 0:a 1:b 2:fifth 3:fourth 4:third
776 11 0:a
794 11 0:a
777
795
778 $ hg branches -T '{index} {branch}\n'
796 $ hg branches -T '{index} {branch}\n'
779 0 default
797 0 default
780 1 foo
798 1 foo
781
799
782 ui verbosity:
800 ui verbosity:
783
801
784 $ hg log -l1 -T '{verbosity}\n'
802 $ hg log -l1 -T '{verbosity}\n'
785
803
786 $ hg log -l1 -T '{verbosity}\n' --debug
804 $ hg log -l1 -T '{verbosity}\n' --debug
787 debug
805 debug
788 $ hg log -l1 -T '{verbosity}\n' --quiet
806 $ hg log -l1 -T '{verbosity}\n' --quiet
789 quiet
807 quiet
790 $ hg log -l1 -T '{verbosity}\n' --verbose
808 $ hg log -l1 -T '{verbosity}\n' --verbose
791 verbose
809 verbose
792
810
793 $ cd ..
811 $ cd ..
794
812
795 latesttag:
813 latesttag:
796
814
797 $ hg init latesttag
815 $ hg init latesttag
798 $ cd latesttag
816 $ cd latesttag
799
817
800 $ echo a > file
818 $ echo a > file
801 $ hg ci -Am a -d '0 0'
819 $ hg ci -Am a -d '0 0'
802 adding file
820 adding file
803
821
804 $ echo b >> file
822 $ echo b >> file
805 $ hg ci -m b -d '1 0'
823 $ hg ci -m b -d '1 0'
806
824
807 $ echo c >> head1
825 $ echo c >> head1
808 $ hg ci -Am h1c -d '2 0'
826 $ hg ci -Am h1c -d '2 0'
809 adding head1
827 adding head1
810
828
811 $ hg update -q 1
829 $ hg update -q 1
812 $ echo d >> head2
830 $ echo d >> head2
813 $ hg ci -Am h2d -d '3 0'
831 $ hg ci -Am h2d -d '3 0'
814 adding head2
832 adding head2
815 created new head
833 created new head
816
834
817 $ echo e >> head2
835 $ echo e >> head2
818 $ hg ci -m h2e -d '4 0'
836 $ hg ci -m h2e -d '4 0'
819
837
820 $ hg merge -q
838 $ hg merge -q
821 $ hg ci -m merge -d '5 -3600'
839 $ hg ci -m merge -d '5 -3600'
822
840
823 No tag set:
841 No tag set:
824
842
825 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
843 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
826 @ 5: null+5
844 @ 5: null+5
827 |\
845 |\
828 | o 4: null+4
846 | o 4: null+4
829 | |
847 | |
830 | o 3: null+3
848 | o 3: null+3
831 | |
849 | |
832 o | 2: null+3
850 o | 2: null+3
833 |/
851 |/
834 o 1: null+2
852 o 1: null+2
835 |
853 |
836 o 0: null+1
854 o 0: null+1
837
855
838
856
839 One common tag: longest path wins for {latesttagdistance}:
857 One common tag: longest path wins for {latesttagdistance}:
840
858
841 $ hg tag -r 1 -m t1 -d '6 0' t1
859 $ hg tag -r 1 -m t1 -d '6 0' t1
842 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
860 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
843 @ 6: t1+4
861 @ 6: t1+4
844 |
862 |
845 o 5: t1+3
863 o 5: t1+3
846 |\
864 |\
847 | o 4: t1+2
865 | o 4: t1+2
848 | |
866 | |
849 | o 3: t1+1
867 | o 3: t1+1
850 | |
868 | |
851 o | 2: t1+1
869 o | 2: t1+1
852 |/
870 |/
853 o 1: t1+0
871 o 1: t1+0
854 |
872 |
855 o 0: null+1
873 o 0: null+1
856
874
857
875
858 One ancestor tag: closest wins:
876 One ancestor tag: closest wins:
859
877
860 $ hg tag -r 2 -m t2 -d '7 0' t2
878 $ hg tag -r 2 -m t2 -d '7 0' t2
861 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
879 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
862 @ 7: t2+3
880 @ 7: t2+3
863 |
881 |
864 o 6: t2+2
882 o 6: t2+2
865 |
883 |
866 o 5: t2+1
884 o 5: t2+1
867 |\
885 |\
868 | o 4: t1+2
886 | o 4: t1+2
869 | |
887 | |
870 | o 3: t1+1
888 | o 3: t1+1
871 | |
889 | |
872 o | 2: t2+0
890 o | 2: t2+0
873 |/
891 |/
874 o 1: t1+0
892 o 1: t1+0
875 |
893 |
876 o 0: null+1
894 o 0: null+1
877
895
878
896
879 Two branch tags: more recent wins if same number of changes:
897 Two branch tags: more recent wins if same number of changes:
880
898
881 $ hg tag -r 3 -m t3 -d '8 0' t3
899 $ hg tag -r 3 -m t3 -d '8 0' t3
882 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
900 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
883 @ 8: t3+5
901 @ 8: t3+5
884 |
902 |
885 o 7: t3+4
903 o 7: t3+4
886 |
904 |
887 o 6: t3+3
905 o 6: t3+3
888 |
906 |
889 o 5: t3+2
907 o 5: t3+2
890 |\
908 |\
891 | o 4: t3+1
909 | o 4: t3+1
892 | |
910 | |
893 | o 3: t3+0
911 | o 3: t3+0
894 | |
912 | |
895 o | 2: t2+0
913 o | 2: t2+0
896 |/
914 |/
897 o 1: t1+0
915 o 1: t1+0
898 |
916 |
899 o 0: null+1
917 o 0: null+1
900
918
901
919
902 Two branch tags: fewest changes wins:
920 Two branch tags: fewest changes wins:
903
921
904 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
922 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
905 $ hg log -G --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
923 $ hg log -G --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
906 @ 9: t4+5,6
924 @ 9: t4+5,6
907 |
925 |
908 o 8: t4+4,5
926 o 8: t4+4,5
909 |
927 |
910 o 7: t4+3,4
928 o 7: t4+3,4
911 |
929 |
912 o 6: t4+2,3
930 o 6: t4+2,3
913 |
931 |
914 o 5: t4+1,2
932 o 5: t4+1,2
915 |\
933 |\
916 | o 4: t4+0,0
934 | o 4: t4+0,0
917 | |
935 | |
918 | o 3: t3+0,0
936 | o 3: t3+0,0
919 | |
937 | |
920 o | 2: t2+0,0
938 o | 2: t2+0,0
921 |/
939 |/
922 o 1: t1+0,0
940 o 1: t1+0,0
923 |
941 |
924 o 0: null+1,1
942 o 0: null+1,1
925
943
926
944
927 Merged tag overrides:
945 Merged tag overrides:
928
946
929 $ hg tag -r 5 -m t5 -d '9 0' t5
947 $ hg tag -r 5 -m t5 -d '9 0' t5
930 $ hg tag -r 3 -m at3 -d '10 0' at3
948 $ hg tag -r 3 -m at3 -d '10 0' at3
931 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
949 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
932 @ 11: t5+6
950 @ 11: t5+6
933 |
951 |
934 o 10: t5+5
952 o 10: t5+5
935 |
953 |
936 o 9: t5+4
954 o 9: t5+4
937 |
955 |
938 o 8: t5+3
956 o 8: t5+3
939 |
957 |
940 o 7: t5+2
958 o 7: t5+2
941 |
959 |
942 o 6: t5+1
960 o 6: t5+1
943 |
961 |
944 o 5: t5+0
962 o 5: t5+0
945 |\
963 |\
946 | o 4: t4+0
964 | o 4: t4+0
947 | |
965 | |
948 | o 3: at3:t3+0
966 | o 3: at3:t3+0
949 | |
967 | |
950 o | 2: t2+0
968 o | 2: t2+0
951 |/
969 |/
952 o 1: t1+0
970 o 1: t1+0
953 |
971 |
954 o 0: null+1
972 o 0: null+1
955
973
956
974
957 $ hg log -G --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
975 $ hg log -G --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
958 @ 11: t5+6,6
976 @ 11: t5+6,6
959 |
977 |
960 o 10: t5+5,5
978 o 10: t5+5,5
961 |
979 |
962 o 9: t5+4,4
980 o 9: t5+4,4
963 |
981 |
964 o 8: t5+3,3
982 o 8: t5+3,3
965 |
983 |
966 o 7: t5+2,2
984 o 7: t5+2,2
967 |
985 |
968 o 6: t5+1,1
986 o 6: t5+1,1
969 |
987 |
970 o 5: t5+0,0
988 o 5: t5+0,0
971 |\
989 |\
972 | o 4: t4+0,0
990 | o 4: t4+0,0
973 | |
991 | |
974 | o 3: at3+0,0 t3+0,0
992 | o 3: at3+0,0 t3+0,0
975 | |
993 | |
976 o | 2: t2+0,0
994 o | 2: t2+0,0
977 |/
995 |/
978 o 1: t1+0,0
996 o 1: t1+0,0
979 |
997 |
980 o 0: null+1,1
998 o 0: null+1,1
981
999
982
1000
983 $ cd ..
1001 $ cd ..
984
1002
985 Set up repository containing template fragments in commit metadata:
1003 Set up repository containing template fragments in commit metadata:
986
1004
987 $ hg init r
1005 $ hg init r
988 $ cd r
1006 $ cd r
989 $ echo a > a
1007 $ echo a > a
990 $ hg ci -Am '{rev}'
1008 $ hg ci -Am '{rev}'
991 adding a
1009 adding a
992
1010
993 $ hg branch -q 'text.{rev}'
1011 $ hg branch -q 'text.{rev}'
994 $ echo aa >> aa
1012 $ echo aa >> aa
995 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
1013 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
996
1014
997 Test termwidth:
1015 Test termwidth:
998
1016
999 $ COLUMNS=25 hg log -l1 --template '{fill(desc, termwidth, "{node|short}:", "termwidth.{rev}:")}'
1017 $ COLUMNS=25 hg log -l1 --template '{fill(desc, termwidth, "{node|short}:", "termwidth.{rev}:")}'
1000 bcc7ff960b8e:desc to be
1018 bcc7ff960b8e:desc to be
1001 termwidth.1:wrapped desc
1019 termwidth.1:wrapped desc
1002 termwidth.1:to be wrapped (no-eol)
1020 termwidth.1:to be wrapped (no-eol)
1003
1021
1004 Just one more commit:
1022 Just one more commit:
1005
1023
1006 $ echo b > b
1024 $ echo b > b
1007 $ hg ci -qAm b
1025 $ hg ci -qAm b
1008
1026
1009 Test 'originalnode'
1027 Test 'originalnode'
1010
1028
1011 $ hg log -r 1 -T '{revset("null") % "{node|short} {originalnode|short}"}\n'
1029 $ hg log -r 1 -T '{revset("null") % "{node|short} {originalnode|short}"}\n'
1012 000000000000 bcc7ff960b8e
1030 000000000000 bcc7ff960b8e
1013 $ hg log -r 0 -T '{manifest % "{node} {originalnode}"}\n'
1031 $ hg log -r 0 -T '{manifest % "{node} {originalnode}"}\n'
1014 a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 f7769ec2ab975ad19684098ad1ffd9b81ecc71a1
1032 a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 f7769ec2ab975ad19684098ad1ffd9b81ecc71a1
1015
1033
1016 Test active bookmark templating
1034 Test active bookmark templating
1017
1035
1018 $ hg book foo
1036 $ hg book foo
1019 $ hg book bar
1037 $ hg book bar
1020 $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, active, \"*\")} '}\n"
1038 $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, active, \"*\")} '}\n"
1021 2 bar* foo
1039 2 bar* foo
1022 1
1040 1
1023 0
1041 0
1024 $ hg log --template "{rev} {activebookmark}\n"
1042 $ hg log --template "{rev} {activebookmark}\n"
1025 2 bar
1043 2 bar
1026 1
1044 1
1027 0
1045 0
1028 $ hg bookmarks --inactive bar
1046 $ hg bookmarks --inactive bar
1029 $ hg log --template "{rev} {activebookmark}\n"
1047 $ hg log --template "{rev} {activebookmark}\n"
1030 2
1048 2
1031 1
1049 1
1032 0
1050 0
1033 $ hg book -r1 baz
1051 $ hg book -r1 baz
1034 $ hg log --template "{rev} {join(bookmarks, ' ')}\n"
1052 $ hg log --template "{rev} {join(bookmarks, ' ')}\n"
1035 2 bar foo
1053 2 bar foo
1036 1 baz
1054 1 baz
1037 0
1055 0
1038 $ hg log --template "{rev} {ifcontains('foo', bookmarks, 't', 'f')}\n"
1056 $ hg log --template "{rev} {ifcontains('foo', bookmarks, 't', 'f')}\n"
1039 2 t
1057 2 t
1040 1 f
1058 1 f
1041 0 f
1059 0 f
1042
1060
1043 Test namespaces dict
1061 Test namespaces dict
1044
1062
1045 $ hg --config extensions.revnamesext=$TESTDIR/revnamesext.py log -T '{rev}\n{namespaces % " {namespace} color={colorname} builtin={builtin}\n {join(names, ",")}\n"}\n'
1063 $ hg --config extensions.revnamesext=$TESTDIR/revnamesext.py log -T '{rev}\n{namespaces % " {namespace} color={colorname} builtin={builtin}\n {join(names, ",")}\n"}\n'
1046 2
1064 2
1047 bookmarks color=bookmark builtin=True
1065 bookmarks color=bookmark builtin=True
1048 bar,foo
1066 bar,foo
1049 tags color=tag builtin=True
1067 tags color=tag builtin=True
1050 tip
1068 tip
1051 branches color=branch builtin=True
1069 branches color=branch builtin=True
1052 text.{rev}
1070 text.{rev}
1053 revnames color=revname builtin=False
1071 revnames color=revname builtin=False
1054 r2
1072 r2
1055
1073
1056 1
1074 1
1057 bookmarks color=bookmark builtin=True
1075 bookmarks color=bookmark builtin=True
1058 baz
1076 baz
1059 tags color=tag builtin=True
1077 tags color=tag builtin=True
1060
1078
1061 branches color=branch builtin=True
1079 branches color=branch builtin=True
1062 text.{rev}
1080 text.{rev}
1063 revnames color=revname builtin=False
1081 revnames color=revname builtin=False
1064 r1
1082 r1
1065
1083
1066 0
1084 0
1067 bookmarks color=bookmark builtin=True
1085 bookmarks color=bookmark builtin=True
1068
1086
1069 tags color=tag builtin=True
1087 tags color=tag builtin=True
1070
1088
1071 branches color=branch builtin=True
1089 branches color=branch builtin=True
1072 default
1090 default
1073 revnames color=revname builtin=False
1091 revnames color=revname builtin=False
1074 r0
1092 r0
1075
1093
1076 $ hg log -r2 -T '{namespaces % "{namespace}: {names}\n"}'
1094 $ hg log -r2 -T '{namespaces % "{namespace}: {names}\n"}'
1077 bookmarks: bar foo
1095 bookmarks: bar foo
1078 tags: tip
1096 tags: tip
1079 branches: text.{rev}
1097 branches: text.{rev}
1080 $ hg log -r2 -T '{namespaces % "{namespace}:\n{names % " {name}\n"}"}'
1098 $ hg log -r2 -T '{namespaces % "{namespace}:\n{names % " {name}\n"}"}'
1081 bookmarks:
1099 bookmarks:
1082 bar
1100 bar
1083 foo
1101 foo
1084 tags:
1102 tags:
1085 tip
1103 tip
1086 branches:
1104 branches:
1087 text.{rev}
1105 text.{rev}
1088 $ hg log -r2 -T '{get(namespaces, "bookmarks") % "{name}\n"}'
1106 $ hg log -r2 -T '{get(namespaces, "bookmarks") % "{name}\n"}'
1089 bar
1107 bar
1090 foo
1108 foo
1091 $ hg log -r2 -T '{namespaces.bookmarks % "{bookmark}\n"}'
1109 $ hg log -r2 -T '{namespaces.bookmarks % "{bookmark}\n"}'
1092 bar
1110 bar
1093 foo
1111 foo
1094
1112
1095 $ cd ..
1113 $ cd ..
1096
1114
1097 Test 'graphwidth' in 'hg log' on various topologies. The key here is that the
1115 Test 'graphwidth' in 'hg log' on various topologies. The key here is that the
1098 printed graphwidths 3, 5, 7, etc. should all line up in their respective
1116 printed graphwidths 3, 5, 7, etc. should all line up in their respective
1099 columns. We don't care about other aspects of the graph rendering here.
1117 columns. We don't care about other aspects of the graph rendering here.
1100
1118
1101 $ hg init graphwidth
1119 $ hg init graphwidth
1102 $ cd graphwidth
1120 $ cd graphwidth
1103
1121
1104 $ wrappabletext="a a a a a a a a a a a a"
1122 $ wrappabletext="a a a a a a a a a a a a"
1105
1123
1106 $ printf "first\n" > file
1124 $ printf "first\n" > file
1107 $ hg add file
1125 $ hg add file
1108 $ hg commit -m "$wrappabletext"
1126 $ hg commit -m "$wrappabletext"
1109
1127
1110 $ printf "first\nsecond\n" > file
1128 $ printf "first\nsecond\n" > file
1111 $ hg commit -m "$wrappabletext"
1129 $ hg commit -m "$wrappabletext"
1112
1130
1113 $ hg checkout 0
1131 $ hg checkout 0
1114 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1132 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1115 $ printf "third\nfirst\n" > file
1133 $ printf "third\nfirst\n" > file
1116 $ hg commit -m "$wrappabletext"
1134 $ hg commit -m "$wrappabletext"
1117 created new head
1135 created new head
1118
1136
1119 $ hg merge
1137 $ hg merge
1120 merging file
1138 merging file
1121 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1139 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1122 (branch merge, don't forget to commit)
1140 (branch merge, don't forget to commit)
1123
1141
1124 $ hg log --graph -T "{graphwidth}"
1142 $ hg log --graph -T "{graphwidth}"
1125 @ 3
1143 @ 3
1126 |
1144 |
1127 | @ 5
1145 | @ 5
1128 |/
1146 |/
1129 o 3
1147 o 3
1130
1148
1131 $ hg commit -m "$wrappabletext"
1149 $ hg commit -m "$wrappabletext"
1132
1150
1133 $ hg log --graph -T "{graphwidth}"
1151 $ hg log --graph -T "{graphwidth}"
1134 @ 5
1152 @ 5
1135 |\
1153 |\
1136 | o 5
1154 | o 5
1137 | |
1155 | |
1138 o | 5
1156 o | 5
1139 |/
1157 |/
1140 o 3
1158 o 3
1141
1159
1142
1160
1143 $ hg checkout 0
1161 $ hg checkout 0
1144 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1162 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1145 $ printf "third\nfirst\nsecond\n" > file
1163 $ printf "third\nfirst\nsecond\n" > file
1146 $ hg commit -m "$wrappabletext"
1164 $ hg commit -m "$wrappabletext"
1147 created new head
1165 created new head
1148
1166
1149 $ hg log --graph -T "{graphwidth}"
1167 $ hg log --graph -T "{graphwidth}"
1150 @ 3
1168 @ 3
1151 |
1169 |
1152 | o 7
1170 | o 7
1153 | |\
1171 | |\
1154 +---o 7
1172 +---o 7
1155 | |
1173 | |
1156 | o 5
1174 | o 5
1157 |/
1175 |/
1158 o 3
1176 o 3
1159
1177
1160
1178
1161 $ hg log --graph -T "{graphwidth}" -r 3
1179 $ hg log --graph -T "{graphwidth}" -r 3
1162 o 5
1180 o 5
1163 |\
1181 |\
1164 ~ ~
1182 ~ ~
1165
1183
1166 $ hg log --graph -T "{graphwidth}" -r 1
1184 $ hg log --graph -T "{graphwidth}" -r 1
1167 o 3
1185 o 3
1168 |
1186 |
1169 ~
1187 ~
1170
1188
1171 $ hg merge
1189 $ hg merge
1172 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1190 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1173 (branch merge, don't forget to commit)
1191 (branch merge, don't forget to commit)
1174 $ hg commit -m "$wrappabletext"
1192 $ hg commit -m "$wrappabletext"
1175
1193
1176 $ printf "seventh\n" >> file
1194 $ printf "seventh\n" >> file
1177 $ hg commit -m "$wrappabletext"
1195 $ hg commit -m "$wrappabletext"
1178
1196
1179 $ hg log --graph -T "{graphwidth}"
1197 $ hg log --graph -T "{graphwidth}"
1180 @ 3
1198 @ 3
1181 |
1199 |
1182 o 5
1200 o 5
1183 |\
1201 |\
1184 | o 5
1202 | o 5
1185 | |
1203 | |
1186 o | 7
1204 o | 7
1187 |\ \
1205 |\ \
1188 | o | 7
1206 | o | 7
1189 | |/
1207 | |/
1190 o / 5
1208 o / 5
1191 |/
1209 |/
1192 o 3
1210 o 3
1193
1211
1194
1212
1195 The point of graphwidth is to allow wrapping that accounts for the space taken
1213 The point of graphwidth is to allow wrapping that accounts for the space taken
1196 by the graph.
1214 by the graph.
1197
1215
1198 $ COLUMNS=10 hg log --graph -T "{fill(desc, termwidth - graphwidth)}"
1216 $ COLUMNS=10 hg log --graph -T "{fill(desc, termwidth - graphwidth)}"
1199 @ a a a a
1217 @ a a a a
1200 | a a a a
1218 | a a a a
1201 | a a a a
1219 | a a a a
1202 o a a a
1220 o a a a
1203 |\ a a a
1221 |\ a a a
1204 | | a a a
1222 | | a a a
1205 | | a a a
1223 | | a a a
1206 | o a a a
1224 | o a a a
1207 | | a a a
1225 | | a a a
1208 | | a a a
1226 | | a a a
1209 | | a a a
1227 | | a a a
1210 o | a a
1228 o | a a
1211 |\ \ a a
1229 |\ \ a a
1212 | | | a a
1230 | | | a a
1213 | | | a a
1231 | | | a a
1214 | | | a a
1232 | | | a a
1215 | | | a a
1233 | | | a a
1216 | o | a a
1234 | o | a a
1217 | |/ a a
1235 | |/ a a
1218 | | a a
1236 | | a a
1219 | | a a
1237 | | a a
1220 | | a a
1238 | | a a
1221 | | a a
1239 | | a a
1222 o | a a a
1240 o | a a a
1223 |/ a a a
1241 |/ a a a
1224 | a a a
1242 | a a a
1225 | a a a
1243 | a a a
1226 o a a a a
1244 o a a a a
1227 a a a a
1245 a a a a
1228 a a a a
1246 a a a a
1229
1247
1230 Something tricky happens when there are elided nodes; the next drawn row of
1248 Something tricky happens when there are elided nodes; the next drawn row of
1231 edges can be more than one column wider, but the graph width only increases by
1249 edges can be more than one column wider, but the graph width only increases by
1232 one column. The remaining columns are added in between the nodes.
1250 one column. The remaining columns are added in between the nodes.
1233
1251
1234 $ hg log --graph -T "{graphwidth}" -r "0|2|4|5"
1252 $ hg log --graph -T "{graphwidth}" -r "0|2|4|5"
1235 o 5
1253 o 5
1236 |\
1254 |\
1237 | \
1255 | \
1238 | :\
1256 | :\
1239 o : : 7
1257 o : : 7
1240 :/ /
1258 :/ /
1241 : o 5
1259 : o 5
1242 :/
1260 :/
1243 o 3
1261 o 3
1244
1262
1245
1263
1246 $ cd ..
1264 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now