##// END OF EJS Templates
templatekw: alias {file} of files list to {path}...
Yuya Nishihara -
r39403:83f8f7b9 default
parent child Browse files
Show More
@@ -1,718 +1,718
1 # templatefuncs.py - common template functions
1 # templatefuncs.py - common template functions
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 re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13 from .node import (
13 from .node import (
14 bin,
14 bin,
15 wdirid,
15 wdirid,
16 )
16 )
17 from . import (
17 from . import (
18 color,
18 color,
19 encoding,
19 encoding,
20 error,
20 error,
21 minirst,
21 minirst,
22 obsutil,
22 obsutil,
23 registrar,
23 registrar,
24 revset as revsetmod,
24 revset as revsetmod,
25 revsetlang,
25 revsetlang,
26 scmutil,
26 scmutil,
27 templatefilters,
27 templatefilters,
28 templatekw,
28 templatekw,
29 templateutil,
29 templateutil,
30 util,
30 util,
31 )
31 )
32 from .utils import (
32 from .utils import (
33 dateutil,
33 dateutil,
34 stringutil,
34 stringutil,
35 )
35 )
36
36
37 evalrawexp = templateutil.evalrawexp
37 evalrawexp = templateutil.evalrawexp
38 evalwrapped = templateutil.evalwrapped
38 evalwrapped = templateutil.evalwrapped
39 evalfuncarg = templateutil.evalfuncarg
39 evalfuncarg = templateutil.evalfuncarg
40 evalboolean = templateutil.evalboolean
40 evalboolean = templateutil.evalboolean
41 evaldate = templateutil.evaldate
41 evaldate = templateutil.evaldate
42 evalinteger = templateutil.evalinteger
42 evalinteger = templateutil.evalinteger
43 evalstring = templateutil.evalstring
43 evalstring = templateutil.evalstring
44 evalstringliteral = templateutil.evalstringliteral
44 evalstringliteral = templateutil.evalstringliteral
45
45
46 # dict of template built-in functions
46 # dict of template built-in functions
47 funcs = {}
47 funcs = {}
48 templatefunc = registrar.templatefunc(funcs)
48 templatefunc = registrar.templatefunc(funcs)
49
49
50 @templatefunc('date(date[, fmt])')
50 @templatefunc('date(date[, fmt])')
51 def date(context, mapping, args):
51 def date(context, mapping, args):
52 """Format a date. See :hg:`help dates` for formatting
52 """Format a date. See :hg:`help dates` for formatting
53 strings. The default is a Unix date format, including the timezone:
53 strings. The default is a Unix date format, including the timezone:
54 "Mon Sep 04 15:13:13 2006 0700"."""
54 "Mon Sep 04 15:13:13 2006 0700"."""
55 if not (1 <= len(args) <= 2):
55 if not (1 <= len(args) <= 2):
56 # i18n: "date" is a keyword
56 # i18n: "date" is a keyword
57 raise error.ParseError(_("date expects one or two arguments"))
57 raise error.ParseError(_("date expects one or two arguments"))
58
58
59 date = evaldate(context, mapping, args[0],
59 date = evaldate(context, mapping, args[0],
60 # i18n: "date" is a keyword
60 # i18n: "date" is a keyword
61 _("date expects a date information"))
61 _("date expects a date information"))
62 fmt = None
62 fmt = None
63 if len(args) == 2:
63 if len(args) == 2:
64 fmt = evalstring(context, mapping, args[1])
64 fmt = evalstring(context, mapping, args[1])
65 if fmt is None:
65 if fmt is None:
66 return dateutil.datestr(date)
66 return dateutil.datestr(date)
67 else:
67 else:
68 return dateutil.datestr(date, fmt)
68 return dateutil.datestr(date, fmt)
69
69
70 @templatefunc('dict([[key=]value...])', argspec='*args **kwargs')
70 @templatefunc('dict([[key=]value...])', argspec='*args **kwargs')
71 def dict_(context, mapping, args):
71 def dict_(context, mapping, args):
72 """Construct a dict from key-value pairs. A key may be omitted if
72 """Construct a dict from key-value pairs. A key may be omitted if
73 a value expression can provide an unambiguous name."""
73 a value expression can provide an unambiguous name."""
74 data = util.sortdict()
74 data = util.sortdict()
75
75
76 for v in args['args']:
76 for v in args['args']:
77 k = templateutil.findsymbolicname(v)
77 k = templateutil.findsymbolicname(v)
78 if not k:
78 if not k:
79 raise error.ParseError(_('dict key cannot be inferred'))
79 raise error.ParseError(_('dict key cannot be inferred'))
80 if k in data or k in args['kwargs']:
80 if k in data or k in args['kwargs']:
81 raise error.ParseError(_("duplicated dict key '%s' inferred") % k)
81 raise error.ParseError(_("duplicated dict key '%s' inferred") % k)
82 data[k] = evalfuncarg(context, mapping, v)
82 data[k] = evalfuncarg(context, mapping, v)
83
83
84 data.update((k, evalfuncarg(context, mapping, v))
84 data.update((k, evalfuncarg(context, mapping, v))
85 for k, v in args['kwargs'].iteritems())
85 for k, v in args['kwargs'].iteritems())
86 return templateutil.hybriddict(data)
86 return templateutil.hybriddict(data)
87
87
88 @templatefunc('diff([includepattern [, excludepattern]])', requires={'ctx'})
88 @templatefunc('diff([includepattern [, excludepattern]])', requires={'ctx'})
89 def diff(context, mapping, args):
89 def diff(context, mapping, args):
90 """Show a diff, optionally
90 """Show a diff, optionally
91 specifying files to include or exclude."""
91 specifying files to include or exclude."""
92 if len(args) > 2:
92 if len(args) > 2:
93 # i18n: "diff" is a keyword
93 # i18n: "diff" is a keyword
94 raise error.ParseError(_("diff expects zero, one, or two arguments"))
94 raise error.ParseError(_("diff expects zero, one, or two arguments"))
95
95
96 def getpatterns(i):
96 def getpatterns(i):
97 if i < len(args):
97 if i < len(args):
98 s = evalstring(context, mapping, args[i]).strip()
98 s = evalstring(context, mapping, args[i]).strip()
99 if s:
99 if s:
100 return [s]
100 return [s]
101 return []
101 return []
102
102
103 ctx = context.resource(mapping, 'ctx')
103 ctx = context.resource(mapping, 'ctx')
104 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
104 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
105
105
106 return ''.join(chunks)
106 return ''.join(chunks)
107
107
108 @templatefunc('extdata(source)', argspec='source', requires={'ctx', 'cache'})
108 @templatefunc('extdata(source)', argspec='source', requires={'ctx', 'cache'})
109 def extdata(context, mapping, args):
109 def extdata(context, mapping, args):
110 """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
110 """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
111 if 'source' not in args:
111 if 'source' not in args:
112 # i18n: "extdata" is a keyword
112 # i18n: "extdata" is a keyword
113 raise error.ParseError(_('extdata expects one argument'))
113 raise error.ParseError(_('extdata expects one argument'))
114
114
115 source = evalstring(context, mapping, args['source'])
115 source = evalstring(context, mapping, args['source'])
116 if not source:
116 if not source:
117 sym = templateutil.findsymbolicname(args['source'])
117 sym = templateutil.findsymbolicname(args['source'])
118 if sym:
118 if sym:
119 raise error.ParseError(_('empty data source specified'),
119 raise error.ParseError(_('empty data source specified'),
120 hint=_("did you mean extdata('%s')?") % sym)
120 hint=_("did you mean extdata('%s')?") % sym)
121 else:
121 else:
122 raise error.ParseError(_('empty data source specified'))
122 raise error.ParseError(_('empty data source specified'))
123 cache = context.resource(mapping, 'cache').setdefault('extdata', {})
123 cache = context.resource(mapping, 'cache').setdefault('extdata', {})
124 ctx = context.resource(mapping, 'ctx')
124 ctx = context.resource(mapping, 'ctx')
125 if source in cache:
125 if source in cache:
126 data = cache[source]
126 data = cache[source]
127 else:
127 else:
128 data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
128 data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
129 return data.get(ctx.rev(), '')
129 return data.get(ctx.rev(), '')
130
130
131 @templatefunc('files(pattern)', requires={'ctx'})
131 @templatefunc('files(pattern)', requires={'ctx'})
132 def files(context, mapping, args):
132 def files(context, mapping, args):
133 """All files of the current changeset matching the pattern. See
133 """All files of the current changeset matching the pattern. See
134 :hg:`help patterns`."""
134 :hg:`help patterns`."""
135 if not len(args) == 1:
135 if not len(args) == 1:
136 # i18n: "files" is a keyword
136 # i18n: "files" is a keyword
137 raise error.ParseError(_("files expects one argument"))
137 raise error.ParseError(_("files expects one argument"))
138
138
139 raw = evalstring(context, mapping, args[0])
139 raw = evalstring(context, mapping, args[0])
140 ctx = context.resource(mapping, 'ctx')
140 ctx = context.resource(mapping, 'ctx')
141 m = ctx.match([raw])
141 m = ctx.match([raw])
142 files = list(ctx.matches(m))
142 files = list(ctx.matches(m))
143 return templateutil.compatlist(context, mapping, "file", files)
143 return templateutil.compatfileslist(context, mapping, "file", files)
144
144
145 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
145 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
146 def fill(context, mapping, args):
146 def fill(context, mapping, args):
147 """Fill many
147 """Fill many
148 paragraphs with optional indentation. See the "fill" filter."""
148 paragraphs with optional indentation. See the "fill" filter."""
149 if not (1 <= len(args) <= 4):
149 if not (1 <= len(args) <= 4):
150 # i18n: "fill" is a keyword
150 # i18n: "fill" is a keyword
151 raise error.ParseError(_("fill expects one to four arguments"))
151 raise error.ParseError(_("fill expects one to four arguments"))
152
152
153 text = evalstring(context, mapping, args[0])
153 text = evalstring(context, mapping, args[0])
154 width = 76
154 width = 76
155 initindent = ''
155 initindent = ''
156 hangindent = ''
156 hangindent = ''
157 if 2 <= len(args) <= 4:
157 if 2 <= len(args) <= 4:
158 width = evalinteger(context, mapping, args[1],
158 width = evalinteger(context, mapping, args[1],
159 # i18n: "fill" is a keyword
159 # i18n: "fill" is a keyword
160 _("fill expects an integer width"))
160 _("fill expects an integer width"))
161 try:
161 try:
162 initindent = evalstring(context, mapping, args[2])
162 initindent = evalstring(context, mapping, args[2])
163 hangindent = evalstring(context, mapping, args[3])
163 hangindent = evalstring(context, mapping, args[3])
164 except IndexError:
164 except IndexError:
165 pass
165 pass
166
166
167 return templatefilters.fill(text, width, initindent, hangindent)
167 return templatefilters.fill(text, width, initindent, hangindent)
168
168
169 @templatefunc('filter(iterable[, expr])')
169 @templatefunc('filter(iterable[, expr])')
170 def filter_(context, mapping, args):
170 def filter_(context, mapping, args):
171 """Remove empty elements from a list or a dict. If expr specified, it's
171 """Remove empty elements from a list or a dict. If expr specified, it's
172 applied to each element to test emptiness."""
172 applied to each element to test emptiness."""
173 if not (1 <= len(args) <= 2):
173 if not (1 <= len(args) <= 2):
174 # i18n: "filter" is a keyword
174 # i18n: "filter" is a keyword
175 raise error.ParseError(_("filter expects one or two arguments"))
175 raise error.ParseError(_("filter expects one or two arguments"))
176 iterable = evalwrapped(context, mapping, args[0])
176 iterable = evalwrapped(context, mapping, args[0])
177 if len(args) == 1:
177 if len(args) == 1:
178 def select(w):
178 def select(w):
179 return w.tobool(context, mapping)
179 return w.tobool(context, mapping)
180 else:
180 else:
181 def select(w):
181 def select(w):
182 if not isinstance(w, templateutil.mappable):
182 if not isinstance(w, templateutil.mappable):
183 raise error.ParseError(_("not filterable by expression"))
183 raise error.ParseError(_("not filterable by expression"))
184 lm = context.overlaymap(mapping, w.tomap(context))
184 lm = context.overlaymap(mapping, w.tomap(context))
185 return evalboolean(context, lm, args[1])
185 return evalboolean(context, lm, args[1])
186 return iterable.filter(context, mapping, select)
186 return iterable.filter(context, mapping, select)
187
187
188 @templatefunc('formatnode(node)', requires={'ui'})
188 @templatefunc('formatnode(node)', requires={'ui'})
189 def formatnode(context, mapping, args):
189 def formatnode(context, mapping, args):
190 """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
190 """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
191 if len(args) != 1:
191 if len(args) != 1:
192 # i18n: "formatnode" is a keyword
192 # i18n: "formatnode" is a keyword
193 raise error.ParseError(_("formatnode expects one argument"))
193 raise error.ParseError(_("formatnode expects one argument"))
194
194
195 ui = context.resource(mapping, 'ui')
195 ui = context.resource(mapping, 'ui')
196 node = evalstring(context, mapping, args[0])
196 node = evalstring(context, mapping, args[0])
197 if ui.debugflag:
197 if ui.debugflag:
198 return node
198 return node
199 return templatefilters.short(node)
199 return templatefilters.short(node)
200
200
201 @templatefunc('mailmap(author)', requires={'repo', 'cache'})
201 @templatefunc('mailmap(author)', requires={'repo', 'cache'})
202 def mailmap(context, mapping, args):
202 def mailmap(context, mapping, args):
203 """Return the author, updated according to the value
203 """Return the author, updated according to the value
204 set in the .mailmap file"""
204 set in the .mailmap file"""
205 if len(args) != 1:
205 if len(args) != 1:
206 raise error.ParseError(_("mailmap expects one argument"))
206 raise error.ParseError(_("mailmap expects one argument"))
207
207
208 author = evalstring(context, mapping, args[0])
208 author = evalstring(context, mapping, args[0])
209
209
210 cache = context.resource(mapping, 'cache')
210 cache = context.resource(mapping, 'cache')
211 repo = context.resource(mapping, 'repo')
211 repo = context.resource(mapping, 'repo')
212
212
213 if 'mailmap' not in cache:
213 if 'mailmap' not in cache:
214 data = repo.wvfs.tryread('.mailmap')
214 data = repo.wvfs.tryread('.mailmap')
215 cache['mailmap'] = stringutil.parsemailmap(data)
215 cache['mailmap'] = stringutil.parsemailmap(data)
216
216
217 return stringutil.mapname(cache['mailmap'], author)
217 return stringutil.mapname(cache['mailmap'], author)
218
218
219 @templatefunc('pad(text, width[, fillchar=\' \'[, left=False]])',
219 @templatefunc('pad(text, width[, fillchar=\' \'[, left=False]])',
220 argspec='text width fillchar left')
220 argspec='text width fillchar left')
221 def pad(context, mapping, args):
221 def pad(context, mapping, args):
222 """Pad text with a
222 """Pad text with a
223 fill character."""
223 fill character."""
224 if 'text' not in args or 'width' not in args:
224 if 'text' not in args or 'width' not in args:
225 # i18n: "pad" is a keyword
225 # i18n: "pad" is a keyword
226 raise error.ParseError(_("pad() expects two to four arguments"))
226 raise error.ParseError(_("pad() expects two to four arguments"))
227
227
228 width = evalinteger(context, mapping, args['width'],
228 width = evalinteger(context, mapping, args['width'],
229 # i18n: "pad" is a keyword
229 # i18n: "pad" is a keyword
230 _("pad() expects an integer width"))
230 _("pad() expects an integer width"))
231
231
232 text = evalstring(context, mapping, args['text'])
232 text = evalstring(context, mapping, args['text'])
233
233
234 left = False
234 left = False
235 fillchar = ' '
235 fillchar = ' '
236 if 'fillchar' in args:
236 if 'fillchar' in args:
237 fillchar = evalstring(context, mapping, args['fillchar'])
237 fillchar = evalstring(context, mapping, args['fillchar'])
238 if len(color.stripeffects(fillchar)) != 1:
238 if len(color.stripeffects(fillchar)) != 1:
239 # i18n: "pad" is a keyword
239 # i18n: "pad" is a keyword
240 raise error.ParseError(_("pad() expects a single fill character"))
240 raise error.ParseError(_("pad() expects a single fill character"))
241 if 'left' in args:
241 if 'left' in args:
242 left = evalboolean(context, mapping, args['left'])
242 left = evalboolean(context, mapping, args['left'])
243
243
244 fillwidth = width - encoding.colwidth(color.stripeffects(text))
244 fillwidth = width - encoding.colwidth(color.stripeffects(text))
245 if fillwidth <= 0:
245 if fillwidth <= 0:
246 return text
246 return text
247 if left:
247 if left:
248 return fillchar * fillwidth + text
248 return fillchar * fillwidth + text
249 else:
249 else:
250 return text + fillchar * fillwidth
250 return text + fillchar * fillwidth
251
251
252 @templatefunc('indent(text, indentchars[, firstline])')
252 @templatefunc('indent(text, indentchars[, firstline])')
253 def indent(context, mapping, args):
253 def indent(context, mapping, args):
254 """Indents all non-empty lines
254 """Indents all non-empty lines
255 with the characters given in the indentchars string. An optional
255 with the characters given in the indentchars string. An optional
256 third parameter will override the indent for the first line only
256 third parameter will override the indent for the first line only
257 if present."""
257 if present."""
258 if not (2 <= len(args) <= 3):
258 if not (2 <= len(args) <= 3):
259 # i18n: "indent" is a keyword
259 # i18n: "indent" is a keyword
260 raise error.ParseError(_("indent() expects two or three arguments"))
260 raise error.ParseError(_("indent() expects two or three arguments"))
261
261
262 text = evalstring(context, mapping, args[0])
262 text = evalstring(context, mapping, args[0])
263 indent = evalstring(context, mapping, args[1])
263 indent = evalstring(context, mapping, args[1])
264
264
265 if len(args) == 3:
265 if len(args) == 3:
266 firstline = evalstring(context, mapping, args[2])
266 firstline = evalstring(context, mapping, args[2])
267 else:
267 else:
268 firstline = indent
268 firstline = indent
269
269
270 # the indent function doesn't indent the first line, so we do it here
270 # the indent function doesn't indent the first line, so we do it here
271 return templatefilters.indent(firstline + text, indent)
271 return templatefilters.indent(firstline + text, indent)
272
272
273 @templatefunc('get(dict, key)')
273 @templatefunc('get(dict, key)')
274 def get(context, mapping, args):
274 def get(context, mapping, args):
275 """Get an attribute/key from an object. Some keywords
275 """Get an attribute/key from an object. Some keywords
276 are complex types. This function allows you to obtain the value of an
276 are complex types. This function allows you to obtain the value of an
277 attribute on these types."""
277 attribute on these types."""
278 if len(args) != 2:
278 if len(args) != 2:
279 # i18n: "get" is a keyword
279 # i18n: "get" is a keyword
280 raise error.ParseError(_("get() expects two arguments"))
280 raise error.ParseError(_("get() expects two arguments"))
281
281
282 dictarg = evalwrapped(context, mapping, args[0])
282 dictarg = evalwrapped(context, mapping, args[0])
283 key = evalrawexp(context, mapping, args[1])
283 key = evalrawexp(context, mapping, args[1])
284 try:
284 try:
285 return dictarg.getmember(context, mapping, key)
285 return dictarg.getmember(context, mapping, key)
286 except error.ParseError as err:
286 except error.ParseError as err:
287 # i18n: "get" is a keyword
287 # i18n: "get" is a keyword
288 hint = _("get() expects a dict as first argument")
288 hint = _("get() expects a dict as first argument")
289 raise error.ParseError(bytes(err), hint=hint)
289 raise error.ParseError(bytes(err), hint=hint)
290
290
291 @templatefunc('if(expr, then[, else])')
291 @templatefunc('if(expr, then[, else])')
292 def if_(context, mapping, args):
292 def if_(context, mapping, args):
293 """Conditionally execute based on the result of
293 """Conditionally execute based on the result of
294 an expression."""
294 an expression."""
295 if not (2 <= len(args) <= 3):
295 if not (2 <= len(args) <= 3):
296 # i18n: "if" is a keyword
296 # i18n: "if" is a keyword
297 raise error.ParseError(_("if expects two or three arguments"))
297 raise error.ParseError(_("if expects two or three arguments"))
298
298
299 test = evalboolean(context, mapping, args[0])
299 test = evalboolean(context, mapping, args[0])
300 if test:
300 if test:
301 return evalrawexp(context, mapping, args[1])
301 return evalrawexp(context, mapping, args[1])
302 elif len(args) == 3:
302 elif len(args) == 3:
303 return evalrawexp(context, mapping, args[2])
303 return evalrawexp(context, mapping, args[2])
304
304
305 @templatefunc('ifcontains(needle, haystack, then[, else])')
305 @templatefunc('ifcontains(needle, haystack, then[, else])')
306 def ifcontains(context, mapping, args):
306 def ifcontains(context, mapping, args):
307 """Conditionally execute based
307 """Conditionally execute based
308 on whether the item "needle" is in "haystack"."""
308 on whether the item "needle" is in "haystack"."""
309 if not (3 <= len(args) <= 4):
309 if not (3 <= len(args) <= 4):
310 # i18n: "ifcontains" is a keyword
310 # i18n: "ifcontains" is a keyword
311 raise error.ParseError(_("ifcontains expects three or four arguments"))
311 raise error.ParseError(_("ifcontains expects three or four arguments"))
312
312
313 haystack = evalwrapped(context, mapping, args[1])
313 haystack = evalwrapped(context, mapping, args[1])
314 try:
314 try:
315 needle = evalrawexp(context, mapping, args[0])
315 needle = evalrawexp(context, mapping, args[0])
316 found = haystack.contains(context, mapping, needle)
316 found = haystack.contains(context, mapping, needle)
317 except error.ParseError:
317 except error.ParseError:
318 found = False
318 found = False
319
319
320 if found:
320 if found:
321 return evalrawexp(context, mapping, args[2])
321 return evalrawexp(context, mapping, args[2])
322 elif len(args) == 4:
322 elif len(args) == 4:
323 return evalrawexp(context, mapping, args[3])
323 return evalrawexp(context, mapping, args[3])
324
324
325 @templatefunc('ifeq(expr1, expr2, then[, else])')
325 @templatefunc('ifeq(expr1, expr2, then[, else])')
326 def ifeq(context, mapping, args):
326 def ifeq(context, mapping, args):
327 """Conditionally execute based on
327 """Conditionally execute based on
328 whether 2 items are equivalent."""
328 whether 2 items are equivalent."""
329 if not (3 <= len(args) <= 4):
329 if not (3 <= len(args) <= 4):
330 # i18n: "ifeq" is a keyword
330 # i18n: "ifeq" is a keyword
331 raise error.ParseError(_("ifeq expects three or four arguments"))
331 raise error.ParseError(_("ifeq expects three or four arguments"))
332
332
333 test = evalstring(context, mapping, args[0])
333 test = evalstring(context, mapping, args[0])
334 match = evalstring(context, mapping, args[1])
334 match = evalstring(context, mapping, args[1])
335 if test == match:
335 if test == match:
336 return evalrawexp(context, mapping, args[2])
336 return evalrawexp(context, mapping, args[2])
337 elif len(args) == 4:
337 elif len(args) == 4:
338 return evalrawexp(context, mapping, args[3])
338 return evalrawexp(context, mapping, args[3])
339
339
340 @templatefunc('join(list, sep)')
340 @templatefunc('join(list, sep)')
341 def join(context, mapping, args):
341 def join(context, mapping, args):
342 """Join items in a list with a delimiter."""
342 """Join items in a list with a delimiter."""
343 if not (1 <= len(args) <= 2):
343 if not (1 <= len(args) <= 2):
344 # i18n: "join" is a keyword
344 # i18n: "join" is a keyword
345 raise error.ParseError(_("join expects one or two arguments"))
345 raise error.ParseError(_("join expects one or two arguments"))
346
346
347 joinset = evalwrapped(context, mapping, args[0])
347 joinset = evalwrapped(context, mapping, args[0])
348 joiner = " "
348 joiner = " "
349 if len(args) > 1:
349 if len(args) > 1:
350 joiner = evalstring(context, mapping, args[1])
350 joiner = evalstring(context, mapping, args[1])
351 return joinset.join(context, mapping, joiner)
351 return joinset.join(context, mapping, joiner)
352
352
353 @templatefunc('label(label, expr)', requires={'ui'})
353 @templatefunc('label(label, expr)', requires={'ui'})
354 def label(context, mapping, args):
354 def label(context, mapping, args):
355 """Apply a label to generated content. Content with
355 """Apply a label to generated content. Content with
356 a label applied can result in additional post-processing, such as
356 a label applied can result in additional post-processing, such as
357 automatic colorization."""
357 automatic colorization."""
358 if len(args) != 2:
358 if len(args) != 2:
359 # i18n: "label" is a keyword
359 # i18n: "label" is a keyword
360 raise error.ParseError(_("label expects two arguments"))
360 raise error.ParseError(_("label expects two arguments"))
361
361
362 ui = context.resource(mapping, 'ui')
362 ui = context.resource(mapping, 'ui')
363 thing = evalstring(context, mapping, args[1])
363 thing = evalstring(context, mapping, args[1])
364 # preserve unknown symbol as literal so effects like 'red', 'bold',
364 # preserve unknown symbol as literal so effects like 'red', 'bold',
365 # etc. don't need to be quoted
365 # etc. don't need to be quoted
366 label = evalstringliteral(context, mapping, args[0])
366 label = evalstringliteral(context, mapping, args[0])
367
367
368 return ui.label(thing, label)
368 return ui.label(thing, label)
369
369
370 @templatefunc('latesttag([pattern])')
370 @templatefunc('latesttag([pattern])')
371 def latesttag(context, mapping, args):
371 def latesttag(context, mapping, args):
372 """The global tags matching the given pattern on the
372 """The global tags matching the given pattern on the
373 most recent globally tagged ancestor of this changeset.
373 most recent globally tagged ancestor of this changeset.
374 If no such tags exist, the "{tag}" template resolves to
374 If no such tags exist, the "{tag}" template resolves to
375 the string "null". See :hg:`help revisions.patterns` for the pattern
375 the string "null". See :hg:`help revisions.patterns` for the pattern
376 syntax.
376 syntax.
377 """
377 """
378 if len(args) > 1:
378 if len(args) > 1:
379 # i18n: "latesttag" is a keyword
379 # i18n: "latesttag" is a keyword
380 raise error.ParseError(_("latesttag expects at most one argument"))
380 raise error.ParseError(_("latesttag expects at most one argument"))
381
381
382 pattern = None
382 pattern = None
383 if len(args) == 1:
383 if len(args) == 1:
384 pattern = evalstring(context, mapping, args[0])
384 pattern = evalstring(context, mapping, args[0])
385 return templatekw.showlatesttags(context, mapping, pattern)
385 return templatekw.showlatesttags(context, mapping, pattern)
386
386
387 @templatefunc('localdate(date[, tz])')
387 @templatefunc('localdate(date[, tz])')
388 def localdate(context, mapping, args):
388 def localdate(context, mapping, args):
389 """Converts a date to the specified timezone.
389 """Converts a date to the specified timezone.
390 The default is local date."""
390 The default is local date."""
391 if not (1 <= len(args) <= 2):
391 if not (1 <= len(args) <= 2):
392 # i18n: "localdate" is a keyword
392 # i18n: "localdate" is a keyword
393 raise error.ParseError(_("localdate expects one or two arguments"))
393 raise error.ParseError(_("localdate expects one or two arguments"))
394
394
395 date = evaldate(context, mapping, args[0],
395 date = evaldate(context, mapping, args[0],
396 # i18n: "localdate" is a keyword
396 # i18n: "localdate" is a keyword
397 _("localdate expects a date information"))
397 _("localdate expects a date information"))
398 if len(args) >= 2:
398 if len(args) >= 2:
399 tzoffset = None
399 tzoffset = None
400 tz = evalfuncarg(context, mapping, args[1])
400 tz = evalfuncarg(context, mapping, args[1])
401 if isinstance(tz, bytes):
401 if isinstance(tz, bytes):
402 tzoffset, remainder = dateutil.parsetimezone(tz)
402 tzoffset, remainder = dateutil.parsetimezone(tz)
403 if remainder:
403 if remainder:
404 tzoffset = None
404 tzoffset = None
405 if tzoffset is None:
405 if tzoffset is None:
406 try:
406 try:
407 tzoffset = int(tz)
407 tzoffset = int(tz)
408 except (TypeError, ValueError):
408 except (TypeError, ValueError):
409 # i18n: "localdate" is a keyword
409 # i18n: "localdate" is a keyword
410 raise error.ParseError(_("localdate expects a timezone"))
410 raise error.ParseError(_("localdate expects a timezone"))
411 else:
411 else:
412 tzoffset = dateutil.makedate()[1]
412 tzoffset = dateutil.makedate()[1]
413 return templateutil.date((date[0], tzoffset))
413 return templateutil.date((date[0], tzoffset))
414
414
415 @templatefunc('max(iterable)')
415 @templatefunc('max(iterable)')
416 def max_(context, mapping, args, **kwargs):
416 def max_(context, mapping, args, **kwargs):
417 """Return the max of an iterable"""
417 """Return the max of an iterable"""
418 if len(args) != 1:
418 if len(args) != 1:
419 # i18n: "max" is a keyword
419 # i18n: "max" is a keyword
420 raise error.ParseError(_("max expects one argument"))
420 raise error.ParseError(_("max expects one argument"))
421
421
422 iterable = evalwrapped(context, mapping, args[0])
422 iterable = evalwrapped(context, mapping, args[0])
423 try:
423 try:
424 return iterable.getmax(context, mapping)
424 return iterable.getmax(context, mapping)
425 except error.ParseError as err:
425 except error.ParseError as err:
426 # i18n: "max" is a keyword
426 # i18n: "max" is a keyword
427 hint = _("max first argument should be an iterable")
427 hint = _("max first argument should be an iterable")
428 raise error.ParseError(bytes(err), hint=hint)
428 raise error.ParseError(bytes(err), hint=hint)
429
429
430 @templatefunc('min(iterable)')
430 @templatefunc('min(iterable)')
431 def min_(context, mapping, args, **kwargs):
431 def min_(context, mapping, args, **kwargs):
432 """Return the min of an iterable"""
432 """Return the min of an iterable"""
433 if len(args) != 1:
433 if len(args) != 1:
434 # i18n: "min" is a keyword
434 # i18n: "min" is a keyword
435 raise error.ParseError(_("min expects one argument"))
435 raise error.ParseError(_("min expects one argument"))
436
436
437 iterable = evalwrapped(context, mapping, args[0])
437 iterable = evalwrapped(context, mapping, args[0])
438 try:
438 try:
439 return iterable.getmin(context, mapping)
439 return iterable.getmin(context, mapping)
440 except error.ParseError as err:
440 except error.ParseError as err:
441 # i18n: "min" is a keyword
441 # i18n: "min" is a keyword
442 hint = _("min first argument should be an iterable")
442 hint = _("min first argument should be an iterable")
443 raise error.ParseError(bytes(err), hint=hint)
443 raise error.ParseError(bytes(err), hint=hint)
444
444
445 @templatefunc('mod(a, b)')
445 @templatefunc('mod(a, b)')
446 def mod(context, mapping, args):
446 def mod(context, mapping, args):
447 """Calculate a mod b such that a / b + a mod b == a"""
447 """Calculate a mod b such that a / b + a mod b == a"""
448 if not len(args) == 2:
448 if not len(args) == 2:
449 # i18n: "mod" is a keyword
449 # i18n: "mod" is a keyword
450 raise error.ParseError(_("mod expects two arguments"))
450 raise error.ParseError(_("mod expects two arguments"))
451
451
452 func = lambda a, b: a % b
452 func = lambda a, b: a % b
453 return templateutil.runarithmetic(context, mapping,
453 return templateutil.runarithmetic(context, mapping,
454 (func, args[0], args[1]))
454 (func, args[0], args[1]))
455
455
456 @templatefunc('obsfateoperations(markers)')
456 @templatefunc('obsfateoperations(markers)')
457 def obsfateoperations(context, mapping, args):
457 def obsfateoperations(context, mapping, args):
458 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
458 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
459 if len(args) != 1:
459 if len(args) != 1:
460 # i18n: "obsfateoperations" is a keyword
460 # i18n: "obsfateoperations" is a keyword
461 raise error.ParseError(_("obsfateoperations expects one argument"))
461 raise error.ParseError(_("obsfateoperations expects one argument"))
462
462
463 markers = evalfuncarg(context, mapping, args[0])
463 markers = evalfuncarg(context, mapping, args[0])
464
464
465 try:
465 try:
466 data = obsutil.markersoperations(markers)
466 data = obsutil.markersoperations(markers)
467 return templateutil.hybridlist(data, name='operation')
467 return templateutil.hybridlist(data, name='operation')
468 except (TypeError, KeyError):
468 except (TypeError, KeyError):
469 # i18n: "obsfateoperations" is a keyword
469 # i18n: "obsfateoperations" is a keyword
470 errmsg = _("obsfateoperations first argument should be an iterable")
470 errmsg = _("obsfateoperations first argument should be an iterable")
471 raise error.ParseError(errmsg)
471 raise error.ParseError(errmsg)
472
472
473 @templatefunc('obsfatedate(markers)')
473 @templatefunc('obsfatedate(markers)')
474 def obsfatedate(context, mapping, args):
474 def obsfatedate(context, mapping, args):
475 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
475 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
476 if len(args) != 1:
476 if len(args) != 1:
477 # i18n: "obsfatedate" is a keyword
477 # i18n: "obsfatedate" is a keyword
478 raise error.ParseError(_("obsfatedate expects one argument"))
478 raise error.ParseError(_("obsfatedate expects one argument"))
479
479
480 markers = evalfuncarg(context, mapping, args[0])
480 markers = evalfuncarg(context, mapping, args[0])
481
481
482 try:
482 try:
483 # TODO: maybe this has to be a wrapped list of date wrappers?
483 # TODO: maybe this has to be a wrapped list of date wrappers?
484 data = obsutil.markersdates(markers)
484 data = obsutil.markersdates(markers)
485 return templateutil.hybridlist(data, name='date', fmt='%d %d')
485 return templateutil.hybridlist(data, name='date', fmt='%d %d')
486 except (TypeError, KeyError):
486 except (TypeError, KeyError):
487 # i18n: "obsfatedate" is a keyword
487 # i18n: "obsfatedate" is a keyword
488 errmsg = _("obsfatedate first argument should be an iterable")
488 errmsg = _("obsfatedate first argument should be an iterable")
489 raise error.ParseError(errmsg)
489 raise error.ParseError(errmsg)
490
490
491 @templatefunc('obsfateusers(markers)')
491 @templatefunc('obsfateusers(markers)')
492 def obsfateusers(context, mapping, args):
492 def obsfateusers(context, mapping, args):
493 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
493 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
494 if len(args) != 1:
494 if len(args) != 1:
495 # i18n: "obsfateusers" is a keyword
495 # i18n: "obsfateusers" is a keyword
496 raise error.ParseError(_("obsfateusers expects one argument"))
496 raise error.ParseError(_("obsfateusers expects one argument"))
497
497
498 markers = evalfuncarg(context, mapping, args[0])
498 markers = evalfuncarg(context, mapping, args[0])
499
499
500 try:
500 try:
501 data = obsutil.markersusers(markers)
501 data = obsutil.markersusers(markers)
502 return templateutil.hybridlist(data, name='user')
502 return templateutil.hybridlist(data, name='user')
503 except (TypeError, KeyError, ValueError):
503 except (TypeError, KeyError, ValueError):
504 # i18n: "obsfateusers" is a keyword
504 # i18n: "obsfateusers" is a keyword
505 msg = _("obsfateusers first argument should be an iterable of "
505 msg = _("obsfateusers first argument should be an iterable of "
506 "obsmakers")
506 "obsmakers")
507 raise error.ParseError(msg)
507 raise error.ParseError(msg)
508
508
509 @templatefunc('obsfateverb(successors, markers)')
509 @templatefunc('obsfateverb(successors, markers)')
510 def obsfateverb(context, mapping, args):
510 def obsfateverb(context, mapping, args):
511 """Compute obsfate related information based on successors (EXPERIMENTAL)"""
511 """Compute obsfate related information based on successors (EXPERIMENTAL)"""
512 if len(args) != 2:
512 if len(args) != 2:
513 # i18n: "obsfateverb" is a keyword
513 # i18n: "obsfateverb" is a keyword
514 raise error.ParseError(_("obsfateverb expects two arguments"))
514 raise error.ParseError(_("obsfateverb expects two arguments"))
515
515
516 successors = evalfuncarg(context, mapping, args[0])
516 successors = evalfuncarg(context, mapping, args[0])
517 markers = evalfuncarg(context, mapping, args[1])
517 markers = evalfuncarg(context, mapping, args[1])
518
518
519 try:
519 try:
520 return obsutil.obsfateverb(successors, markers)
520 return obsutil.obsfateverb(successors, markers)
521 except TypeError:
521 except TypeError:
522 # i18n: "obsfateverb" is a keyword
522 # i18n: "obsfateverb" is a keyword
523 errmsg = _("obsfateverb first argument should be countable")
523 errmsg = _("obsfateverb first argument should be countable")
524 raise error.ParseError(errmsg)
524 raise error.ParseError(errmsg)
525
525
526 @templatefunc('relpath(path)', requires={'repo'})
526 @templatefunc('relpath(path)', requires={'repo'})
527 def relpath(context, mapping, args):
527 def relpath(context, mapping, args):
528 """Convert a repository-absolute path into a filesystem path relative to
528 """Convert a repository-absolute path into a filesystem path relative to
529 the current working directory."""
529 the current working directory."""
530 if len(args) != 1:
530 if len(args) != 1:
531 # i18n: "relpath" is a keyword
531 # i18n: "relpath" is a keyword
532 raise error.ParseError(_("relpath expects one argument"))
532 raise error.ParseError(_("relpath expects one argument"))
533
533
534 repo = context.resource(mapping, 'repo')
534 repo = context.resource(mapping, 'repo')
535 path = evalstring(context, mapping, args[0])
535 path = evalstring(context, mapping, args[0])
536 return repo.pathto(path)
536 return repo.pathto(path)
537
537
538 @templatefunc('revset(query[, formatargs...])', requires={'repo', 'cache'})
538 @templatefunc('revset(query[, formatargs...])', requires={'repo', 'cache'})
539 def revset(context, mapping, args):
539 def revset(context, mapping, args):
540 """Execute a revision set query. See
540 """Execute a revision set query. See
541 :hg:`help revset`."""
541 :hg:`help revset`."""
542 if not len(args) > 0:
542 if not len(args) > 0:
543 # i18n: "revset" is a keyword
543 # i18n: "revset" is a keyword
544 raise error.ParseError(_("revset expects one or more arguments"))
544 raise error.ParseError(_("revset expects one or more arguments"))
545
545
546 raw = evalstring(context, mapping, args[0])
546 raw = evalstring(context, mapping, args[0])
547 repo = context.resource(mapping, 'repo')
547 repo = context.resource(mapping, 'repo')
548
548
549 def query(expr):
549 def query(expr):
550 m = revsetmod.match(repo.ui, expr, lookup=revsetmod.lookupfn(repo))
550 m = revsetmod.match(repo.ui, expr, lookup=revsetmod.lookupfn(repo))
551 return m(repo)
551 return m(repo)
552
552
553 if len(args) > 1:
553 if len(args) > 1:
554 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
554 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
555 revs = query(revsetlang.formatspec(raw, *formatargs))
555 revs = query(revsetlang.formatspec(raw, *formatargs))
556 revs = list(revs)
556 revs = list(revs)
557 else:
557 else:
558 cache = context.resource(mapping, 'cache')
558 cache = context.resource(mapping, 'cache')
559 revsetcache = cache.setdefault("revsetcache", {})
559 revsetcache = cache.setdefault("revsetcache", {})
560 if raw in revsetcache:
560 if raw in revsetcache:
561 revs = revsetcache[raw]
561 revs = revsetcache[raw]
562 else:
562 else:
563 revs = query(raw)
563 revs = query(raw)
564 revs = list(revs)
564 revs = list(revs)
565 revsetcache[raw] = revs
565 revsetcache[raw] = revs
566 return templatekw.showrevslist(context, mapping, "revision", revs)
566 return templatekw.showrevslist(context, mapping, "revision", revs)
567
567
568 @templatefunc('rstdoc(text, style)')
568 @templatefunc('rstdoc(text, style)')
569 def rstdoc(context, mapping, args):
569 def rstdoc(context, mapping, args):
570 """Format reStructuredText."""
570 """Format reStructuredText."""
571 if len(args) != 2:
571 if len(args) != 2:
572 # i18n: "rstdoc" is a keyword
572 # i18n: "rstdoc" is a keyword
573 raise error.ParseError(_("rstdoc expects two arguments"))
573 raise error.ParseError(_("rstdoc expects two arguments"))
574
574
575 text = evalstring(context, mapping, args[0])
575 text = evalstring(context, mapping, args[0])
576 style = evalstring(context, mapping, args[1])
576 style = evalstring(context, mapping, args[1])
577
577
578 return minirst.format(text, style=style, keep=['verbose'])
578 return minirst.format(text, style=style, keep=['verbose'])
579
579
580 @templatefunc('separate(sep, args...)', argspec='sep *args')
580 @templatefunc('separate(sep, args...)', argspec='sep *args')
581 def separate(context, mapping, args):
581 def separate(context, mapping, args):
582 """Add a separator between non-empty arguments."""
582 """Add a separator between non-empty arguments."""
583 if 'sep' not in args:
583 if 'sep' not in args:
584 # i18n: "separate" is a keyword
584 # i18n: "separate" is a keyword
585 raise error.ParseError(_("separate expects at least one argument"))
585 raise error.ParseError(_("separate expects at least one argument"))
586
586
587 sep = evalstring(context, mapping, args['sep'])
587 sep = evalstring(context, mapping, args['sep'])
588 first = True
588 first = True
589 for arg in args['args']:
589 for arg in args['args']:
590 argstr = evalstring(context, mapping, arg)
590 argstr = evalstring(context, mapping, arg)
591 if not argstr:
591 if not argstr:
592 continue
592 continue
593 if first:
593 if first:
594 first = False
594 first = False
595 else:
595 else:
596 yield sep
596 yield sep
597 yield argstr
597 yield argstr
598
598
599 @templatefunc('shortest(node, minlength=4)', requires={'repo', 'cache'})
599 @templatefunc('shortest(node, minlength=4)', requires={'repo', 'cache'})
600 def shortest(context, mapping, args):
600 def shortest(context, mapping, args):
601 """Obtain the shortest representation of
601 """Obtain the shortest representation of
602 a node."""
602 a node."""
603 if not (1 <= len(args) <= 2):
603 if not (1 <= len(args) <= 2):
604 # i18n: "shortest" is a keyword
604 # i18n: "shortest" is a keyword
605 raise error.ParseError(_("shortest() expects one or two arguments"))
605 raise error.ParseError(_("shortest() expects one or two arguments"))
606
606
607 hexnode = evalstring(context, mapping, args[0])
607 hexnode = evalstring(context, mapping, args[0])
608
608
609 minlength = 4
609 minlength = 4
610 if len(args) > 1:
610 if len(args) > 1:
611 minlength = evalinteger(context, mapping, args[1],
611 minlength = evalinteger(context, mapping, args[1],
612 # i18n: "shortest" is a keyword
612 # i18n: "shortest" is a keyword
613 _("shortest() expects an integer minlength"))
613 _("shortest() expects an integer minlength"))
614
614
615 repo = context.resource(mapping, 'repo')
615 repo = context.resource(mapping, 'repo')
616 if len(hexnode) > 40:
616 if len(hexnode) > 40:
617 return hexnode
617 return hexnode
618 elif len(hexnode) == 40:
618 elif len(hexnode) == 40:
619 try:
619 try:
620 node = bin(hexnode)
620 node = bin(hexnode)
621 except TypeError:
621 except TypeError:
622 return hexnode
622 return hexnode
623 else:
623 else:
624 try:
624 try:
625 node = scmutil.resolvehexnodeidprefix(repo, hexnode)
625 node = scmutil.resolvehexnodeidprefix(repo, hexnode)
626 except error.WdirUnsupported:
626 except error.WdirUnsupported:
627 node = wdirid
627 node = wdirid
628 except error.LookupError:
628 except error.LookupError:
629 return hexnode
629 return hexnode
630 if not node:
630 if not node:
631 return hexnode
631 return hexnode
632 cache = context.resource(mapping, 'cache')
632 cache = context.resource(mapping, 'cache')
633 try:
633 try:
634 return scmutil.shortesthexnodeidprefix(repo, node, minlength, cache)
634 return scmutil.shortesthexnodeidprefix(repo, node, minlength, cache)
635 except error.RepoLookupError:
635 except error.RepoLookupError:
636 return hexnode
636 return hexnode
637
637
638 @templatefunc('strip(text[, chars])')
638 @templatefunc('strip(text[, chars])')
639 def strip(context, mapping, args):
639 def strip(context, mapping, args):
640 """Strip characters from a string. By default,
640 """Strip characters from a string. By default,
641 strips all leading and trailing whitespace."""
641 strips all leading and trailing whitespace."""
642 if not (1 <= len(args) <= 2):
642 if not (1 <= len(args) <= 2):
643 # i18n: "strip" is a keyword
643 # i18n: "strip" is a keyword
644 raise error.ParseError(_("strip expects one or two arguments"))
644 raise error.ParseError(_("strip expects one or two arguments"))
645
645
646 text = evalstring(context, mapping, args[0])
646 text = evalstring(context, mapping, args[0])
647 if len(args) == 2:
647 if len(args) == 2:
648 chars = evalstring(context, mapping, args[1])
648 chars = evalstring(context, mapping, args[1])
649 return text.strip(chars)
649 return text.strip(chars)
650 return text.strip()
650 return text.strip()
651
651
652 @templatefunc('sub(pattern, replacement, expression)')
652 @templatefunc('sub(pattern, replacement, expression)')
653 def sub(context, mapping, args):
653 def sub(context, mapping, args):
654 """Perform text substitution
654 """Perform text substitution
655 using regular expressions."""
655 using regular expressions."""
656 if len(args) != 3:
656 if len(args) != 3:
657 # i18n: "sub" is a keyword
657 # i18n: "sub" is a keyword
658 raise error.ParseError(_("sub expects three arguments"))
658 raise error.ParseError(_("sub expects three arguments"))
659
659
660 pat = evalstring(context, mapping, args[0])
660 pat = evalstring(context, mapping, args[0])
661 rpl = evalstring(context, mapping, args[1])
661 rpl = evalstring(context, mapping, args[1])
662 src = evalstring(context, mapping, args[2])
662 src = evalstring(context, mapping, args[2])
663 try:
663 try:
664 patre = re.compile(pat)
664 patre = re.compile(pat)
665 except re.error:
665 except re.error:
666 # i18n: "sub" is a keyword
666 # i18n: "sub" is a keyword
667 raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
667 raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
668 try:
668 try:
669 yield patre.sub(rpl, src)
669 yield patre.sub(rpl, src)
670 except re.error:
670 except re.error:
671 # i18n: "sub" is a keyword
671 # i18n: "sub" is a keyword
672 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
672 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
673
673
674 @templatefunc('startswith(pattern, text)')
674 @templatefunc('startswith(pattern, text)')
675 def startswith(context, mapping, args):
675 def startswith(context, mapping, args):
676 """Returns the value from the "text" argument
676 """Returns the value from the "text" argument
677 if it begins with the content from the "pattern" argument."""
677 if it begins with the content from the "pattern" argument."""
678 if len(args) != 2:
678 if len(args) != 2:
679 # i18n: "startswith" is a keyword
679 # i18n: "startswith" is a keyword
680 raise error.ParseError(_("startswith expects two arguments"))
680 raise error.ParseError(_("startswith expects two arguments"))
681
681
682 patn = evalstring(context, mapping, args[0])
682 patn = evalstring(context, mapping, args[0])
683 text = evalstring(context, mapping, args[1])
683 text = evalstring(context, mapping, args[1])
684 if text.startswith(patn):
684 if text.startswith(patn):
685 return text
685 return text
686 return ''
686 return ''
687
687
688 @templatefunc('word(number, text[, separator])')
688 @templatefunc('word(number, text[, separator])')
689 def word(context, mapping, args):
689 def word(context, mapping, args):
690 """Return the nth word from a string."""
690 """Return the nth word from a string."""
691 if not (2 <= len(args) <= 3):
691 if not (2 <= len(args) <= 3):
692 # i18n: "word" is a keyword
692 # i18n: "word" is a keyword
693 raise error.ParseError(_("word expects two or three arguments, got %d")
693 raise error.ParseError(_("word expects two or three arguments, got %d")
694 % len(args))
694 % len(args))
695
695
696 num = evalinteger(context, mapping, args[0],
696 num = evalinteger(context, mapping, args[0],
697 # i18n: "word" is a keyword
697 # i18n: "word" is a keyword
698 _("word expects an integer index"))
698 _("word expects an integer index"))
699 text = evalstring(context, mapping, args[1])
699 text = evalstring(context, mapping, args[1])
700 if len(args) == 3:
700 if len(args) == 3:
701 splitter = evalstring(context, mapping, args[2])
701 splitter = evalstring(context, mapping, args[2])
702 else:
702 else:
703 splitter = None
703 splitter = None
704
704
705 tokens = text.split(splitter)
705 tokens = text.split(splitter)
706 if num >= len(tokens) or num < -len(tokens):
706 if num >= len(tokens) or num < -len(tokens):
707 return ''
707 return ''
708 else:
708 else:
709 return tokens[num]
709 return tokens[num]
710
710
711 def loadfunction(ui, extname, registrarobj):
711 def loadfunction(ui, extname, registrarobj):
712 """Load template function from specified registrarobj
712 """Load template function from specified registrarobj
713 """
713 """
714 for name, func in registrarobj._table.iteritems():
714 for name, func in registrarobj._table.iteritems():
715 funcs[name] = func
715 funcs[name] = func
716
716
717 # tell hggettext to extract docstrings from these functions:
717 # tell hggettext to extract docstrings from these functions:
718 i18nfunctions = funcs.values()
718 i18nfunctions = funcs.values()
@@ -1,820 +1,820
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 compatlist(context, mapping, name, files, element='file')
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
327 copies = util.sortdict(copies)
327 copies = util.sortdict(copies)
328 return compatdict(context, mapping, 'file_copy', copies,
328 return compatdict(context, mapping, 'file_copy', copies,
329 key='name', value='source', fmt='%s (%s)',
329 key='name', value='source', fmt='%s (%s)',
330 plural='file_copies')
330 plural='file_copies')
331
331
332 # showfilecopiesswitch() displays file copies only if copy records are
332 # showfilecopiesswitch() displays file copies only if copy records are
333 # provided before calling the templater, usually with a --copies
333 # provided before calling the templater, usually with a --copies
334 # command line switch.
334 # command line switch.
335 @templatekeyword('file_copies_switch', requires={'revcache'})
335 @templatekeyword('file_copies_switch', requires={'revcache'})
336 def showfilecopiesswitch(context, mapping):
336 def showfilecopiesswitch(context, mapping):
337 """List of strings. Like "file_copies" but displayed
337 """List of strings. Like "file_copies" but displayed
338 only if the --copied switch is set.
338 only if the --copied switch is set.
339 """
339 """
340 copies = context.resource(mapping, 'revcache').get('copies') or []
340 copies = context.resource(mapping, 'revcache').get('copies') or []
341 copies = util.sortdict(copies)
341 copies = util.sortdict(copies)
342 return compatdict(context, mapping, 'file_copy', copies,
342 return compatdict(context, mapping, 'file_copy', copies,
343 key='name', value='source', fmt='%s (%s)',
343 key='name', value='source', fmt='%s (%s)',
344 plural='file_copies')
344 plural='file_copies')
345
345
346 @templatekeyword('file_dels', requires={'ctx', 'revcache'})
346 @templatekeyword('file_dels', requires={'ctx', 'revcache'})
347 def showfiledels(context, mapping):
347 def showfiledels(context, mapping):
348 """List of strings. Files removed by this changeset."""
348 """List of strings. Files removed by this changeset."""
349 return _showfilesbystat(context, mapping, 'file_del', 2)
349 return _showfilesbystat(context, mapping, 'file_del', 2)
350
350
351 @templatekeyword('file_mods', requires={'ctx', 'revcache'})
351 @templatekeyword('file_mods', requires={'ctx', 'revcache'})
352 def showfilemods(context, mapping):
352 def showfilemods(context, mapping):
353 """List of strings. Files modified by this changeset."""
353 """List of strings. Files modified by this changeset."""
354 return _showfilesbystat(context, mapping, 'file_mod', 0)
354 return _showfilesbystat(context, mapping, 'file_mod', 0)
355
355
356 @templatekeyword('files', requires={'ctx'})
356 @templatekeyword('files', requires={'ctx'})
357 def showfiles(context, mapping):
357 def showfiles(context, mapping):
358 """List of strings. All files modified, added, or removed by this
358 """List of strings. All files modified, added, or removed by this
359 changeset.
359 changeset.
360 """
360 """
361 ctx = context.resource(mapping, 'ctx')
361 ctx = context.resource(mapping, 'ctx')
362 return compatlist(context, mapping, 'file', ctx.files())
362 return templateutil.compatfileslist(context, mapping, 'file', ctx.files())
363
363
364 @templatekeyword('graphnode', requires={'repo', 'ctx'})
364 @templatekeyword('graphnode', requires={'repo', 'ctx'})
365 def showgraphnode(context, mapping):
365 def showgraphnode(context, mapping):
366 """String. The character representing the changeset node in an ASCII
366 """String. The character representing the changeset node in an ASCII
367 revision graph."""
367 revision graph."""
368 repo = context.resource(mapping, 'repo')
368 repo = context.resource(mapping, 'repo')
369 ctx = context.resource(mapping, 'ctx')
369 ctx = context.resource(mapping, 'ctx')
370 return getgraphnode(repo, ctx)
370 return getgraphnode(repo, ctx)
371
371
372 def getgraphnode(repo, ctx):
372 def getgraphnode(repo, ctx):
373 return getgraphnodecurrent(repo, ctx) or getgraphnodesymbol(ctx)
373 return getgraphnodecurrent(repo, ctx) or getgraphnodesymbol(ctx)
374
374
375 def getgraphnodecurrent(repo, ctx):
375 def getgraphnodecurrent(repo, ctx):
376 wpnodes = repo.dirstate.parents()
376 wpnodes = repo.dirstate.parents()
377 if wpnodes[1] == nullid:
377 if wpnodes[1] == nullid:
378 wpnodes = wpnodes[:1]
378 wpnodes = wpnodes[:1]
379 if ctx.node() in wpnodes:
379 if ctx.node() in wpnodes:
380 return '@'
380 return '@'
381 else:
381 else:
382 return ''
382 return ''
383
383
384 def getgraphnodesymbol(ctx):
384 def getgraphnodesymbol(ctx):
385 if ctx.obsolete():
385 if ctx.obsolete():
386 return 'x'
386 return 'x'
387 elif ctx.isunstable():
387 elif ctx.isunstable():
388 return '*'
388 return '*'
389 elif ctx.closesbranch():
389 elif ctx.closesbranch():
390 return '_'
390 return '_'
391 else:
391 else:
392 return 'o'
392 return 'o'
393
393
394 @templatekeyword('graphwidth', requires=())
394 @templatekeyword('graphwidth', requires=())
395 def showgraphwidth(context, mapping):
395 def showgraphwidth(context, mapping):
396 """Integer. The width of the graph drawn by 'log --graph' or zero."""
396 """Integer. The width of the graph drawn by 'log --graph' or zero."""
397 # just hosts documentation; should be overridden by template mapping
397 # just hosts documentation; should be overridden by template mapping
398 return 0
398 return 0
399
399
400 @templatekeyword('index', requires=())
400 @templatekeyword('index', requires=())
401 def showindex(context, mapping):
401 def showindex(context, mapping):
402 """Integer. The current iteration of the loop. (0 indexed)"""
402 """Integer. The current iteration of the loop. (0 indexed)"""
403 # just hosts documentation; should be overridden by template mapping
403 # just hosts documentation; should be overridden by template mapping
404 raise error.Abort(_("can't use index in this context"))
404 raise error.Abort(_("can't use index in this context"))
405
405
406 @templatekeyword('latesttag', requires={'repo', 'ctx', 'cache'})
406 @templatekeyword('latesttag', requires={'repo', 'ctx', 'cache'})
407 def showlatesttag(context, mapping):
407 def showlatesttag(context, mapping):
408 """List of strings. The global tags on the most recent globally
408 """List of strings. The global tags on the most recent globally
409 tagged ancestor of this changeset. If no such tags exist, the list
409 tagged ancestor of this changeset. If no such tags exist, the list
410 consists of the single string "null".
410 consists of the single string "null".
411 """
411 """
412 return showlatesttags(context, mapping, None)
412 return showlatesttags(context, mapping, None)
413
413
414 def showlatesttags(context, mapping, pattern):
414 def showlatesttags(context, mapping, pattern):
415 """helper method for the latesttag keyword and function"""
415 """helper method for the latesttag keyword and function"""
416 latesttags = getlatesttags(context, mapping, pattern)
416 latesttags = getlatesttags(context, mapping, pattern)
417
417
418 # latesttag[0] is an implementation detail for sorting csets on different
418 # 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,
419 # 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.
420 # not the date the tag was created. Therefore it isn't made visible here.
421 makemap = lambda v: {
421 makemap = lambda v: {
422 'changes': _showchangessincetag,
422 'changes': _showchangessincetag,
423 'distance': latesttags[1],
423 'distance': latesttags[1],
424 'latesttag': v, # BC with {latesttag % '{latesttag}'}
424 'latesttag': v, # BC with {latesttag % '{latesttag}'}
425 'tag': v
425 'tag': v
426 }
426 }
427
427
428 tags = latesttags[2]
428 tags = latesttags[2]
429 f = _showcompatlist(context, mapping, 'latesttag', tags, separator=':')
429 f = _showcompatlist(context, mapping, 'latesttag', tags, separator=':')
430 return _hybrid(f, tags, makemap, pycompat.identity)
430 return _hybrid(f, tags, makemap, pycompat.identity)
431
431
432 @templatekeyword('latesttagdistance', requires={'repo', 'ctx', 'cache'})
432 @templatekeyword('latesttagdistance', requires={'repo', 'ctx', 'cache'})
433 def showlatesttagdistance(context, mapping):
433 def showlatesttagdistance(context, mapping):
434 """Integer. Longest path to the latest tag."""
434 """Integer. Longest path to the latest tag."""
435 return getlatesttags(context, mapping)[1]
435 return getlatesttags(context, mapping)[1]
436
436
437 @templatekeyword('changessincelatesttag', requires={'repo', 'ctx', 'cache'})
437 @templatekeyword('changessincelatesttag', requires={'repo', 'ctx', 'cache'})
438 def showchangessincelatesttag(context, mapping):
438 def showchangessincelatesttag(context, mapping):
439 """Integer. All ancestors not in the latest tag."""
439 """Integer. All ancestors not in the latest tag."""
440 tag = getlatesttags(context, mapping)[2][0]
440 tag = getlatesttags(context, mapping)[2][0]
441 mapping = context.overlaymap(mapping, {'tag': tag})
441 mapping = context.overlaymap(mapping, {'tag': tag})
442 return _showchangessincetag(context, mapping)
442 return _showchangessincetag(context, mapping)
443
443
444 def _showchangessincetag(context, mapping):
444 def _showchangessincetag(context, mapping):
445 repo = context.resource(mapping, 'repo')
445 repo = context.resource(mapping, 'repo')
446 ctx = context.resource(mapping, 'ctx')
446 ctx = context.resource(mapping, 'ctx')
447 offset = 0
447 offset = 0
448 revs = [ctx.rev()]
448 revs = [ctx.rev()]
449 tag = context.symbol(mapping, 'tag')
449 tag = context.symbol(mapping, 'tag')
450
450
451 # The only() revset doesn't currently support wdir()
451 # The only() revset doesn't currently support wdir()
452 if ctx.rev() is None:
452 if ctx.rev() is None:
453 offset = 1
453 offset = 1
454 revs = [p.rev() for p in ctx.parents()]
454 revs = [p.rev() for p in ctx.parents()]
455
455
456 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
456 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
457
457
458 # teach templater latesttags.changes is switched to (context, mapping) API
458 # teach templater latesttags.changes is switched to (context, mapping) API
459 _showchangessincetag._requires = {'repo', 'ctx'}
459 _showchangessincetag._requires = {'repo', 'ctx'}
460
460
461 @templatekeyword('manifest', requires={'repo', 'ctx'})
461 @templatekeyword('manifest', requires={'repo', 'ctx'})
462 def showmanifest(context, mapping):
462 def showmanifest(context, mapping):
463 repo = context.resource(mapping, 'repo')
463 repo = context.resource(mapping, 'repo')
464 ctx = context.resource(mapping, 'ctx')
464 ctx = context.resource(mapping, 'ctx')
465 mnode = ctx.manifestnode()
465 mnode = ctx.manifestnode()
466 if mnode is None:
466 if mnode is None:
467 # just avoid crash, we might want to use the 'ff...' hash in future
467 # just avoid crash, we might want to use the 'ff...' hash in future
468 return
468 return
469 mrev = repo.manifestlog.rev(mnode)
469 mrev = repo.manifestlog.rev(mnode)
470 mhex = hex(mnode)
470 mhex = hex(mnode)
471 mapping = context.overlaymap(mapping, {'rev': mrev, 'node': mhex})
471 mapping = context.overlaymap(mapping, {'rev': mrev, 'node': mhex})
472 f = context.process('manifest', mapping)
472 f = context.process('manifest', mapping)
473 # TODO: perhaps 'ctx' should be dropped from mapping because manifest
473 # TODO: perhaps 'ctx' should be dropped from mapping because manifest
474 # rev and node are completely different from changeset's.
474 # rev and node are completely different from changeset's.
475 return templateutil.hybriditem(f, None, f,
475 return templateutil.hybriditem(f, None, f,
476 lambda x: {'rev': mrev, 'node': mhex})
476 lambda x: {'rev': mrev, 'node': mhex})
477
477
478 @templatekeyword('obsfate', requires={'ui', 'repo', 'ctx'})
478 @templatekeyword('obsfate', requires={'ui', 'repo', 'ctx'})
479 def showobsfate(context, mapping):
479 def showobsfate(context, mapping):
480 # this function returns a list containing pre-formatted obsfate strings.
480 # this function returns a list containing pre-formatted obsfate strings.
481 #
481 #
482 # This function will be replaced by templates fragments when we will have
482 # This function will be replaced by templates fragments when we will have
483 # the verbosity templatekw available.
483 # the verbosity templatekw available.
484 succsandmarkers = showsuccsandmarkers(context, mapping)
484 succsandmarkers = showsuccsandmarkers(context, mapping)
485
485
486 ui = context.resource(mapping, 'ui')
486 ui = context.resource(mapping, 'ui')
487 repo = context.resource(mapping, 'repo')
487 repo = context.resource(mapping, 'repo')
488 values = []
488 values = []
489
489
490 for x in succsandmarkers.tovalue(context, mapping):
490 for x in succsandmarkers.tovalue(context, mapping):
491 v = obsutil.obsfateprinter(ui, repo, x['successors'], x['markers'],
491 v = obsutil.obsfateprinter(ui, repo, x['successors'], x['markers'],
492 scmutil.formatchangeid)
492 scmutil.formatchangeid)
493 values.append(v)
493 values.append(v)
494
494
495 return compatlist(context, mapping, "fate", values)
495 return compatlist(context, mapping, "fate", values)
496
496
497 def shownames(context, mapping, namespace):
497 def shownames(context, mapping, namespace):
498 """helper method to generate a template keyword for a namespace"""
498 """helper method to generate a template keyword for a namespace"""
499 repo = context.resource(mapping, 'repo')
499 repo = context.resource(mapping, 'repo')
500 ctx = context.resource(mapping, 'ctx')
500 ctx = context.resource(mapping, 'ctx')
501 ns = repo.names[namespace]
501 ns = repo.names[namespace]
502 names = ns.names(repo, ctx.node())
502 names = ns.names(repo, ctx.node())
503 return compatlist(context, mapping, ns.templatename, names,
503 return compatlist(context, mapping, ns.templatename, names,
504 plural=namespace)
504 plural=namespace)
505
505
506 @templatekeyword('namespaces', requires={'repo', 'ctx'})
506 @templatekeyword('namespaces', requires={'repo', 'ctx'})
507 def shownamespaces(context, mapping):
507 def shownamespaces(context, mapping):
508 """Dict of lists. Names attached to this changeset per
508 """Dict of lists. Names attached to this changeset per
509 namespace."""
509 namespace."""
510 repo = context.resource(mapping, 'repo')
510 repo = context.resource(mapping, 'repo')
511 ctx = context.resource(mapping, 'ctx')
511 ctx = context.resource(mapping, 'ctx')
512
512
513 namespaces = util.sortdict()
513 namespaces = util.sortdict()
514 def makensmapfn(ns):
514 def makensmapfn(ns):
515 # 'name' for iterating over namespaces, templatename for local reference
515 # 'name' for iterating over namespaces, templatename for local reference
516 return lambda v: {'name': v, ns.templatename: v}
516 return lambda v: {'name': v, ns.templatename: v}
517
517
518 for k, ns in repo.names.iteritems():
518 for k, ns in repo.names.iteritems():
519 names = ns.names(repo, ctx.node())
519 names = ns.names(repo, ctx.node())
520 f = _showcompatlist(context, mapping, 'name', names)
520 f = _showcompatlist(context, mapping, 'name', names)
521 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
521 namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
522
522
523 f = _showcompatlist(context, mapping, 'namespace', list(namespaces))
523 f = _showcompatlist(context, mapping, 'namespace', list(namespaces))
524
524
525 def makemap(ns):
525 def makemap(ns):
526 return {
526 return {
527 'namespace': ns,
527 'namespace': ns,
528 'names': namespaces[ns],
528 'names': namespaces[ns],
529 'builtin': repo.names[ns].builtin,
529 'builtin': repo.names[ns].builtin,
530 'colorname': repo.names[ns].colorname,
530 'colorname': repo.names[ns].colorname,
531 }
531 }
532
532
533 return _hybrid(f, namespaces, makemap, pycompat.identity)
533 return _hybrid(f, namespaces, makemap, pycompat.identity)
534
534
535 @templatekeyword('node', requires={'ctx'})
535 @templatekeyword('node', requires={'ctx'})
536 def shownode(context, mapping):
536 def shownode(context, mapping):
537 """String. The changeset identification hash, as a 40 hexadecimal
537 """String. The changeset identification hash, as a 40 hexadecimal
538 digit string.
538 digit string.
539 """
539 """
540 ctx = context.resource(mapping, 'ctx')
540 ctx = context.resource(mapping, 'ctx')
541 return ctx.hex()
541 return ctx.hex()
542
542
543 @templatekeyword('obsolete', requires={'ctx'})
543 @templatekeyword('obsolete', requires={'ctx'})
544 def showobsolete(context, mapping):
544 def showobsolete(context, mapping):
545 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
545 """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
546 ctx = context.resource(mapping, 'ctx')
546 ctx = context.resource(mapping, 'ctx')
547 if ctx.obsolete():
547 if ctx.obsolete():
548 return 'obsolete'
548 return 'obsolete'
549 return ''
549 return ''
550
550
551 @templatekeyword('peerurls', requires={'repo'})
551 @templatekeyword('peerurls', requires={'repo'})
552 def showpeerurls(context, mapping):
552 def showpeerurls(context, mapping):
553 """A dictionary of repository locations defined in the [paths] section
553 """A dictionary of repository locations defined in the [paths] section
554 of your configuration file."""
554 of your configuration file."""
555 repo = context.resource(mapping, 'repo')
555 repo = context.resource(mapping, 'repo')
556 # see commands.paths() for naming of dictionary keys
556 # see commands.paths() for naming of dictionary keys
557 paths = repo.ui.paths
557 paths = repo.ui.paths
558 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
558 urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
559 def makemap(k):
559 def makemap(k):
560 p = paths[k]
560 p = paths[k]
561 d = {'name': k, 'url': p.rawloc}
561 d = {'name': k, 'url': p.rawloc}
562 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
562 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
563 return d
563 return d
564 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
564 return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
565
565
566 @templatekeyword("predecessors", requires={'repo', 'ctx'})
566 @templatekeyword("predecessors", requires={'repo', 'ctx'})
567 def showpredecessors(context, mapping):
567 def showpredecessors(context, mapping):
568 """Returns the list if the closest visible successors. (EXPERIMENTAL)"""
568 """Returns the list if the closest visible successors. (EXPERIMENTAL)"""
569 repo = context.resource(mapping, 'repo')
569 repo = context.resource(mapping, 'repo')
570 ctx = context.resource(mapping, 'ctx')
570 ctx = context.resource(mapping, 'ctx')
571 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
571 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
572 predecessors = pycompat.maplist(hex, predecessors)
572 predecessors = pycompat.maplist(hex, predecessors)
573
573
574 return _hybrid(None, predecessors,
574 return _hybrid(None, predecessors,
575 lambda x: {'ctx': repo[x]},
575 lambda x: {'ctx': repo[x]},
576 lambda x: scmutil.formatchangeid(repo[x]))
576 lambda x: scmutil.formatchangeid(repo[x]))
577
577
578 @templatekeyword('reporoot', requires={'repo'})
578 @templatekeyword('reporoot', requires={'repo'})
579 def showreporoot(context, mapping):
579 def showreporoot(context, mapping):
580 """String. The root directory of the current repository."""
580 """String. The root directory of the current repository."""
581 repo = context.resource(mapping, 'repo')
581 repo = context.resource(mapping, 'repo')
582 return repo.root
582 return repo.root
583
583
584 @templatekeyword("successorssets", requires={'repo', 'ctx'})
584 @templatekeyword("successorssets", requires={'repo', 'ctx'})
585 def showsuccessorssets(context, mapping):
585 def showsuccessorssets(context, mapping):
586 """Returns a string of sets of successors for a changectx. Format used
586 """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
587 is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
588 while also diverged into ctx3. (EXPERIMENTAL)"""
588 while also diverged into ctx3. (EXPERIMENTAL)"""
589 repo = context.resource(mapping, 'repo')
589 repo = context.resource(mapping, 'repo')
590 ctx = context.resource(mapping, 'ctx')
590 ctx = context.resource(mapping, 'ctx')
591 if not ctx.obsolete():
591 if not ctx.obsolete():
592 return ''
592 return ''
593
593
594 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
594 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
595 ssets = [[hex(n) for n in ss] for ss in ssets]
595 ssets = [[hex(n) for n in ss] for ss in ssets]
596
596
597 data = []
597 data = []
598 for ss in ssets:
598 for ss in ssets:
599 h = _hybrid(None, ss, lambda x: {'ctx': repo[x]},
599 h = _hybrid(None, ss, lambda x: {'ctx': repo[x]},
600 lambda x: scmutil.formatchangeid(repo[x]))
600 lambda x: scmutil.formatchangeid(repo[x]))
601 data.append(h)
601 data.append(h)
602
602
603 # Format the successorssets
603 # Format the successorssets
604 def render(d):
604 def render(d):
605 return templateutil.stringify(context, mapping, d)
605 return templateutil.stringify(context, mapping, d)
606
606
607 def gen(data):
607 def gen(data):
608 yield "; ".join(render(d) for d in data)
608 yield "; ".join(render(d) for d in data)
609
609
610 return _hybrid(gen(data), data, lambda x: {'successorset': x},
610 return _hybrid(gen(data), data, lambda x: {'successorset': x},
611 pycompat.identity)
611 pycompat.identity)
612
612
613 @templatekeyword("succsandmarkers", requires={'repo', 'ctx'})
613 @templatekeyword("succsandmarkers", requires={'repo', 'ctx'})
614 def showsuccsandmarkers(context, mapping):
614 def showsuccsandmarkers(context, mapping):
615 """Returns a list of dict for each final successor of ctx. The dict
615 """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
616 contains successors node id in "successors" keys and the list of
617 obs-markers from ctx to the set of successors in "markers".
617 obs-markers from ctx to the set of successors in "markers".
618 (EXPERIMENTAL)
618 (EXPERIMENTAL)
619 """
619 """
620 repo = context.resource(mapping, 'repo')
620 repo = context.resource(mapping, 'repo')
621 ctx = context.resource(mapping, 'ctx')
621 ctx = context.resource(mapping, 'ctx')
622
622
623 values = obsutil.successorsandmarkers(repo, ctx)
623 values = obsutil.successorsandmarkers(repo, ctx)
624
624
625 if values is None:
625 if values is None:
626 values = []
626 values = []
627
627
628 # Format successors and markers to avoid exposing binary to templates
628 # Format successors and markers to avoid exposing binary to templates
629 data = []
629 data = []
630 for i in values:
630 for i in values:
631 # Format successors
631 # Format successors
632 successors = i['successors']
632 successors = i['successors']
633
633
634 successors = [hex(n) for n in successors]
634 successors = [hex(n) for n in successors]
635 successors = _hybrid(None, successors,
635 successors = _hybrid(None, successors,
636 lambda x: {'ctx': repo[x]},
636 lambda x: {'ctx': repo[x]},
637 lambda x: scmutil.formatchangeid(repo[x]))
637 lambda x: scmutil.formatchangeid(repo[x]))
638
638
639 # Format markers
639 # Format markers
640 finalmarkers = []
640 finalmarkers = []
641 for m in i['markers']:
641 for m in i['markers']:
642 hexprec = hex(m[0])
642 hexprec = hex(m[0])
643 hexsucs = tuple(hex(n) for n in m[1])
643 hexsucs = tuple(hex(n) for n in m[1])
644 hexparents = None
644 hexparents = None
645 if m[5] is not None:
645 if m[5] is not None:
646 hexparents = tuple(hex(n) for n in m[5])
646 hexparents = tuple(hex(n) for n in m[5])
647 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
647 newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
648 finalmarkers.append(newmarker)
648 finalmarkers.append(newmarker)
649
649
650 data.append({'successors': successors, 'markers': finalmarkers})
650 data.append({'successors': successors, 'markers': finalmarkers})
651
651
652 return templateutil.mappinglist(data)
652 return templateutil.mappinglist(data)
653
653
654 @templatekeyword('p1rev', requires={'ctx'})
654 @templatekeyword('p1rev', requires={'ctx'})
655 def showp1rev(context, mapping):
655 def showp1rev(context, mapping):
656 """Integer. The repository-local revision number of the changeset's
656 """Integer. The repository-local revision number of the changeset's
657 first parent, or -1 if the changeset has no parents."""
657 first parent, or -1 if the changeset has no parents."""
658 ctx = context.resource(mapping, 'ctx')
658 ctx = context.resource(mapping, 'ctx')
659 return ctx.p1().rev()
659 return ctx.p1().rev()
660
660
661 @templatekeyword('p2rev', requires={'ctx'})
661 @templatekeyword('p2rev', requires={'ctx'})
662 def showp2rev(context, mapping):
662 def showp2rev(context, mapping):
663 """Integer. The repository-local revision number of the changeset's
663 """Integer. The repository-local revision number of the changeset's
664 second parent, or -1 if the changeset has no second parent."""
664 second parent, or -1 if the changeset has no second parent."""
665 ctx = context.resource(mapping, 'ctx')
665 ctx = context.resource(mapping, 'ctx')
666 return ctx.p2().rev()
666 return ctx.p2().rev()
667
667
668 @templatekeyword('p1node', requires={'ctx'})
668 @templatekeyword('p1node', requires={'ctx'})
669 def showp1node(context, mapping):
669 def showp1node(context, mapping):
670 """String. The identification hash of the changeset's first parent,
670 """String. The identification hash of the changeset's first parent,
671 as a 40 digit hexadecimal string. If the changeset has no parents, all
671 as a 40 digit hexadecimal string. If the changeset has no parents, all
672 digits are 0."""
672 digits are 0."""
673 ctx = context.resource(mapping, 'ctx')
673 ctx = context.resource(mapping, 'ctx')
674 return ctx.p1().hex()
674 return ctx.p1().hex()
675
675
676 @templatekeyword('p2node', requires={'ctx'})
676 @templatekeyword('p2node', requires={'ctx'})
677 def showp2node(context, mapping):
677 def showp2node(context, mapping):
678 """String. The identification hash of the changeset's second
678 """String. The identification hash of the changeset's second
679 parent, as a 40 digit hexadecimal string. If the changeset has no second
679 parent, as a 40 digit hexadecimal string. If the changeset has no second
680 parent, all digits are 0."""
680 parent, all digits are 0."""
681 ctx = context.resource(mapping, 'ctx')
681 ctx = context.resource(mapping, 'ctx')
682 return ctx.p2().hex()
682 return ctx.p2().hex()
683
683
684 @templatekeyword('parents', requires={'repo', 'ctx'})
684 @templatekeyword('parents', requires={'repo', 'ctx'})
685 def showparents(context, mapping):
685 def showparents(context, mapping):
686 """List of strings. The parents of the changeset in "rev:node"
686 """List of strings. The parents of the changeset in "rev:node"
687 format. If the changeset has only one "natural" parent (the predecessor
687 format. If the changeset has only one "natural" parent (the predecessor
688 revision) nothing is shown."""
688 revision) nothing is shown."""
689 repo = context.resource(mapping, 'repo')
689 repo = context.resource(mapping, 'repo')
690 ctx = context.resource(mapping, 'ctx')
690 ctx = context.resource(mapping, 'ctx')
691 pctxs = scmutil.meaningfulparents(repo, ctx)
691 pctxs = scmutil.meaningfulparents(repo, ctx)
692 prevs = [p.rev() for p in pctxs]
692 prevs = [p.rev() for p in pctxs]
693 parents = [[('rev', p.rev()),
693 parents = [[('rev', p.rev()),
694 ('node', p.hex()),
694 ('node', p.hex()),
695 ('phase', p.phasestr())]
695 ('phase', p.phasestr())]
696 for p in pctxs]
696 for p in pctxs]
697 f = _showcompatlist(context, mapping, 'parent', parents)
697 f = _showcompatlist(context, mapping, 'parent', parents)
698 return _hybrid(f, prevs, lambda x: {'ctx': repo[x]},
698 return _hybrid(f, prevs, lambda x: {'ctx': repo[x]},
699 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
699 lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
700
700
701 @templatekeyword('phase', requires={'ctx'})
701 @templatekeyword('phase', requires={'ctx'})
702 def showphase(context, mapping):
702 def showphase(context, mapping):
703 """String. The changeset phase name."""
703 """String. The changeset phase name."""
704 ctx = context.resource(mapping, 'ctx')
704 ctx = context.resource(mapping, 'ctx')
705 return ctx.phasestr()
705 return ctx.phasestr()
706
706
707 @templatekeyword('phaseidx', requires={'ctx'})
707 @templatekeyword('phaseidx', requires={'ctx'})
708 def showphaseidx(context, mapping):
708 def showphaseidx(context, mapping):
709 """Integer. The changeset phase index. (ADVANCED)"""
709 """Integer. The changeset phase index. (ADVANCED)"""
710 ctx = context.resource(mapping, 'ctx')
710 ctx = context.resource(mapping, 'ctx')
711 return ctx.phase()
711 return ctx.phase()
712
712
713 @templatekeyword('rev', requires={'ctx'})
713 @templatekeyword('rev', requires={'ctx'})
714 def showrev(context, mapping):
714 def showrev(context, mapping):
715 """Integer. The repository-local changeset revision number."""
715 """Integer. The repository-local changeset revision number."""
716 ctx = context.resource(mapping, 'ctx')
716 ctx = context.resource(mapping, 'ctx')
717 return scmutil.intrev(ctx)
717 return scmutil.intrev(ctx)
718
718
719 def showrevslist(context, mapping, name, revs):
719 def showrevslist(context, mapping, name, revs):
720 """helper to generate a list of revisions in which a mapped template will
720 """helper to generate a list of revisions in which a mapped template will
721 be evaluated"""
721 be evaluated"""
722 repo = context.resource(mapping, 'repo')
722 repo = context.resource(mapping, 'repo')
723 f = _showcompatlist(context, mapping, name, ['%d' % r for r in revs])
723 f = _showcompatlist(context, mapping, name, ['%d' % r for r in revs])
724 return _hybrid(f, revs,
724 return _hybrid(f, revs,
725 lambda x: {name: x, 'ctx': repo[x]},
725 lambda x: {name: x, 'ctx': repo[x]},
726 pycompat.identity, keytype=int)
726 pycompat.identity, keytype=int)
727
727
728 @templatekeyword('subrepos', requires={'ctx'})
728 @templatekeyword('subrepos', requires={'ctx'})
729 def showsubrepos(context, mapping):
729 def showsubrepos(context, mapping):
730 """List of strings. Updated subrepositories in the changeset."""
730 """List of strings. Updated subrepositories in the changeset."""
731 ctx = context.resource(mapping, 'ctx')
731 ctx = context.resource(mapping, 'ctx')
732 substate = ctx.substate
732 substate = ctx.substate
733 if not substate:
733 if not substate:
734 return compatlist(context, mapping, 'subrepo', [])
734 return compatlist(context, mapping, 'subrepo', [])
735 psubstate = ctx.parents()[0].substate or {}
735 psubstate = ctx.parents()[0].substate or {}
736 subrepos = []
736 subrepos = []
737 for sub in substate:
737 for sub in substate:
738 if sub not in psubstate or substate[sub] != psubstate[sub]:
738 if sub not in psubstate or substate[sub] != psubstate[sub]:
739 subrepos.append(sub) # modified or newly added in ctx
739 subrepos.append(sub) # modified or newly added in ctx
740 for sub in psubstate:
740 for sub in psubstate:
741 if sub not in substate:
741 if sub not in substate:
742 subrepos.append(sub) # removed in ctx
742 subrepos.append(sub) # removed in ctx
743 return compatlist(context, mapping, 'subrepo', sorted(subrepos))
743 return compatlist(context, mapping, 'subrepo', sorted(subrepos))
744
744
745 # don't remove "showtags" definition, even though namespaces will put
745 # don't remove "showtags" definition, even though namespaces will put
746 # a helper function for "tags" keyword into "keywords" map automatically,
746 # a helper function for "tags" keyword into "keywords" map automatically,
747 # because online help text is built without namespaces initialization
747 # because online help text is built without namespaces initialization
748 @templatekeyword('tags', requires={'repo', 'ctx'})
748 @templatekeyword('tags', requires={'repo', 'ctx'})
749 def showtags(context, mapping):
749 def showtags(context, mapping):
750 """List of strings. Any tags associated with the changeset."""
750 """List of strings. Any tags associated with the changeset."""
751 return shownames(context, mapping, 'tags')
751 return shownames(context, mapping, 'tags')
752
752
753 @templatekeyword('termwidth', requires={'ui'})
753 @templatekeyword('termwidth', requires={'ui'})
754 def showtermwidth(context, mapping):
754 def showtermwidth(context, mapping):
755 """Integer. The width of the current terminal."""
755 """Integer. The width of the current terminal."""
756 ui = context.resource(mapping, 'ui')
756 ui = context.resource(mapping, 'ui')
757 return ui.termwidth()
757 return ui.termwidth()
758
758
759 @templatekeyword('user', requires={'ctx'})
759 @templatekeyword('user', requires={'ctx'})
760 def showuser(context, mapping):
760 def showuser(context, mapping):
761 """String. The unmodified author of the changeset."""
761 """String. The unmodified author of the changeset."""
762 ctx = context.resource(mapping, 'ctx')
762 ctx = context.resource(mapping, 'ctx')
763 return ctx.user()
763 return ctx.user()
764
764
765 @templatekeyword('instabilities', requires={'ctx'})
765 @templatekeyword('instabilities', requires={'ctx'})
766 def showinstabilities(context, mapping):
766 def showinstabilities(context, mapping):
767 """List of strings. Evolution instabilities affecting the changeset.
767 """List of strings. Evolution instabilities affecting the changeset.
768 (EXPERIMENTAL)
768 (EXPERIMENTAL)
769 """
769 """
770 ctx = context.resource(mapping, 'ctx')
770 ctx = context.resource(mapping, 'ctx')
771 return compatlist(context, mapping, 'instability', ctx.instabilities(),
771 return compatlist(context, mapping, 'instability', ctx.instabilities(),
772 plural='instabilities')
772 plural='instabilities')
773
773
774 @templatekeyword('verbosity', requires={'ui'})
774 @templatekeyword('verbosity', requires={'ui'})
775 def showverbosity(context, mapping):
775 def showverbosity(context, mapping):
776 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
776 """String. The current output verbosity in 'debug', 'quiet', 'verbose',
777 or ''."""
777 or ''."""
778 ui = context.resource(mapping, 'ui')
778 ui = context.resource(mapping, 'ui')
779 # see logcmdutil.changesettemplater for priority of these flags
779 # see logcmdutil.changesettemplater for priority of these flags
780 if ui.debugflag:
780 if ui.debugflag:
781 return 'debug'
781 return 'debug'
782 elif ui.quiet:
782 elif ui.quiet:
783 return 'quiet'
783 return 'quiet'
784 elif ui.verbose:
784 elif ui.verbose:
785 return 'verbose'
785 return 'verbose'
786 return ''
786 return ''
787
787
788 @templatekeyword('whyunstable', requires={'repo', 'ctx'})
788 @templatekeyword('whyunstable', requires={'repo', 'ctx'})
789 def showwhyunstable(context, mapping):
789 def showwhyunstable(context, mapping):
790 """List of dicts explaining all instabilities of a changeset.
790 """List of dicts explaining all instabilities of a changeset.
791 (EXPERIMENTAL)
791 (EXPERIMENTAL)
792 """
792 """
793 repo = context.resource(mapping, 'repo')
793 repo = context.resource(mapping, 'repo')
794 ctx = context.resource(mapping, 'ctx')
794 ctx = context.resource(mapping, 'ctx')
795
795
796 def formatnode(ctx):
796 def formatnode(ctx):
797 return '%s (%s)' % (scmutil.formatchangeid(ctx), ctx.phasestr())
797 return '%s (%s)' % (scmutil.formatchangeid(ctx), ctx.phasestr())
798
798
799 entries = obsutil.whyunstable(repo, ctx)
799 entries = obsutil.whyunstable(repo, ctx)
800
800
801 for entry in entries:
801 for entry in entries:
802 if entry.get('divergentnodes'):
802 if entry.get('divergentnodes'):
803 dnodes = entry['divergentnodes']
803 dnodes = entry['divergentnodes']
804 dnhybrid = _hybrid(None, [dnode.hex() for dnode in dnodes],
804 dnhybrid = _hybrid(None, [dnode.hex() for dnode in dnodes],
805 lambda x: {'ctx': repo[x]},
805 lambda x: {'ctx': repo[x]},
806 lambda x: formatnode(repo[x]))
806 lambda x: formatnode(repo[x]))
807 entry['divergentnodes'] = dnhybrid
807 entry['divergentnodes'] = dnhybrid
808
808
809 tmpl = ('{instability}:{if(divergentnodes, " ")}{divergentnodes} '
809 tmpl = ('{instability}:{if(divergentnodes, " ")}{divergentnodes} '
810 '{reason} {node|short}')
810 '{reason} {node|short}')
811 return templateutil.mappinglist(entries, tmpl=tmpl, sep='\n')
811 return templateutil.mappinglist(entries, tmpl=tmpl, sep='\n')
812
812
813 def loadkeyword(ui, extname, registrarobj):
813 def loadkeyword(ui, extname, registrarobj):
814 """Load template keyword from specified registrarobj
814 """Load template keyword from specified registrarobj
815 """
815 """
816 for name, func in registrarobj._table.iteritems():
816 for name, func in registrarobj._table.iteritems():
817 keywords[name] = func
817 keywords[name] = func
818
818
819 # tell hggettext to extract docstrings from these functions:
819 # tell hggettext to extract docstrings from these functions:
820 i18nfunctions = keywords.values()
820 i18nfunctions = keywords.values()
@@ -1,937 +1,948
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 compatfileslist(context, mapping, name, files):
574 """Wrap list of file names to support old-style list template and field
575 names
576
577 This exists for backward compatibility. Use hybridlist for new template
578 keywords.
579 """
580 f = _showcompatlist(context, mapping, name, files)
581 return hybrid(f, files, lambda x: {'file': x, 'path': x},
582 pycompat.identity)
583
573 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
584 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
574 """Return a generator that renders old-style list template
585 """Return a generator that renders old-style list template
575
586
576 name is name of key in template map.
587 name is name of key in template map.
577 values is list of strings or dicts.
588 values is list of strings or dicts.
578 plural is plural of name, if not simply name + 's'.
589 plural is plural of name, if not simply name + 's'.
579 separator is used to join values as a string
590 separator is used to join values as a string
580
591
581 expansion works like this, given name 'foo'.
592 expansion works like this, given name 'foo'.
582
593
583 if values is empty, expand 'no_foos'.
594 if values is empty, expand 'no_foos'.
584
595
585 if 'foo' not in template map, return values as a string,
596 if 'foo' not in template map, return values as a string,
586 joined by 'separator'.
597 joined by 'separator'.
587
598
588 expand 'start_foos'.
599 expand 'start_foos'.
589
600
590 for each value, expand 'foo'. if 'last_foo' in template
601 for each value, expand 'foo'. if 'last_foo' in template
591 map, expand it instead of 'foo' for last key.
602 map, expand it instead of 'foo' for last key.
592
603
593 expand 'end_foos'.
604 expand 'end_foos'.
594 """
605 """
595 if not plural:
606 if not plural:
596 plural = name + 's'
607 plural = name + 's'
597 if not values:
608 if not values:
598 noname = 'no_' + plural
609 noname = 'no_' + plural
599 if context.preload(noname):
610 if context.preload(noname):
600 yield context.process(noname, mapping)
611 yield context.process(noname, mapping)
601 return
612 return
602 if not context.preload(name):
613 if not context.preload(name):
603 if isinstance(values[0], bytes):
614 if isinstance(values[0], bytes):
604 yield separator.join(values)
615 yield separator.join(values)
605 else:
616 else:
606 for v in values:
617 for v in values:
607 r = dict(v)
618 r = dict(v)
608 r.update(mapping)
619 r.update(mapping)
609 yield r
620 yield r
610 return
621 return
611 startname = 'start_' + plural
622 startname = 'start_' + plural
612 if context.preload(startname):
623 if context.preload(startname):
613 yield context.process(startname, mapping)
624 yield context.process(startname, mapping)
614 def one(v, tag=name):
625 def one(v, tag=name):
615 vmapping = {}
626 vmapping = {}
616 try:
627 try:
617 vmapping.update(v)
628 vmapping.update(v)
618 # Python 2 raises ValueError if the type of v is wrong. Python
629 # Python 2 raises ValueError if the type of v is wrong. Python
619 # 3 raises TypeError.
630 # 3 raises TypeError.
620 except (AttributeError, TypeError, ValueError):
631 except (AttributeError, TypeError, ValueError):
621 try:
632 try:
622 # Python 2 raises ValueError trying to destructure an e.g.
633 # Python 2 raises ValueError trying to destructure an e.g.
623 # bytes. Python 3 raises TypeError.
634 # bytes. Python 3 raises TypeError.
624 for a, b in v:
635 for a, b in v:
625 vmapping[a] = b
636 vmapping[a] = b
626 except (TypeError, ValueError):
637 except (TypeError, ValueError):
627 vmapping[name] = v
638 vmapping[name] = v
628 vmapping = context.overlaymap(mapping, vmapping)
639 vmapping = context.overlaymap(mapping, vmapping)
629 return context.process(tag, vmapping)
640 return context.process(tag, vmapping)
630 lastname = 'last_' + name
641 lastname = 'last_' + name
631 if context.preload(lastname):
642 if context.preload(lastname):
632 last = values.pop()
643 last = values.pop()
633 else:
644 else:
634 last = None
645 last = None
635 for v in values:
646 for v in values:
636 yield one(v)
647 yield one(v)
637 if last is not None:
648 if last is not None:
638 yield one(last, tag=lastname)
649 yield one(last, tag=lastname)
639 endname = 'end_' + plural
650 endname = 'end_' + plural
640 if context.preload(endname):
651 if context.preload(endname):
641 yield context.process(endname, mapping)
652 yield context.process(endname, mapping)
642
653
643 def flatten(context, mapping, thing):
654 def flatten(context, mapping, thing):
644 """Yield a single stream from a possibly nested set of iterators"""
655 """Yield a single stream from a possibly nested set of iterators"""
645 if isinstance(thing, wrapped):
656 if isinstance(thing, wrapped):
646 thing = thing.show(context, mapping)
657 thing = thing.show(context, mapping)
647 if isinstance(thing, bytes):
658 if isinstance(thing, bytes):
648 yield thing
659 yield thing
649 elif isinstance(thing, str):
660 elif isinstance(thing, str):
650 # We can only hit this on Python 3, and it's here to guard
661 # We can only hit this on Python 3, and it's here to guard
651 # against infinite recursion.
662 # against infinite recursion.
652 raise error.ProgrammingError('Mercurial IO including templates is done'
663 raise error.ProgrammingError('Mercurial IO including templates is done'
653 ' with bytes, not strings, got %r' % thing)
664 ' with bytes, not strings, got %r' % thing)
654 elif thing is None:
665 elif thing is None:
655 pass
666 pass
656 elif not util.safehasattr(thing, '__iter__'):
667 elif not util.safehasattr(thing, '__iter__'):
657 yield pycompat.bytestr(thing)
668 yield pycompat.bytestr(thing)
658 else:
669 else:
659 for i in thing:
670 for i in thing:
660 if isinstance(i, wrapped):
671 if isinstance(i, wrapped):
661 i = i.show(context, mapping)
672 i = i.show(context, mapping)
662 if isinstance(i, bytes):
673 if isinstance(i, bytes):
663 yield i
674 yield i
664 elif i is None:
675 elif i is None:
665 pass
676 pass
666 elif not util.safehasattr(i, '__iter__'):
677 elif not util.safehasattr(i, '__iter__'):
667 yield pycompat.bytestr(i)
678 yield pycompat.bytestr(i)
668 else:
679 else:
669 for j in flatten(context, mapping, i):
680 for j in flatten(context, mapping, i):
670 yield j
681 yield j
671
682
672 def stringify(context, mapping, thing):
683 def stringify(context, mapping, thing):
673 """Turn values into bytes by converting into text and concatenating them"""
684 """Turn values into bytes by converting into text and concatenating them"""
674 if isinstance(thing, bytes):
685 if isinstance(thing, bytes):
675 return thing # retain localstr to be round-tripped
686 return thing # retain localstr to be round-tripped
676 return b''.join(flatten(context, mapping, thing))
687 return b''.join(flatten(context, mapping, thing))
677
688
678 def findsymbolicname(arg):
689 def findsymbolicname(arg):
679 """Find symbolic name for the given compiled expression; returns None
690 """Find symbolic name for the given compiled expression; returns None
680 if nothing found reliably"""
691 if nothing found reliably"""
681 while True:
692 while True:
682 func, data = arg
693 func, data = arg
683 if func is runsymbol:
694 if func is runsymbol:
684 return data
695 return data
685 elif func is runfilter:
696 elif func is runfilter:
686 arg = data[0]
697 arg = data[0]
687 else:
698 else:
688 return None
699 return None
689
700
690 def _nonempty(xiter):
701 def _nonempty(xiter):
691 try:
702 try:
692 next(xiter)
703 next(xiter)
693 return True
704 return True
694 except StopIteration:
705 except StopIteration:
695 return False
706 return False
696
707
697 def _unthunk(context, mapping, thing):
708 def _unthunk(context, mapping, thing):
698 """Evaluate a lazy byte string into value"""
709 """Evaluate a lazy byte string into value"""
699 if not isinstance(thing, types.GeneratorType):
710 if not isinstance(thing, types.GeneratorType):
700 return thing
711 return thing
701 return stringify(context, mapping, thing)
712 return stringify(context, mapping, thing)
702
713
703 def evalrawexp(context, mapping, arg):
714 def evalrawexp(context, mapping, arg):
704 """Evaluate given argument as a bare template object which may require
715 """Evaluate given argument as a bare template object which may require
705 further processing (such as folding generator of strings)"""
716 further processing (such as folding generator of strings)"""
706 func, data = arg
717 func, data = arg
707 return func(context, mapping, data)
718 return func(context, mapping, data)
708
719
709 def evalwrapped(context, mapping, arg):
720 def evalwrapped(context, mapping, arg):
710 """Evaluate given argument to wrapped object"""
721 """Evaluate given argument to wrapped object"""
711 thing = evalrawexp(context, mapping, arg)
722 thing = evalrawexp(context, mapping, arg)
712 return makewrapped(context, mapping, thing)
723 return makewrapped(context, mapping, thing)
713
724
714 def makewrapped(context, mapping, thing):
725 def makewrapped(context, mapping, thing):
715 """Lift object to a wrapped type"""
726 """Lift object to a wrapped type"""
716 if isinstance(thing, wrapped):
727 if isinstance(thing, wrapped):
717 return thing
728 return thing
718 thing = _unthunk(context, mapping, thing)
729 thing = _unthunk(context, mapping, thing)
719 if isinstance(thing, bytes):
730 if isinstance(thing, bytes):
720 return wrappedbytes(thing)
731 return wrappedbytes(thing)
721 return wrappedvalue(thing)
732 return wrappedvalue(thing)
722
733
723 def evalfuncarg(context, mapping, arg):
734 def evalfuncarg(context, mapping, arg):
724 """Evaluate given argument as value type"""
735 """Evaluate given argument as value type"""
725 return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
736 return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
726
737
727 def unwrapvalue(context, mapping, thing):
738 def unwrapvalue(context, mapping, thing):
728 """Move the inner value object out of the wrapper"""
739 """Move the inner value object out of the wrapper"""
729 if isinstance(thing, wrapped):
740 if isinstance(thing, wrapped):
730 return thing.tovalue(context, mapping)
741 return thing.tovalue(context, mapping)
731 # evalrawexp() may return string, generator of strings or arbitrary object
742 # evalrawexp() may return string, generator of strings or arbitrary object
732 # such as date tuple, but filter does not want generator.
743 # such as date tuple, but filter does not want generator.
733 return _unthunk(context, mapping, thing)
744 return _unthunk(context, mapping, thing)
734
745
735 def evalboolean(context, mapping, arg):
746 def evalboolean(context, mapping, arg):
736 """Evaluate given argument as boolean, but also takes boolean literals"""
747 """Evaluate given argument as boolean, but also takes boolean literals"""
737 func, data = arg
748 func, data = arg
738 if func is runsymbol:
749 if func is runsymbol:
739 thing = func(context, mapping, data, default=None)
750 thing = func(context, mapping, data, default=None)
740 if thing is None:
751 if thing is None:
741 # not a template keyword, takes as a boolean literal
752 # not a template keyword, takes as a boolean literal
742 thing = stringutil.parsebool(data)
753 thing = stringutil.parsebool(data)
743 else:
754 else:
744 thing = func(context, mapping, data)
755 thing = func(context, mapping, data)
745 return makewrapped(context, mapping, thing).tobool(context, mapping)
756 return makewrapped(context, mapping, thing).tobool(context, mapping)
746
757
747 def evaldate(context, mapping, arg, err=None):
758 def evaldate(context, mapping, arg, err=None):
748 """Evaluate given argument as a date tuple or a date string; returns
759 """Evaluate given argument as a date tuple or a date string; returns
749 a (unixtime, offset) tuple"""
760 a (unixtime, offset) tuple"""
750 thing = evalrawexp(context, mapping, arg)
761 thing = evalrawexp(context, mapping, arg)
751 return unwrapdate(context, mapping, thing, err)
762 return unwrapdate(context, mapping, thing, err)
752
763
753 def unwrapdate(context, mapping, thing, err=None):
764 def unwrapdate(context, mapping, thing, err=None):
754 if isinstance(thing, date):
765 if isinstance(thing, date):
755 return thing.tovalue(context, mapping)
766 return thing.tovalue(context, mapping)
756 # TODO: update hgweb to not return bare tuple; then just stringify 'thing'
767 # TODO: update hgweb to not return bare tuple; then just stringify 'thing'
757 thing = unwrapvalue(context, mapping, thing)
768 thing = unwrapvalue(context, mapping, thing)
758 try:
769 try:
759 return dateutil.parsedate(thing)
770 return dateutil.parsedate(thing)
760 except AttributeError:
771 except AttributeError:
761 raise error.ParseError(err or _('not a date tuple nor a string'))
772 raise error.ParseError(err or _('not a date tuple nor a string'))
762 except error.ParseError:
773 except error.ParseError:
763 if not err:
774 if not err:
764 raise
775 raise
765 raise error.ParseError(err)
776 raise error.ParseError(err)
766
777
767 def evalinteger(context, mapping, arg, err=None):
778 def evalinteger(context, mapping, arg, err=None):
768 thing = evalrawexp(context, mapping, arg)
779 thing = evalrawexp(context, mapping, arg)
769 return unwrapinteger(context, mapping, thing, err)
780 return unwrapinteger(context, mapping, thing, err)
770
781
771 def unwrapinteger(context, mapping, thing, err=None):
782 def unwrapinteger(context, mapping, thing, err=None):
772 thing = unwrapvalue(context, mapping, thing)
783 thing = unwrapvalue(context, mapping, thing)
773 try:
784 try:
774 return int(thing)
785 return int(thing)
775 except (TypeError, ValueError):
786 except (TypeError, ValueError):
776 raise error.ParseError(err or _('not an integer'))
787 raise error.ParseError(err or _('not an integer'))
777
788
778 def evalstring(context, mapping, arg):
789 def evalstring(context, mapping, arg):
779 return stringify(context, mapping, evalrawexp(context, mapping, arg))
790 return stringify(context, mapping, evalrawexp(context, mapping, arg))
780
791
781 def evalstringliteral(context, mapping, arg):
792 def evalstringliteral(context, mapping, arg):
782 """Evaluate given argument as string template, but returns symbol name
793 """Evaluate given argument as string template, but returns symbol name
783 if it is unknown"""
794 if it is unknown"""
784 func, data = arg
795 func, data = arg
785 if func is runsymbol:
796 if func is runsymbol:
786 thing = func(context, mapping, data, default=data)
797 thing = func(context, mapping, data, default=data)
787 else:
798 else:
788 thing = func(context, mapping, data)
799 thing = func(context, mapping, data)
789 return stringify(context, mapping, thing)
800 return stringify(context, mapping, thing)
790
801
791 _unwrapfuncbytype = {
802 _unwrapfuncbytype = {
792 None: unwrapvalue,
803 None: unwrapvalue,
793 bytes: stringify,
804 bytes: stringify,
794 date: unwrapdate,
805 date: unwrapdate,
795 int: unwrapinteger,
806 int: unwrapinteger,
796 }
807 }
797
808
798 def unwrapastype(context, mapping, thing, typ):
809 def unwrapastype(context, mapping, thing, typ):
799 """Move the inner value object out of the wrapper and coerce its type"""
810 """Move the inner value object out of the wrapper and coerce its type"""
800 try:
811 try:
801 f = _unwrapfuncbytype[typ]
812 f = _unwrapfuncbytype[typ]
802 except KeyError:
813 except KeyError:
803 raise error.ProgrammingError('invalid type specified: %r' % typ)
814 raise error.ProgrammingError('invalid type specified: %r' % typ)
804 return f(context, mapping, thing)
815 return f(context, mapping, thing)
805
816
806 def runinteger(context, mapping, data):
817 def runinteger(context, mapping, data):
807 return int(data)
818 return int(data)
808
819
809 def runstring(context, mapping, data):
820 def runstring(context, mapping, data):
810 return data
821 return data
811
822
812 def _recursivesymbolblocker(key):
823 def _recursivesymbolblocker(key):
813 def showrecursion(context, mapping):
824 def showrecursion(context, mapping):
814 raise error.Abort(_("recursive reference '%s' in template") % key)
825 raise error.Abort(_("recursive reference '%s' in template") % key)
815 showrecursion._requires = () # mark as new-style templatekw
826 showrecursion._requires = () # mark as new-style templatekw
816 return showrecursion
827 return showrecursion
817
828
818 def runsymbol(context, mapping, key, default=''):
829 def runsymbol(context, mapping, key, default=''):
819 v = context.symbol(mapping, key)
830 v = context.symbol(mapping, key)
820 if v is None:
831 if v is None:
821 # put poison to cut recursion. we can't move this to parsing phase
832 # put poison to cut recursion. we can't move this to parsing phase
822 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
833 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
823 safemapping = mapping.copy()
834 safemapping = mapping.copy()
824 safemapping[key] = _recursivesymbolblocker(key)
835 safemapping[key] = _recursivesymbolblocker(key)
825 try:
836 try:
826 v = context.process(key, safemapping)
837 v = context.process(key, safemapping)
827 except TemplateNotFound:
838 except TemplateNotFound:
828 v = default
839 v = default
829 if callable(v) and getattr(v, '_requires', None) is None:
840 if callable(v) and getattr(v, '_requires', None) is None:
830 # old templatekw: expand all keywords and resources
841 # old templatekw: expand all keywords and resources
831 # (TODO: drop support for old-style functions. 'f._requires = ()'
842 # (TODO: drop support for old-style functions. 'f._requires = ()'
832 # can be removed.)
843 # can be removed.)
833 props = {k: context._resources.lookup(context, mapping, k)
844 props = {k: context._resources.lookup(context, mapping, k)
834 for k in context._resources.knownkeys()}
845 for k in context._resources.knownkeys()}
835 # pass context to _showcompatlist() through templatekw._showlist()
846 # pass context to _showcompatlist() through templatekw._showlist()
836 props['templ'] = context
847 props['templ'] = context
837 props.update(mapping)
848 props.update(mapping)
838 ui = props.get('ui')
849 ui = props.get('ui')
839 if ui:
850 if ui:
840 ui.deprecwarn("old-style template keyword '%s'" % key, '4.8')
851 ui.deprecwarn("old-style template keyword '%s'" % key, '4.8')
841 return v(**pycompat.strkwargs(props))
852 return v(**pycompat.strkwargs(props))
842 if callable(v):
853 if callable(v):
843 # new templatekw
854 # new templatekw
844 try:
855 try:
845 return v(context, mapping)
856 return v(context, mapping)
846 except ResourceUnavailable:
857 except ResourceUnavailable:
847 # unsupported keyword is mapped to empty just like unknown keyword
858 # unsupported keyword is mapped to empty just like unknown keyword
848 return None
859 return None
849 return v
860 return v
850
861
851 def runtemplate(context, mapping, template):
862 def runtemplate(context, mapping, template):
852 for arg in template:
863 for arg in template:
853 yield evalrawexp(context, mapping, arg)
864 yield evalrawexp(context, mapping, arg)
854
865
855 def runfilter(context, mapping, data):
866 def runfilter(context, mapping, data):
856 arg, filt = data
867 arg, filt = data
857 thing = evalrawexp(context, mapping, arg)
868 thing = evalrawexp(context, mapping, arg)
858 intype = getattr(filt, '_intype', None)
869 intype = getattr(filt, '_intype', None)
859 try:
870 try:
860 thing = unwrapastype(context, mapping, thing, intype)
871 thing = unwrapastype(context, mapping, thing, intype)
861 return filt(thing)
872 return filt(thing)
862 except error.ParseError as e:
873 except error.ParseError as e:
863 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
874 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
864
875
865 def _formatfiltererror(arg, filt):
876 def _formatfiltererror(arg, filt):
866 fn = pycompat.sysbytes(filt.__name__)
877 fn = pycompat.sysbytes(filt.__name__)
867 sym = findsymbolicname(arg)
878 sym = findsymbolicname(arg)
868 if not sym:
879 if not sym:
869 return _("incompatible use of template filter '%s'") % fn
880 return _("incompatible use of template filter '%s'") % fn
870 return (_("template filter '%s' is not compatible with keyword '%s'")
881 return (_("template filter '%s' is not compatible with keyword '%s'")
871 % (fn, sym))
882 % (fn, sym))
872
883
873 def _iteroverlaymaps(context, origmapping, newmappings):
884 def _iteroverlaymaps(context, origmapping, newmappings):
874 """Generate combined mappings from the original mapping and an iterable
885 """Generate combined mappings from the original mapping and an iterable
875 of partial mappings to override the original"""
886 of partial mappings to override the original"""
876 for i, nm in enumerate(newmappings):
887 for i, nm in enumerate(newmappings):
877 lm = context.overlaymap(origmapping, nm)
888 lm = context.overlaymap(origmapping, nm)
878 lm['index'] = i
889 lm['index'] = i
879 yield lm
890 yield lm
880
891
881 def _applymap(context, mapping, d, darg, targ):
892 def _applymap(context, mapping, d, darg, targ):
882 try:
893 try:
883 diter = d.itermaps(context)
894 diter = d.itermaps(context)
884 except error.ParseError as err:
895 except error.ParseError as err:
885 sym = findsymbolicname(darg)
896 sym = findsymbolicname(darg)
886 if not sym:
897 if not sym:
887 raise
898 raise
888 hint = _("keyword '%s' does not support map operation") % sym
899 hint = _("keyword '%s' does not support map operation") % sym
889 raise error.ParseError(bytes(err), hint=hint)
900 raise error.ParseError(bytes(err), hint=hint)
890 for lm in _iteroverlaymaps(context, mapping, diter):
901 for lm in _iteroverlaymaps(context, mapping, diter):
891 yield evalrawexp(context, lm, targ)
902 yield evalrawexp(context, lm, targ)
892
903
893 def runmap(context, mapping, data):
904 def runmap(context, mapping, data):
894 darg, targ = data
905 darg, targ = data
895 d = evalwrapped(context, mapping, darg)
906 d = evalwrapped(context, mapping, darg)
896 return mappedgenerator(_applymap, args=(mapping, d, darg, targ))
907 return mappedgenerator(_applymap, args=(mapping, d, darg, targ))
897
908
898 def runmember(context, mapping, data):
909 def runmember(context, mapping, data):
899 darg, memb = data
910 darg, memb = data
900 d = evalwrapped(context, mapping, darg)
911 d = evalwrapped(context, mapping, darg)
901 if isinstance(d, mappable):
912 if isinstance(d, mappable):
902 lm = context.overlaymap(mapping, d.tomap(context))
913 lm = context.overlaymap(mapping, d.tomap(context))
903 return runsymbol(context, lm, memb)
914 return runsymbol(context, lm, memb)
904 try:
915 try:
905 return d.getmember(context, mapping, memb)
916 return d.getmember(context, mapping, memb)
906 except error.ParseError as err:
917 except error.ParseError as err:
907 sym = findsymbolicname(darg)
918 sym = findsymbolicname(darg)
908 if not sym:
919 if not sym:
909 raise
920 raise
910 hint = _("keyword '%s' does not support member operation") % sym
921 hint = _("keyword '%s' does not support member operation") % sym
911 raise error.ParseError(bytes(err), hint=hint)
922 raise error.ParseError(bytes(err), hint=hint)
912
923
913 def runnegate(context, mapping, data):
924 def runnegate(context, mapping, data):
914 data = evalinteger(context, mapping, data,
925 data = evalinteger(context, mapping, data,
915 _('negation needs an integer argument'))
926 _('negation needs an integer argument'))
916 return -data
927 return -data
917
928
918 def runarithmetic(context, mapping, data):
929 def runarithmetic(context, mapping, data):
919 func, left, right = data
930 func, left, right = data
920 left = evalinteger(context, mapping, left,
931 left = evalinteger(context, mapping, left,
921 _('arithmetic only defined on integers'))
932 _('arithmetic only defined on integers'))
922 right = evalinteger(context, mapping, right,
933 right = evalinteger(context, mapping, right,
923 _('arithmetic only defined on integers'))
934 _('arithmetic only defined on integers'))
924 try:
935 try:
925 return func(left, right)
936 return func(left, right)
926 except ZeroDivisionError:
937 except ZeroDivisionError:
927 raise error.Abort(_('division by zero is not defined'))
938 raise error.Abort(_('division by zero is not defined'))
928
939
929 def joinitems(itemiter, sep):
940 def joinitems(itemiter, sep):
930 """Join items with the separator; Returns generator of bytes"""
941 """Join items with the separator; Returns generator of bytes"""
931 first = True
942 first = True
932 for x in itemiter:
943 for x in itemiter:
933 if first:
944 if first:
934 first = False
945 first = False
935 elif sep:
946 elif sep:
936 yield sep
947 yield sep
937 yield x
948 yield x
@@ -1,1435 +1,1441
1 Test template filters and functions
1 Test template filters and functions
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 $ hg phase -r 5 --public
49 $ hg phase -r 5 --public
50 $ hg phase -r 7 --secret --force
50 $ hg phase -r 7 --secret --force
51
51
52 Filters work:
52 Filters work:
53
53
54 $ hg log --template '{author|domain}\n'
54 $ hg log --template '{author|domain}\n'
55
55
56 hostname
56 hostname
57
57
58
58
59
59
60
60
61 place
61 place
62 place
62 place
63 hostname
63 hostname
64
64
65 $ hg log --template '{author|person}\n'
65 $ hg log --template '{author|person}\n'
66 test
66 test
67 User Name
67 User Name
68 person
68 person
69 person
69 person
70 person
70 person
71 person
71 person
72 other
72 other
73 A. N. Other
73 A. N. Other
74 User Name
74 User Name
75
75
76 $ hg log --template '{author|user}\n'
76 $ hg log --template '{author|user}\n'
77 test
77 test
78 user
78 user
79 person
79 person
80 person
80 person
81 person
81 person
82 person
82 person
83 other
83 other
84 other
84 other
85 user
85 user
86
86
87 $ hg log --template '{date|date}\n'
87 $ hg log --template '{date|date}\n'
88 Wed Jan 01 10:01:00 2020 +0000
88 Wed Jan 01 10:01:00 2020 +0000
89 Mon Jan 12 13:46:40 1970 +0000
89 Mon Jan 12 13:46:40 1970 +0000
90 Sun Jan 18 08:40:01 1970 +0000
90 Sun Jan 18 08:40:01 1970 +0000
91 Sun Jan 18 08:40:00 1970 +0000
91 Sun Jan 18 08:40:00 1970 +0000
92 Sat Jan 17 04:53:20 1970 +0000
92 Sat Jan 17 04:53:20 1970 +0000
93 Fri Jan 16 01:06:40 1970 +0000
93 Fri Jan 16 01:06:40 1970 +0000
94 Wed Jan 14 21:20:00 1970 +0000
94 Wed Jan 14 21:20:00 1970 +0000
95 Tue Jan 13 17:33:20 1970 +0000
95 Tue Jan 13 17:33:20 1970 +0000
96 Mon Jan 12 13:46:40 1970 +0000
96 Mon Jan 12 13:46:40 1970 +0000
97
97
98 $ hg log --template '{date|isodate}\n'
98 $ hg log --template '{date|isodate}\n'
99 2020-01-01 10:01 +0000
99 2020-01-01 10:01 +0000
100 1970-01-12 13:46 +0000
100 1970-01-12 13:46 +0000
101 1970-01-18 08:40 +0000
101 1970-01-18 08:40 +0000
102 1970-01-18 08:40 +0000
102 1970-01-18 08:40 +0000
103 1970-01-17 04:53 +0000
103 1970-01-17 04:53 +0000
104 1970-01-16 01:06 +0000
104 1970-01-16 01:06 +0000
105 1970-01-14 21:20 +0000
105 1970-01-14 21:20 +0000
106 1970-01-13 17:33 +0000
106 1970-01-13 17:33 +0000
107 1970-01-12 13:46 +0000
107 1970-01-12 13:46 +0000
108
108
109 $ hg log --template '{date|isodatesec}\n'
109 $ hg log --template '{date|isodatesec}\n'
110 2020-01-01 10:01:00 +0000
110 2020-01-01 10:01:00 +0000
111 1970-01-12 13:46:40 +0000
111 1970-01-12 13:46:40 +0000
112 1970-01-18 08:40:01 +0000
112 1970-01-18 08:40:01 +0000
113 1970-01-18 08:40:00 +0000
113 1970-01-18 08:40:00 +0000
114 1970-01-17 04:53:20 +0000
114 1970-01-17 04:53:20 +0000
115 1970-01-16 01:06:40 +0000
115 1970-01-16 01:06:40 +0000
116 1970-01-14 21:20:00 +0000
116 1970-01-14 21:20:00 +0000
117 1970-01-13 17:33:20 +0000
117 1970-01-13 17:33:20 +0000
118 1970-01-12 13:46:40 +0000
118 1970-01-12 13:46:40 +0000
119
119
120 $ hg log --template '{date|rfc822date}\n'
120 $ hg log --template '{date|rfc822date}\n'
121 Wed, 01 Jan 2020 10:01:00 +0000
121 Wed, 01 Jan 2020 10:01:00 +0000
122 Mon, 12 Jan 1970 13:46:40 +0000
122 Mon, 12 Jan 1970 13:46:40 +0000
123 Sun, 18 Jan 1970 08:40:01 +0000
123 Sun, 18 Jan 1970 08:40:01 +0000
124 Sun, 18 Jan 1970 08:40:00 +0000
124 Sun, 18 Jan 1970 08:40:00 +0000
125 Sat, 17 Jan 1970 04:53:20 +0000
125 Sat, 17 Jan 1970 04:53:20 +0000
126 Fri, 16 Jan 1970 01:06:40 +0000
126 Fri, 16 Jan 1970 01:06:40 +0000
127 Wed, 14 Jan 1970 21:20:00 +0000
127 Wed, 14 Jan 1970 21:20:00 +0000
128 Tue, 13 Jan 1970 17:33:20 +0000
128 Tue, 13 Jan 1970 17:33:20 +0000
129 Mon, 12 Jan 1970 13:46:40 +0000
129 Mon, 12 Jan 1970 13:46:40 +0000
130
130
131 $ hg log --template '{desc|firstline}\n'
131 $ hg log --template '{desc|firstline}\n'
132 third
132 third
133 second
133 second
134 merge
134 merge
135 new head
135 new head
136 new branch
136 new branch
137 no user, no domain
137 no user, no domain
138 no person
138 no person
139 other 1
139 other 1
140 line 1
140 line 1
141
141
142 $ hg log --template '{node|short}\n'
142 $ hg log --template '{node|short}\n'
143 95c24699272e
143 95c24699272e
144 29114dbae42b
144 29114dbae42b
145 d41e714fe50d
145 d41e714fe50d
146 13207e5a10d9
146 13207e5a10d9
147 bbe44766e73d
147 bbe44766e73d
148 10e46f2dcbf4
148 10e46f2dcbf4
149 97054abb4ab8
149 97054abb4ab8
150 b608e9d1a3f0
150 b608e9d1a3f0
151 1e4e1b8f71e0
151 1e4e1b8f71e0
152
152
153 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
153 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
154 <changeset author="test"/>
154 <changeset author="test"/>
155 <changeset author="User Name &lt;user@hostname&gt;"/>
155 <changeset author="User Name &lt;user@hostname&gt;"/>
156 <changeset author="person"/>
156 <changeset author="person"/>
157 <changeset author="person"/>
157 <changeset author="person"/>
158 <changeset author="person"/>
158 <changeset author="person"/>
159 <changeset author="person"/>
159 <changeset author="person"/>
160 <changeset author="other@place"/>
160 <changeset author="other@place"/>
161 <changeset author="A. N. Other &lt;other@place&gt;"/>
161 <changeset author="A. N. Other &lt;other@place&gt;"/>
162 <changeset author="User Name &lt;user@hostname&gt;"/>
162 <changeset author="User Name &lt;user@hostname&gt;"/>
163
163
164 $ hg log --template '{rev}: {children}\n'
164 $ hg log --template '{rev}: {children}\n'
165 8:
165 8:
166 7: 8:95c24699272e
166 7: 8:95c24699272e
167 6:
167 6:
168 5: 6:d41e714fe50d
168 5: 6:d41e714fe50d
169 4: 6:d41e714fe50d
169 4: 6:d41e714fe50d
170 3: 4:bbe44766e73d 5:13207e5a10d9
170 3: 4:bbe44766e73d 5:13207e5a10d9
171 2: 3:10e46f2dcbf4
171 2: 3:10e46f2dcbf4
172 1: 2:97054abb4ab8
172 1: 2:97054abb4ab8
173 0: 1:b608e9d1a3f0
173 0: 1:b608e9d1a3f0
174
174
175 Formatnode filter works:
175 Formatnode filter works:
176
176
177 $ hg -q log -r 0 --template '{node|formatnode}\n'
177 $ hg -q log -r 0 --template '{node|formatnode}\n'
178 1e4e1b8f71e0
178 1e4e1b8f71e0
179
179
180 $ hg log -r 0 --template '{node|formatnode}\n'
180 $ hg log -r 0 --template '{node|formatnode}\n'
181 1e4e1b8f71e0
181 1e4e1b8f71e0
182
182
183 $ hg -v log -r 0 --template '{node|formatnode}\n'
183 $ hg -v log -r 0 --template '{node|formatnode}\n'
184 1e4e1b8f71e0
184 1e4e1b8f71e0
185
185
186 $ hg --debug log -r 0 --template '{node|formatnode}\n'
186 $ hg --debug log -r 0 --template '{node|formatnode}\n'
187 1e4e1b8f71e05681d422154f5421e385fec3454f
187 1e4e1b8f71e05681d422154f5421e385fec3454f
188
188
189 Age filter:
189 Age filter:
190
190
191 $ hg init unstable-hash
191 $ hg init unstable-hash
192 $ cd unstable-hash
192 $ cd unstable-hash
193 $ hg log --template '{date|age}\n' > /dev/null || exit 1
193 $ hg log --template '{date|age}\n' > /dev/null || exit 1
194
194
195 >>> from __future__ import absolute_import
195 >>> from __future__ import absolute_import
196 >>> import datetime
196 >>> import datetime
197 >>> fp = open('a', 'wb')
197 >>> fp = open('a', 'wb')
198 >>> n = datetime.datetime.now() + datetime.timedelta(366 * 7)
198 >>> n = datetime.datetime.now() + datetime.timedelta(366 * 7)
199 >>> fp.write(b'%d-%d-%d 00:00' % (n.year, n.month, n.day)) and None
199 >>> fp.write(b'%d-%d-%d 00:00' % (n.year, n.month, n.day)) and None
200 >>> fp.close()
200 >>> fp.close()
201 $ hg add a
201 $ hg add a
202 $ hg commit -m future -d "`cat a`"
202 $ hg commit -m future -d "`cat a`"
203
203
204 $ hg log -l1 --template '{date|age}\n'
204 $ hg log -l1 --template '{date|age}\n'
205 7 years from now
205 7 years from now
206
206
207 $ cd ..
207 $ cd ..
208 $ rm -rf unstable-hash
208 $ rm -rf unstable-hash
209
209
210 Filename filters:
210 Filename filters:
211
211
212 $ hg debugtemplate '{"foo/bar"|basename}|{"foo/"|basename}|{"foo"|basename}|\n'
212 $ hg debugtemplate '{"foo/bar"|basename}|{"foo/"|basename}|{"foo"|basename}|\n'
213 bar||foo|
213 bar||foo|
214 $ hg debugtemplate '{"foo/bar"|dirname}|{"foo/"|dirname}|{"foo"|dirname}|\n'
214 $ hg debugtemplate '{"foo/bar"|dirname}|{"foo/"|dirname}|{"foo"|dirname}|\n'
215 foo|foo||
215 foo|foo||
216 $ hg debugtemplate '{"foo/bar"|stripdir}|{"foo/"|stripdir}|{"foo"|stripdir}|\n'
216 $ hg debugtemplate '{"foo/bar"|stripdir}|{"foo/"|stripdir}|{"foo"|stripdir}|\n'
217 foo|foo|foo|
217 foo|foo|foo|
218
218
219 commondir() filter:
219 commondir() filter:
220
220
221 $ hg debugtemplate '{""|splitlines|commondir}\n'
221 $ hg debugtemplate '{""|splitlines|commondir}\n'
222
222
223 $ hg debugtemplate '{"foo/bar\nfoo/baz\nfoo/foobar\n"|splitlines|commondir}\n'
223 $ hg debugtemplate '{"foo/bar\nfoo/baz\nfoo/foobar\n"|splitlines|commondir}\n'
224 foo
224 foo
225 $ hg debugtemplate '{"foo/bar\nfoo/bar\n"|splitlines|commondir}\n'
225 $ hg debugtemplate '{"foo/bar\nfoo/bar\n"|splitlines|commondir}\n'
226 foo
226 foo
227 $ hg debugtemplate '{"/foo/bar\n/foo/bar\n"|splitlines|commondir}\n'
227 $ hg debugtemplate '{"/foo/bar\n/foo/bar\n"|splitlines|commondir}\n'
228 foo
228 foo
229 $ hg debugtemplate '{"/foo\n/foo\n"|splitlines|commondir}\n'
229 $ hg debugtemplate '{"/foo\n/foo\n"|splitlines|commondir}\n'
230
230
231 $ hg debugtemplate '{"foo/bar\nbar/baz"|splitlines|commondir}\n'
231 $ hg debugtemplate '{"foo/bar\nbar/baz"|splitlines|commondir}\n'
232
232
233 $ hg debugtemplate '{"foo/bar\nbar/baz\nbar/foo\n"|splitlines|commondir}\n'
233 $ hg debugtemplate '{"foo/bar\nbar/baz\nbar/foo\n"|splitlines|commondir}\n'
234
234
235 $ hg debugtemplate '{"foo/../bar\nfoo/bar"|splitlines|commondir}\n'
235 $ hg debugtemplate '{"foo/../bar\nfoo/bar"|splitlines|commondir}\n'
236 foo
236 foo
237 $ hg debugtemplate '{"foo\n/foo"|splitlines|commondir}\n'
237 $ hg debugtemplate '{"foo\n/foo"|splitlines|commondir}\n'
238
238
239
239
240 $ hg log -r null -T '{rev|commondir}'
240 $ hg log -r null -T '{rev|commondir}'
241 hg: parse error: argument is not a list of text
241 hg: parse error: argument is not a list of text
242 (template filter 'commondir' is not compatible with keyword 'rev')
242 (template filter 'commondir' is not compatible with keyword 'rev')
243 [255]
243 [255]
244
244
245 Add a dummy commit to make up for the instability of the above:
245 Add a dummy commit to make up for the instability of the above:
246
246
247 $ echo a > a
247 $ echo a > a
248 $ hg add a
248 $ hg add a
249 $ hg ci -m future
249 $ hg ci -m future
250
250
251 Count filter:
251 Count filter:
252
252
253 $ hg log -l1 --template '{node|count} {node|short|count}\n'
253 $ hg log -l1 --template '{node|count} {node|short|count}\n'
254 40 12
254 40 12
255
255
256 $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
256 $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
257 0 1 4
257 0 1 4
258
258
259 $ hg log -G --template '{rev}: children: {children|count}, \
259 $ hg log -G --template '{rev}: children: {children|count}, \
260 > tags: {tags|count}, file_adds: {file_adds|count}, \
260 > tags: {tags|count}, file_adds: {file_adds|count}, \
261 > ancestors: {revset("ancestors(%s)", rev)|count}'
261 > ancestors: {revset("ancestors(%s)", rev)|count}'
262 @ 9: children: 0, tags: 1, file_adds: 1, ancestors: 3
262 @ 9: children: 0, tags: 1, file_adds: 1, ancestors: 3
263 |
263 |
264 o 8: children: 1, tags: 0, file_adds: 2, ancestors: 2
264 o 8: children: 1, tags: 0, file_adds: 2, ancestors: 2
265 |
265 |
266 o 7: children: 1, tags: 0, file_adds: 1, ancestors: 1
266 o 7: children: 1, tags: 0, file_adds: 1, ancestors: 1
267
267
268 o 6: children: 0, tags: 0, file_adds: 0, ancestors: 7
268 o 6: children: 0, tags: 0, file_adds: 0, ancestors: 7
269 |\
269 |\
270 | o 5: children: 1, tags: 0, file_adds: 1, ancestors: 5
270 | o 5: children: 1, tags: 0, file_adds: 1, ancestors: 5
271 | |
271 | |
272 o | 4: children: 1, tags: 0, file_adds: 0, ancestors: 5
272 o | 4: children: 1, tags: 0, file_adds: 0, ancestors: 5
273 |/
273 |/
274 o 3: children: 2, tags: 0, file_adds: 0, ancestors: 4
274 o 3: children: 2, tags: 0, file_adds: 0, ancestors: 4
275 |
275 |
276 o 2: children: 1, tags: 0, file_adds: 1, ancestors: 3
276 o 2: children: 1, tags: 0, file_adds: 1, ancestors: 3
277 |
277 |
278 o 1: children: 1, tags: 0, file_adds: 1, ancestors: 2
278 o 1: children: 1, tags: 0, file_adds: 1, ancestors: 2
279 |
279 |
280 o 0: children: 1, tags: 0, file_adds: 1, ancestors: 1
280 o 0: children: 1, tags: 0, file_adds: 1, ancestors: 1
281
281
282
282
283 $ hg log -l1 -T '{termwidth|count}\n'
283 $ hg log -l1 -T '{termwidth|count}\n'
284 hg: parse error: not countable
284 hg: parse error: not countable
285 (template filter 'count' is not compatible with keyword 'termwidth')
285 (template filter 'count' is not compatible with keyword 'termwidth')
286 [255]
286 [255]
287
287
288 Upper/lower filters:
288 Upper/lower filters:
289
289
290 $ hg log -r0 --template '{branch|upper}\n'
290 $ hg log -r0 --template '{branch|upper}\n'
291 DEFAULT
291 DEFAULT
292 $ hg log -r0 --template '{author|lower}\n'
292 $ hg log -r0 --template '{author|lower}\n'
293 user name <user@hostname>
293 user name <user@hostname>
294 $ hg log -r0 --template '{date|upper}\n'
294 $ hg log -r0 --template '{date|upper}\n'
295 1000000.00
295 1000000.00
296
296
297 Add a commit that does all possible modifications at once
297 Add a commit that does all possible modifications at once
298
298
299 $ echo modify >> third
299 $ echo modify >> third
300 $ touch b
300 $ touch b
301 $ hg add b
301 $ hg add b
302 $ hg mv fourth fifth
302 $ hg mv fourth fifth
303 $ hg rm a
303 $ hg rm a
304 $ hg ci -m "Modify, add, remove, rename"
304 $ hg ci -m "Modify, add, remove, rename"
305
305
306 Pass generator object created by template function to filter
306 Pass generator object created by template function to filter
307
307
308 $ hg log -l 1 --template '{if(author, author)|user}\n'
308 $ hg log -l 1 --template '{if(author, author)|user}\n'
309 test
309 test
310
310
311 Test diff function:
311 Test diff function:
312
312
313 $ hg diff -c 8
313 $ hg diff -c 8
314 diff -r 29114dbae42b -r 95c24699272e fourth
314 diff -r 29114dbae42b -r 95c24699272e fourth
315 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
315 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
316 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
316 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
317 @@ -0,0 +1,1 @@
317 @@ -0,0 +1,1 @@
318 +second
318 +second
319 diff -r 29114dbae42b -r 95c24699272e second
319 diff -r 29114dbae42b -r 95c24699272e second
320 --- a/second Mon Jan 12 13:46:40 1970 +0000
320 --- a/second Mon Jan 12 13:46:40 1970 +0000
321 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
321 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
322 @@ -1,1 +0,0 @@
322 @@ -1,1 +0,0 @@
323 -second
323 -second
324 diff -r 29114dbae42b -r 95c24699272e third
324 diff -r 29114dbae42b -r 95c24699272e third
325 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
325 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
326 +++ b/third Wed Jan 01 10:01:00 2020 +0000
326 +++ b/third Wed Jan 01 10:01:00 2020 +0000
327 @@ -0,0 +1,1 @@
327 @@ -0,0 +1,1 @@
328 +third
328 +third
329
329
330 $ hg log -r 8 -T "{diff()}"
330 $ hg log -r 8 -T "{diff()}"
331 diff -r 29114dbae42b -r 95c24699272e fourth
331 diff -r 29114dbae42b -r 95c24699272e fourth
332 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
332 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
333 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
333 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
334 @@ -0,0 +1,1 @@
334 @@ -0,0 +1,1 @@
335 +second
335 +second
336 diff -r 29114dbae42b -r 95c24699272e second
336 diff -r 29114dbae42b -r 95c24699272e second
337 --- a/second Mon Jan 12 13:46:40 1970 +0000
337 --- a/second Mon Jan 12 13:46:40 1970 +0000
338 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
338 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
339 @@ -1,1 +0,0 @@
339 @@ -1,1 +0,0 @@
340 -second
340 -second
341 diff -r 29114dbae42b -r 95c24699272e third
341 diff -r 29114dbae42b -r 95c24699272e third
342 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
342 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
343 +++ b/third Wed Jan 01 10:01:00 2020 +0000
343 +++ b/third Wed Jan 01 10:01:00 2020 +0000
344 @@ -0,0 +1,1 @@
344 @@ -0,0 +1,1 @@
345 +third
345 +third
346
346
347 $ hg log -r 8 -T "{diff('glob:f*')}"
347 $ hg log -r 8 -T "{diff('glob:f*')}"
348 diff -r 29114dbae42b -r 95c24699272e fourth
348 diff -r 29114dbae42b -r 95c24699272e fourth
349 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
349 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
350 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
350 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
351 @@ -0,0 +1,1 @@
351 @@ -0,0 +1,1 @@
352 +second
352 +second
353
353
354 $ hg log -r 8 -T "{diff('', 'glob:f*')}"
354 $ hg log -r 8 -T "{diff('', 'glob:f*')}"
355 diff -r 29114dbae42b -r 95c24699272e second
355 diff -r 29114dbae42b -r 95c24699272e second
356 --- a/second Mon Jan 12 13:46:40 1970 +0000
356 --- a/second Mon Jan 12 13:46:40 1970 +0000
357 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
357 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
358 @@ -1,1 +0,0 @@
358 @@ -1,1 +0,0 @@
359 -second
359 -second
360 diff -r 29114dbae42b -r 95c24699272e third
360 diff -r 29114dbae42b -r 95c24699272e third
361 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
361 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
362 +++ b/third Wed Jan 01 10:01:00 2020 +0000
362 +++ b/third Wed Jan 01 10:01:00 2020 +0000
363 @@ -0,0 +1,1 @@
363 @@ -0,0 +1,1 @@
364 +third
364 +third
365
365
366 $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
366 $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
367 diff -r 29114dbae42b -r 95c24699272e fourth
367 diff -r 29114dbae42b -r 95c24699272e fourth
368 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
368 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
369 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
369 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
370 @@ -0,0 +1,1 @@
370 @@ -0,0 +1,1 @@
371 +second
371 +second
372
372
373 $ cd ..
373 $ cd ..
374
374
375 latesttag() function:
375 latesttag() function:
376
376
377 $ hg init latesttag
377 $ hg init latesttag
378 $ cd latesttag
378 $ cd latesttag
379
379
380 $ echo a > file
380 $ echo a > file
381 $ hg ci -Am a -d '0 0'
381 $ hg ci -Am a -d '0 0'
382 adding file
382 adding file
383
383
384 $ echo b >> file
384 $ echo b >> file
385 $ hg ci -m b -d '1 0'
385 $ hg ci -m b -d '1 0'
386
386
387 $ echo c >> head1
387 $ echo c >> head1
388 $ hg ci -Am h1c -d '2 0'
388 $ hg ci -Am h1c -d '2 0'
389 adding head1
389 adding head1
390
390
391 $ hg update -q 1
391 $ hg update -q 1
392 $ echo d >> head2
392 $ echo d >> head2
393 $ hg ci -Am h2d -d '3 0'
393 $ hg ci -Am h2d -d '3 0'
394 adding head2
394 adding head2
395 created new head
395 created new head
396
396
397 $ echo e >> head2
397 $ echo e >> head2
398 $ hg ci -m h2e -d '4 0'
398 $ hg ci -m h2e -d '4 0'
399
399
400 $ hg merge -q
400 $ hg merge -q
401 $ hg ci -m merge -d '5 -3600'
401 $ hg ci -m merge -d '5 -3600'
402
402
403 $ hg tag -r 1 -m t1 -d '6 0' t1
403 $ hg tag -r 1 -m t1 -d '6 0' t1
404 $ hg tag -r 2 -m t2 -d '7 0' t2
404 $ hg tag -r 2 -m t2 -d '7 0' t2
405 $ hg tag -r 3 -m t3 -d '8 0' t3
405 $ hg tag -r 3 -m t3 -d '8 0' t3
406 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
406 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
407 $ hg tag -r 5 -m t5 -d '9 0' t5
407 $ hg tag -r 5 -m t5 -d '9 0' t5
408 $ hg tag -r 3 -m at3 -d '10 0' at3
408 $ hg tag -r 3 -m at3 -d '10 0' at3
409
409
410 $ hg log -G --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
410 $ hg log -G --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
411 @ 11: t3, C: 9, D: 8
411 @ 11: t3, C: 9, D: 8
412 |
412 |
413 o 10: t3, C: 8, D: 7
413 o 10: t3, C: 8, D: 7
414 |
414 |
415 o 9: t3, C: 7, D: 6
415 o 9: t3, C: 7, D: 6
416 |
416 |
417 o 8: t3, C: 6, D: 5
417 o 8: t3, C: 6, D: 5
418 |
418 |
419 o 7: t3, C: 5, D: 4
419 o 7: t3, C: 5, D: 4
420 |
420 |
421 o 6: t3, C: 4, D: 3
421 o 6: t3, C: 4, D: 3
422 |
422 |
423 o 5: t3, C: 3, D: 2
423 o 5: t3, C: 3, D: 2
424 |\
424 |\
425 | o 4: t3, C: 1, D: 1
425 | o 4: t3, C: 1, D: 1
426 | |
426 | |
427 | o 3: t3, C: 0, D: 0
427 | o 3: t3, C: 0, D: 0
428 | |
428 | |
429 o | 2: t1, C: 1, D: 1
429 o | 2: t1, C: 1, D: 1
430 |/
430 |/
431 o 1: t1, C: 0, D: 0
431 o 1: t1, C: 0, D: 0
432 |
432 |
433 o 0: null, C: 1, D: 1
433 o 0: null, C: 1, D: 1
434
434
435
435
436 $ cd ..
436 $ cd ..
437
437
438 Test filter() empty values:
438 Test filter() empty values:
439
439
440 $ hg log -R a -r 1 -T '{filter(desc|splitlines) % "{line}\n"}'
440 $ hg log -R a -r 1 -T '{filter(desc|splitlines) % "{line}\n"}'
441 other 1
441 other 1
442 other 2
442 other 2
443 other 3
443 other 3
444 $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1) % "{ifeq(key, "a", "{value}\n")}")}'
444 $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1) % "{ifeq(key, "a", "{value}\n")}")}'
445 0
445 0
446
446
447 0 should not be falsy
447 0 should not be falsy
448
448
449 $ hg log -R a -r 0 -T '{filter(revset("0:2"))}\n'
449 $ hg log -R a -r 0 -T '{filter(revset("0:2"))}\n'
450 0 1 2
450 0 1 2
451
451
452 Test filter() by expression:
452 Test filter() by expression:
453
453
454 $ hg log -R a -r 1 -T '{filter(desc|splitlines, ifcontains("1", line, "t"))}\n'
454 $ hg log -R a -r 1 -T '{filter(desc|splitlines, ifcontains("1", line, "t"))}\n'
455 other 1
455 other 1
456 $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1), ifeq(key, "b", "t"))}\n'
456 $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1), ifeq(key, "b", "t"))}\n'
457 b=1
457 b=1
458
458
459 Test filter() shouldn't crash:
459 Test filter() shouldn't crash:
460
460
461 $ hg log -R a -r 0 -T '{filter(extras)}\n'
461 $ hg log -R a -r 0 -T '{filter(extras)}\n'
462 branch=default
462 branch=default
463 $ hg log -R a -r 0 -T '{filter(files)}\n'
463 $ hg log -R a -r 0 -T '{filter(files)}\n'
464 a
464 a
465
465
466 Test filter() unsupported arguments:
466 Test filter() unsupported arguments:
467
467
468 $ hg log -R a -r 0 -T '{filter()}\n'
468 $ hg log -R a -r 0 -T '{filter()}\n'
469 hg: parse error: filter expects one or two arguments
469 hg: parse error: filter expects one or two arguments
470 [255]
470 [255]
471 $ hg log -R a -r 0 -T '{filter(date)}\n'
471 $ hg log -R a -r 0 -T '{filter(date)}\n'
472 hg: parse error: date is not iterable
472 hg: parse error: date is not iterable
473 [255]
473 [255]
474 $ hg log -R a -r 0 -T '{filter(rev)}\n'
474 $ hg log -R a -r 0 -T '{filter(rev)}\n'
475 hg: parse error: 0 is not iterable
475 hg: parse error: 0 is not iterable
476 [255]
476 [255]
477 $ hg log -R a -r 0 -T '{filter(desc|firstline)}\n'
477 $ hg log -R a -r 0 -T '{filter(desc|firstline)}\n'
478 hg: parse error: 'line 1' is not filterable
478 hg: parse error: 'line 1' is not filterable
479 [255]
479 [255]
480 $ hg log -R a -r 0 -T '{filter(manifest)}\n'
480 $ hg log -R a -r 0 -T '{filter(manifest)}\n'
481 hg: parse error: '0:a0c8bcbbb45c' is not filterable
481 hg: parse error: '0:a0c8bcbbb45c' is not filterable
482 [255]
482 [255]
483 $ hg log -R a -r 0 -T '{filter(succsandmarkers)}\n'
483 $ hg log -R a -r 0 -T '{filter(succsandmarkers)}\n'
484 hg: parse error: not filterable without template
484 hg: parse error: not filterable without template
485 [255]
485 [255]
486 $ hg log -R a -r 0 -T '{filter(desc|splitlines % "{line}", "")}\n'
486 $ hg log -R a -r 0 -T '{filter(desc|splitlines % "{line}", "")}\n'
487 hg: parse error: not filterable by expression
487 hg: parse error: not filterable by expression
488 [255]
488 [255]
489
489
490 Test manifest/get() can be join()-ed as string, though it's silly:
490 Test manifest/get() can be join()-ed as string, though it's silly:
491
491
492 $ hg log -R latesttag -r tip -T '{join(manifest, ".")}\n'
492 $ hg log -R latesttag -r tip -T '{join(manifest, ".")}\n'
493 1.1.:.2.b.c.6.e.9.0.0.6.c.e.2
493 1.1.:.2.b.c.6.e.9.0.0.6.c.e.2
494 $ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), ".")}\n'
494 $ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), ".")}\n'
495 d.e.f.a.u.l.t
495 d.e.f.a.u.l.t
496
496
497 Test join() over string
497 Test join() over string
498
498
499 $ hg log -R latesttag -r tip -T '{join(rev|stringify, ".")}\n'
499 $ hg log -R latesttag -r tip -T '{join(rev|stringify, ".")}\n'
500 1.1
500 1.1
501
501
502 Test join() over uniterable
502 Test join() over uniterable
503
503
504 $ hg log -R latesttag -r tip -T '{join(rev, "")}\n'
504 $ hg log -R latesttag -r tip -T '{join(rev, "")}\n'
505 hg: parse error: 11 is not iterable
505 hg: parse error: 11 is not iterable
506 [255]
506 [255]
507
507
508 Test min/max of integers
508 Test min/max of integers
509
509
510 $ hg log -R latesttag -l1 -T '{min(revset("9:10"))}\n'
510 $ hg log -R latesttag -l1 -T '{min(revset("9:10"))}\n'
511 9
511 9
512 $ hg log -R latesttag -l1 -T '{max(revset("9:10"))}\n'
512 $ hg log -R latesttag -l1 -T '{max(revset("9:10"))}\n'
513 10
513 10
514
514
515 Test min/max over map operation:
515 Test min/max over map operation:
516
516
517 $ hg log -R latesttag -r3 -T '{min(tags % "{tag}")}\n'
517 $ hg log -R latesttag -r3 -T '{min(tags % "{tag}")}\n'
518 at3
518 at3
519 $ hg log -R latesttag -r3 -T '{max(tags % "{tag}")}\n'
519 $ hg log -R latesttag -r3 -T '{max(tags % "{tag}")}\n'
520 t3
520 t3
521
521
522 Test min/max of strings:
522 Test min/max of strings:
523
523
524 $ hg log -R latesttag -l1 -T '{min(desc)}\n'
524 $ hg log -R latesttag -l1 -T '{min(desc)}\n'
525 3
525 3
526 $ hg log -R latesttag -l1 -T '{max(desc)}\n'
526 $ hg log -R latesttag -l1 -T '{max(desc)}\n'
527 t
527 t
528
528
529 Test min/max of non-iterable:
529 Test min/max of non-iterable:
530
530
531 $ hg debugtemplate '{min(1)}'
531 $ hg debugtemplate '{min(1)}'
532 hg: parse error: 1 is not iterable
532 hg: parse error: 1 is not iterable
533 (min first argument should be an iterable)
533 (min first argument should be an iterable)
534 [255]
534 [255]
535 $ hg debugtemplate '{max(2)}'
535 $ hg debugtemplate '{max(2)}'
536 hg: parse error: 2 is not iterable
536 hg: parse error: 2 is not iterable
537 (max first argument should be an iterable)
537 (max first argument should be an iterable)
538 [255]
538 [255]
539
539
540 $ hg log -R latesttag -l1 -T '{min(date)}'
540 $ hg log -R latesttag -l1 -T '{min(date)}'
541 hg: parse error: date is not iterable
541 hg: parse error: date is not iterable
542 (min first argument should be an iterable)
542 (min first argument should be an iterable)
543 [255]
543 [255]
544 $ hg log -R latesttag -l1 -T '{max(date)}'
544 $ hg log -R latesttag -l1 -T '{max(date)}'
545 hg: parse error: date is not iterable
545 hg: parse error: date is not iterable
546 (max first argument should be an iterable)
546 (max first argument should be an iterable)
547 [255]
547 [255]
548
548
549 Test min/max of empty sequence:
549 Test min/max of empty sequence:
550
550
551 $ hg debugtemplate '{min("")}'
551 $ hg debugtemplate '{min("")}'
552 hg: parse error: empty string
552 hg: parse error: empty string
553 (min first argument should be an iterable)
553 (min first argument should be an iterable)
554 [255]
554 [255]
555 $ hg debugtemplate '{max("")}'
555 $ hg debugtemplate '{max("")}'
556 hg: parse error: empty string
556 hg: parse error: empty string
557 (max first argument should be an iterable)
557 (max first argument should be an iterable)
558 [255]
558 [255]
559 $ hg debugtemplate '{min(dict())}'
559 $ hg debugtemplate '{min(dict())}'
560 hg: parse error: empty sequence
560 hg: parse error: empty sequence
561 (min first argument should be an iterable)
561 (min first argument should be an iterable)
562 [255]
562 [255]
563 $ hg debugtemplate '{max(dict())}'
563 $ hg debugtemplate '{max(dict())}'
564 hg: parse error: empty sequence
564 hg: parse error: empty sequence
565 (max first argument should be an iterable)
565 (max first argument should be an iterable)
566 [255]
566 [255]
567 $ hg debugtemplate '{min(dict() % "")}'
567 $ hg debugtemplate '{min(dict() % "")}'
568 hg: parse error: empty sequence
568 hg: parse error: empty sequence
569 (min first argument should be an iterable)
569 (min first argument should be an iterable)
570 [255]
570 [255]
571 $ hg debugtemplate '{max(dict() % "")}'
571 $ hg debugtemplate '{max(dict() % "")}'
572 hg: parse error: empty sequence
572 hg: parse error: empty sequence
573 (max first argument should be an iterable)
573 (max first argument should be an iterable)
574 [255]
574 [255]
575
575
576 Test min/max of if() result
576 Test min/max of if() result
577
577
578 $ cd latesttag
578 $ cd latesttag
579 $ hg log -l1 -T '{min(if(true, revset("9:10"), ""))}\n'
579 $ hg log -l1 -T '{min(if(true, revset("9:10"), ""))}\n'
580 9
580 9
581 $ hg log -l1 -T '{max(if(false, "", revset("9:10")))}\n'
581 $ hg log -l1 -T '{max(if(false, "", revset("9:10")))}\n'
582 10
582 10
583 $ hg log -l1 -T '{min(ifcontains("a", "aa", revset("9:10"), ""))}\n'
583 $ hg log -l1 -T '{min(ifcontains("a", "aa", revset("9:10"), ""))}\n'
584 9
584 9
585 $ hg log -l1 -T '{max(ifcontains("a", "bb", "", revset("9:10")))}\n'
585 $ hg log -l1 -T '{max(ifcontains("a", "bb", "", revset("9:10")))}\n'
586 10
586 10
587 $ hg log -l1 -T '{min(ifeq(0, 0, revset("9:10"), ""))}\n'
587 $ hg log -l1 -T '{min(ifeq(0, 0, revset("9:10"), ""))}\n'
588 9
588 9
589 $ hg log -l1 -T '{max(ifeq(0, 1, "", revset("9:10")))}\n'
589 $ hg log -l1 -T '{max(ifeq(0, 1, "", revset("9:10")))}\n'
590 10
590 10
591 $ cd ..
591 $ cd ..
592
592
593 Test laziness of if() then/else clause
593 Test laziness of if() then/else clause
594
594
595 $ hg debugtemplate '{count(0)}'
595 $ hg debugtemplate '{count(0)}'
596 hg: parse error: not countable
596 hg: parse error: not countable
597 (incompatible use of template filter 'count')
597 (incompatible use of template filter 'count')
598 [255]
598 [255]
599 $ hg debugtemplate '{if(true, "", count(0))}'
599 $ hg debugtemplate '{if(true, "", count(0))}'
600 $ hg debugtemplate '{if(false, count(0), "")}'
600 $ hg debugtemplate '{if(false, count(0), "")}'
601 $ hg debugtemplate '{ifcontains("a", "aa", "", count(0))}'
601 $ hg debugtemplate '{ifcontains("a", "aa", "", count(0))}'
602 $ hg debugtemplate '{ifcontains("a", "bb", count(0), "")}'
602 $ hg debugtemplate '{ifcontains("a", "bb", count(0), "")}'
603 $ hg debugtemplate '{ifeq(0, 0, "", count(0))}'
603 $ hg debugtemplate '{ifeq(0, 0, "", count(0))}'
604 $ hg debugtemplate '{ifeq(0, 1, count(0), "")}'
604 $ hg debugtemplate '{ifeq(0, 1, count(0), "")}'
605
605
606 Test the sub function of templating for expansion:
606 Test the sub function of templating for expansion:
607
607
608 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
608 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
609 xx
609 xx
610
610
611 $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
611 $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
612 hg: parse error: sub got an invalid pattern: [
612 hg: parse error: sub got an invalid pattern: [
613 [255]
613 [255]
614 $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
614 $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
615 hg: parse error: sub got an invalid replacement: \1
615 hg: parse error: sub got an invalid replacement: \1
616 [255]
616 [255]
617
617
618 Test the strip function with chars specified:
618 Test the strip function with chars specified:
619
619
620 $ hg log -R latesttag --template '{desc}\n'
620 $ hg log -R latesttag --template '{desc}\n'
621 at3
621 at3
622 t5
622 t5
623 t4
623 t4
624 t3
624 t3
625 t2
625 t2
626 t1
626 t1
627 merge
627 merge
628 h2e
628 h2e
629 h2d
629 h2d
630 h1c
630 h1c
631 b
631 b
632 a
632 a
633
633
634 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
634 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
635 at3
635 at3
636 5
636 5
637 4
637 4
638 3
638 3
639 2
639 2
640 1
640 1
641 merg
641 merg
642 h2
642 h2
643 h2d
643 h2d
644 h1c
644 h1c
645 b
645 b
646 a
646 a
647
647
648 Test date format:
648 Test date format:
649
649
650 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
650 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
651 date: 70 01 01 10 +0000
651 date: 70 01 01 10 +0000
652 date: 70 01 01 09 +0000
652 date: 70 01 01 09 +0000
653 date: 70 01 01 04 +0000
653 date: 70 01 01 04 +0000
654 date: 70 01 01 08 +0000
654 date: 70 01 01 08 +0000
655 date: 70 01 01 07 +0000
655 date: 70 01 01 07 +0000
656 date: 70 01 01 06 +0000
656 date: 70 01 01 06 +0000
657 date: 70 01 01 05 +0100
657 date: 70 01 01 05 +0100
658 date: 70 01 01 04 +0000
658 date: 70 01 01 04 +0000
659 date: 70 01 01 03 +0000
659 date: 70 01 01 03 +0000
660 date: 70 01 01 02 +0000
660 date: 70 01 01 02 +0000
661 date: 70 01 01 01 +0000
661 date: 70 01 01 01 +0000
662 date: 70 01 01 00 +0000
662 date: 70 01 01 00 +0000
663
663
664 Test invalid date:
664 Test invalid date:
665
665
666 $ hg log -R latesttag -T '{date(rev)}\n'
666 $ hg log -R latesttag -T '{date(rev)}\n'
667 hg: parse error: date expects a date information
667 hg: parse error: date expects a date information
668 [255]
668 [255]
669
669
670 Set up repository containing template fragments in commit metadata:
670 Set up repository containing template fragments in commit metadata:
671
671
672 $ hg init r
672 $ hg init r
673 $ cd r
673 $ cd r
674 $ echo a > a
674 $ echo a > a
675 $ hg ci -Am '{rev}'
675 $ hg ci -Am '{rev}'
676 adding a
676 adding a
677
677
678 $ hg branch -q 'text.{rev}'
678 $ hg branch -q 'text.{rev}'
679 $ echo aa >> aa
679 $ echo aa >> aa
680 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
680 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
681
681
682 color effect can be specified without quoting:
682 color effect can be specified without quoting:
683
683
684 $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
684 $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
685 \x1b[0;31mtext\x1b[0m (esc)
685 \x1b[0;31mtext\x1b[0m (esc)
686
686
687 color effects can be nested (issue5413)
687 color effects can be nested (issue5413)
688
688
689 $ hg debugtemplate --color=always \
689 $ hg debugtemplate --color=always \
690 > '{label(red, "red{label(magenta, "ma{label(cyan, "cyan")}{label(yellow, "yellow")}genta")}")}\n'
690 > '{label(red, "red{label(magenta, "ma{label(cyan, "cyan")}{label(yellow, "yellow")}genta")}")}\n'
691 \x1b[0;31mred\x1b[0;35mma\x1b[0;36mcyan\x1b[0m\x1b[0;31m\x1b[0;35m\x1b[0;33myellow\x1b[0m\x1b[0;31m\x1b[0;35mgenta\x1b[0m (esc)
691 \x1b[0;31mred\x1b[0;35mma\x1b[0;36mcyan\x1b[0m\x1b[0;31m\x1b[0;35m\x1b[0;33myellow\x1b[0m\x1b[0;31m\x1b[0;35mgenta\x1b[0m (esc)
692
692
693 pad() should interact well with color codes (issue5416)
693 pad() should interact well with color codes (issue5416)
694
694
695 $ hg debugtemplate --color=always \
695 $ hg debugtemplate --color=always \
696 > '{pad(label(red, "red"), 5, label(cyan, "-"))}\n'
696 > '{pad(label(red, "red"), 5, label(cyan, "-"))}\n'
697 \x1b[0;31mred\x1b[0m\x1b[0;36m-\x1b[0m\x1b[0;36m-\x1b[0m (esc)
697 \x1b[0;31mred\x1b[0m\x1b[0;36m-\x1b[0m\x1b[0;36m-\x1b[0m (esc)
698
698
699 label should be no-op if color is disabled:
699 label should be no-op if color is disabled:
700
700
701 $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
701 $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
702 text
702 text
703 $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
703 $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
704 text
704 text
705
705
706 Test branches inside if statement:
706 Test branches inside if statement:
707
707
708 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
708 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
709 no
709 no
710
710
711 Test dict constructor:
711 Test dict constructor:
712
712
713 $ hg log -r 0 -T '{dict(y=node|short, x=rev)}\n'
713 $ hg log -r 0 -T '{dict(y=node|short, x=rev)}\n'
714 y=f7769ec2ab97 x=0
714 y=f7769ec2ab97 x=0
715 $ hg log -r 0 -T '{dict(x=rev, y=node|short) % "{key}={value}\n"}'
715 $ hg log -r 0 -T '{dict(x=rev, y=node|short) % "{key}={value}\n"}'
716 x=0
716 x=0
717 y=f7769ec2ab97
717 y=f7769ec2ab97
718 $ hg log -r 0 -T '{dict(x=rev, y=node|short)|json}\n'
718 $ hg log -r 0 -T '{dict(x=rev, y=node|short)|json}\n'
719 {"x": 0, "y": "f7769ec2ab97"}
719 {"x": 0, "y": "f7769ec2ab97"}
720 $ hg log -r 0 -T '{dict()|json}\n'
720 $ hg log -r 0 -T '{dict()|json}\n'
721 {}
721 {}
722
722
723 $ hg log -r 0 -T '{dict(rev, node=node|short)}\n'
723 $ hg log -r 0 -T '{dict(rev, node=node|short)}\n'
724 rev=0 node=f7769ec2ab97
724 rev=0 node=f7769ec2ab97
725 $ hg log -r 0 -T '{dict(rev, node|short)}\n'
725 $ hg log -r 0 -T '{dict(rev, node|short)}\n'
726 rev=0 node=f7769ec2ab97
726 rev=0 node=f7769ec2ab97
727
727
728 $ hg log -r 0 -T '{dict(rev, rev=rev)}\n'
728 $ hg log -r 0 -T '{dict(rev, rev=rev)}\n'
729 hg: parse error: duplicated dict key 'rev' inferred
729 hg: parse error: duplicated dict key 'rev' inferred
730 [255]
730 [255]
731 $ hg log -r 0 -T '{dict(node, node|short)}\n'
731 $ hg log -r 0 -T '{dict(node, node|short)}\n'
732 hg: parse error: duplicated dict key 'node' inferred
732 hg: parse error: duplicated dict key 'node' inferred
733 [255]
733 [255]
734 $ hg log -r 0 -T '{dict(1 + 2)}'
734 $ hg log -r 0 -T '{dict(1 + 2)}'
735 hg: parse error: dict key cannot be inferred
735 hg: parse error: dict key cannot be inferred
736 [255]
736 [255]
737
737
738 $ hg log -r 0 -T '{dict(x=rev, x=node)}'
738 $ hg log -r 0 -T '{dict(x=rev, x=node)}'
739 hg: parse error: dict got multiple values for keyword argument 'x'
739 hg: parse error: dict got multiple values for keyword argument 'x'
740 [255]
740 [255]
741
741
742 Test get function:
742 Test get function:
743
743
744 $ hg log -r 0 --template '{get(extras, "branch")}\n'
744 $ hg log -r 0 --template '{get(extras, "branch")}\n'
745 default
745 default
746 $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
746 $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
747 default
747 default
748 $ hg log -r 0 --template '{get(files, "should_fail")}\n'
748 $ hg log -r 0 --template '{get(files, "should_fail")}\n'
749 hg: parse error: not a dictionary
749 hg: parse error: not a dictionary
750 (get() expects a dict as first argument)
750 (get() expects a dict as first argument)
751 [255]
751 [255]
752
752
753 Test json filter applied to wrapped object:
753 Test json filter applied to wrapped object:
754
754
755 $ hg log -r0 -T '{files|json}\n'
755 $ hg log -r0 -T '{files|json}\n'
756 ["a"]
756 ["a"]
757 $ hg log -r0 -T '{extras|json}\n'
757 $ hg log -r0 -T '{extras|json}\n'
758 {"branch": "default"}
758 {"branch": "default"}
759 $ hg log -r0 -T '{date|json}\n'
759 $ hg log -r0 -T '{date|json}\n'
760 [0, 0]
760 [0, 0]
761
761
762 Test json filter applied to map result:
762 Test json filter applied to map result:
763
763
764 $ hg log -r0 -T '{json(extras % "{key}")}\n'
764 $ hg log -r0 -T '{json(extras % "{key}")}\n'
765 ["branch"]
765 ["branch"]
766
766
767 Test localdate(date, tz) function:
767 Test localdate(date, tz) function:
768
768
769 $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
769 $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
770 1970-01-01 09:00 +0900
770 1970-01-01 09:00 +0900
771 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
771 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
772 1970-01-01 00:00 +0000
772 1970-01-01 00:00 +0000
773 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
773 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
774 hg: parse error: localdate expects a timezone
774 hg: parse error: localdate expects a timezone
775 [255]
775 [255]
776 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
776 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
777 1970-01-01 02:00 +0200
777 1970-01-01 02:00 +0200
778 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
778 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
779 1970-01-01 00:00 +0000
779 1970-01-01 00:00 +0000
780 $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
780 $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
781 1970-01-01 00:00 +0000
781 1970-01-01 00:00 +0000
782 $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
782 $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
783 hg: parse error: localdate expects a timezone
783 hg: parse error: localdate expects a timezone
784 [255]
784 [255]
785 $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
785 $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
786 hg: parse error: localdate expects a timezone
786 hg: parse error: localdate expects a timezone
787 [255]
787 [255]
788
788
789 Test shortest(node) function:
789 Test shortest(node) function:
790
790
791 $ echo b > b
791 $ echo b > b
792 $ hg ci -qAm b
792 $ hg ci -qAm b
793 $ hg log --template '{shortest(node)}\n'
793 $ hg log --template '{shortest(node)}\n'
794 e777
794 e777
795 bcc7
795 bcc7
796 f776
796 f776
797 $ hg log --template '{shortest(node, 10)}\n'
797 $ hg log --template '{shortest(node, 10)}\n'
798 e777603221
798 e777603221
799 bcc7ff960b
799 bcc7ff960b
800 f7769ec2ab
800 f7769ec2ab
801 $ hg log --template '{node|shortest}\n' -l1
801 $ hg log --template '{node|shortest}\n' -l1
802 e777
802 e777
803
803
804 $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
804 $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
805 f7769ec2ab
805 f7769ec2ab
806 $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
806 $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
807 hg: parse error: shortest() expects an integer minlength
807 hg: parse error: shortest() expects an integer minlength
808 [255]
808 [255]
809
809
810 $ hg log -r 'wdir()' -T '{node|shortest}\n'
810 $ hg log -r 'wdir()' -T '{node|shortest}\n'
811 ffff
811 ffff
812
812
813 $ hg log --template '{shortest("f")}\n' -l1
813 $ hg log --template '{shortest("f")}\n' -l1
814 f
814 f
815
815
816 $ hg log --template '{shortest("0123456789012345678901234567890123456789")}\n' -l1
816 $ hg log --template '{shortest("0123456789012345678901234567890123456789")}\n' -l1
817 0123456789012345678901234567890123456789
817 0123456789012345678901234567890123456789
818
818
819 $ hg log --template '{shortest("01234567890123456789012345678901234567890123456789")}\n' -l1
819 $ hg log --template '{shortest("01234567890123456789012345678901234567890123456789")}\n' -l1
820 01234567890123456789012345678901234567890123456789
820 01234567890123456789012345678901234567890123456789
821
821
822 $ hg log --template '{shortest("not a hex string")}\n' -l1
822 $ hg log --template '{shortest("not a hex string")}\n' -l1
823 not a hex string
823 not a hex string
824
824
825 $ hg log --template '{shortest("not a hex string, but it'\''s 40 bytes long")}\n' -l1
825 $ hg log --template '{shortest("not a hex string, but it'\''s 40 bytes long")}\n' -l1
826 not a hex string, but it's 40 bytes long
826 not a hex string, but it's 40 bytes long
827
827
828 $ hg log --template '{shortest("ffffffffffffffffffffffffffffffffffffffff")}\n' -l1
828 $ hg log --template '{shortest("ffffffffffffffffffffffffffffffffffffffff")}\n' -l1
829 ffff
829 ffff
830
830
831 $ hg log --template '{shortest("fffffff")}\n' -l1
831 $ hg log --template '{shortest("fffffff")}\n' -l1
832 ffff
832 ffff
833
833
834 $ hg log --template '{shortest("ff")}\n' -l1
834 $ hg log --template '{shortest("ff")}\n' -l1
835 ffff
835 ffff
836
836
837 $ cd ..
837 $ cd ..
838
838
839 Test shortest(node) with the repo having short hash collision:
839 Test shortest(node) with the repo having short hash collision:
840
840
841 $ hg init hashcollision
841 $ hg init hashcollision
842 $ cd hashcollision
842 $ cd hashcollision
843 $ cat <<EOF >> .hg/hgrc
843 $ cat <<EOF >> .hg/hgrc
844 > [experimental]
844 > [experimental]
845 > evolution.createmarkers=True
845 > evolution.createmarkers=True
846 > EOF
846 > EOF
847 $ echo 0 > a
847 $ echo 0 > a
848 $ hg ci -qAm 0
848 $ hg ci -qAm 0
849 $ for i in 17 129 248 242 480 580 617 1057 2857 4025; do
849 $ for i in 17 129 248 242 480 580 617 1057 2857 4025; do
850 > hg up -q 0
850 > hg up -q 0
851 > echo $i > a
851 > echo $i > a
852 > hg ci -qm $i
852 > hg ci -qm $i
853 > done
853 > done
854 $ hg up -q null
854 $ hg up -q null
855 $ hg log -r0: -T '{rev}:{node}\n'
855 $ hg log -r0: -T '{rev}:{node}\n'
856 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
856 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
857 1:11424df6dc1dd4ea255eae2b58eaca7831973bbc
857 1:11424df6dc1dd4ea255eae2b58eaca7831973bbc
858 2:11407b3f1b9c3e76a79c1ec5373924df096f0499
858 2:11407b3f1b9c3e76a79c1ec5373924df096f0499
859 3:11dd92fe0f39dfdaacdaa5f3997edc533875cfc4
859 3:11dd92fe0f39dfdaacdaa5f3997edc533875cfc4
860 4:10776689e627b465361ad5c296a20a487e153ca4
860 4:10776689e627b465361ad5c296a20a487e153ca4
861 5:a00be79088084cb3aff086ab799f8790e01a976b
861 5:a00be79088084cb3aff086ab799f8790e01a976b
862 6:a0b0acd79b4498d0052993d35a6a748dd51d13e6
862 6:a0b0acd79b4498d0052993d35a6a748dd51d13e6
863 7:a0457b3450b8e1b778f1163b31a435802987fe5d
863 7:a0457b3450b8e1b778f1163b31a435802987fe5d
864 8:c56256a09cd28e5764f32e8e2810d0f01e2e357a
864 8:c56256a09cd28e5764f32e8e2810d0f01e2e357a
865 9:c5623987d205cd6d9d8389bfc40fff9dbb670b48
865 9:c5623987d205cd6d9d8389bfc40fff9dbb670b48
866 10:c562ddd9c94164376c20b86b0b4991636a3bf84f
866 10:c562ddd9c94164376c20b86b0b4991636a3bf84f
867 $ hg debugobsolete a00be79088084cb3aff086ab799f8790e01a976b
867 $ hg debugobsolete a00be79088084cb3aff086ab799f8790e01a976b
868 obsoleted 1 changesets
868 obsoleted 1 changesets
869 $ hg debugobsolete c5623987d205cd6d9d8389bfc40fff9dbb670b48
869 $ hg debugobsolete c5623987d205cd6d9d8389bfc40fff9dbb670b48
870 obsoleted 1 changesets
870 obsoleted 1 changesets
871 $ hg debugobsolete c562ddd9c94164376c20b86b0b4991636a3bf84f
871 $ hg debugobsolete c562ddd9c94164376c20b86b0b4991636a3bf84f
872 obsoleted 1 changesets
872 obsoleted 1 changesets
873
873
874 nodes starting with '11' (we don't have the revision number '11' though)
874 nodes starting with '11' (we don't have the revision number '11' though)
875
875
876 $ hg log -r 1:3 -T '{rev}:{shortest(node, 0)}\n'
876 $ hg log -r 1:3 -T '{rev}:{shortest(node, 0)}\n'
877 1:1142
877 1:1142
878 2:1140
878 2:1140
879 3:11d
879 3:11d
880
880
881 '5:a00' is hidden, but still we have two nodes starting with 'a0'
881 '5:a00' is hidden, but still we have two nodes starting with 'a0'
882
882
883 $ hg log -r 6:7 -T '{rev}:{shortest(node, 0)}\n'
883 $ hg log -r 6:7 -T '{rev}:{shortest(node, 0)}\n'
884 6:a0b
884 6:a0b
885 7:a04
885 7:a04
886
886
887 node '10' conflicts with the revision number '10' even if it is hidden
887 node '10' conflicts with the revision number '10' even if it is hidden
888 (we could exclude hidden revision numbers, but currently we don't)
888 (we could exclude hidden revision numbers, but currently we don't)
889
889
890 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n'
890 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n'
891 4:107
891 4:107
892 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
892 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
893 4:107
893 4:107
894
894
895 $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n'
895 $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n'
896 4:x10
896 4:x10
897 $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
897 $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
898 4:x10
898 4:x10
899
899
900 node 'c562' should be unique if the other 'c562' nodes are hidden
900 node 'c562' should be unique if the other 'c562' nodes are hidden
901 (but we don't try the slow path to filter out hidden nodes for now)
901 (but we don't try the slow path to filter out hidden nodes for now)
902
902
903 $ hg log -r 8 -T '{rev}:{node|shortest}\n'
903 $ hg log -r 8 -T '{rev}:{node|shortest}\n'
904 8:c5625
904 8:c5625
905 $ hg log -r 8:10 -T '{rev}:{node|shortest}\n' --hidden
905 $ hg log -r 8:10 -T '{rev}:{node|shortest}\n' --hidden
906 8:c5625
906 8:c5625
907 9:c5623
907 9:c5623
908 10:c562d
908 10:c562d
909
909
910 $ cd ..
910 $ cd ..
911
911
912 Test pad function
912 Test pad function
913
913
914 $ cd r
914 $ cd r
915
915
916 $ hg log --template '{pad(rev, 20)} {author|user}\n'
916 $ hg log --template '{pad(rev, 20)} {author|user}\n'
917 2 test
917 2 test
918 1 {node|short}
918 1 {node|short}
919 0 test
919 0 test
920
920
921 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
921 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
922 2 test
922 2 test
923 1 {node|short}
923 1 {node|short}
924 0 test
924 0 test
925
925
926 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
926 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
927 2------------------- test
927 2------------------- test
928 1------------------- {node|short}
928 1------------------- {node|short}
929 0------------------- test
929 0------------------- test
930
930
931 Test template string in pad function
931 Test template string in pad function
932
932
933 $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
933 $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
934 {0} test
934 {0} test
935
935
936 $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
936 $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
937 \{rev} test
937 \{rev} test
938
938
939 Test width argument passed to pad function
939 Test width argument passed to pad function
940
940
941 $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
941 $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
942 0 test
942 0 test
943 $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
943 $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
944 hg: parse error: pad() expects an integer width
944 hg: parse error: pad() expects an integer width
945 [255]
945 [255]
946
946
947 Test invalid fillchar passed to pad function
947 Test invalid fillchar passed to pad function
948
948
949 $ hg log -r 0 -T '{pad(rev, 10, "")}\n'
949 $ hg log -r 0 -T '{pad(rev, 10, "")}\n'
950 hg: parse error: pad() expects a single fill character
950 hg: parse error: pad() expects a single fill character
951 [255]
951 [255]
952 $ hg log -r 0 -T '{pad(rev, 10, "--")}\n'
952 $ hg log -r 0 -T '{pad(rev, 10, "--")}\n'
953 hg: parse error: pad() expects a single fill character
953 hg: parse error: pad() expects a single fill character
954 [255]
954 [255]
955
955
956 Test boolean argument passed to pad function
956 Test boolean argument passed to pad function
957
957
958 no crash
958 no crash
959
959
960 $ hg log -r 0 -T '{pad(rev, 10, "-", "f{"oo"}")}\n'
960 $ hg log -r 0 -T '{pad(rev, 10, "-", "f{"oo"}")}\n'
961 ---------0
961 ---------0
962
962
963 string/literal
963 string/literal
964
964
965 $ hg log -r 0 -T '{pad(rev, 10, "-", "false")}\n'
965 $ hg log -r 0 -T '{pad(rev, 10, "-", "false")}\n'
966 ---------0
966 ---------0
967 $ hg log -r 0 -T '{pad(rev, 10, "-", false)}\n'
967 $ hg log -r 0 -T '{pad(rev, 10, "-", false)}\n'
968 0---------
968 0---------
969 $ hg log -r 0 -T '{pad(rev, 10, "-", "")}\n'
969 $ hg log -r 0 -T '{pad(rev, 10, "-", "")}\n'
970 0---------
970 0---------
971
971
972 unknown keyword is evaluated to ''
972 unknown keyword is evaluated to ''
973
973
974 $ hg log -r 0 -T '{pad(rev, 10, "-", unknownkeyword)}\n'
974 $ hg log -r 0 -T '{pad(rev, 10, "-", unknownkeyword)}\n'
975 0---------
975 0---------
976
976
977 Test separate function
977 Test separate function
978
978
979 $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
979 $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
980 a-b-c
980 a-b-c
981 $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
981 $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
982 0:f7769ec2ab97 test default
982 0:f7769ec2ab97 test default
983 $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
983 $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
984 a \x1b[0;31mb\x1b[0m c d (esc)
984 a \x1b[0;31mb\x1b[0m c d (esc)
985
985
986 Test boolean expression/literal passed to if function
986 Test boolean expression/literal passed to if function
987
987
988 $ hg log -r 0 -T '{if(rev, "rev 0 is True")}\n'
988 $ hg log -r 0 -T '{if(rev, "rev 0 is True")}\n'
989 rev 0 is True
989 rev 0 is True
990 $ hg log -r 0 -T '{if(0, "literal 0 is True as well")}\n'
990 $ hg log -r 0 -T '{if(0, "literal 0 is True as well")}\n'
991 literal 0 is True as well
991 literal 0 is True as well
992 $ hg log -r 0 -T '{if(min(revset(r"0")), "0 of hybriditem is also True")}\n'
992 $ hg log -r 0 -T '{if(min(revset(r"0")), "0 of hybriditem is also True")}\n'
993 0 of hybriditem is also True
993 0 of hybriditem is also True
994 $ hg log -r 0 -T '{if("", "", "empty string is False")}\n'
994 $ hg log -r 0 -T '{if("", "", "empty string is False")}\n'
995 empty string is False
995 empty string is False
996 $ hg log -r 0 -T '{if(revset(r"0 - 0"), "", "empty list is False")}\n'
996 $ hg log -r 0 -T '{if(revset(r"0 - 0"), "", "empty list is False")}\n'
997 empty list is False
997 empty list is False
998 $ hg log -r 0 -T '{if(revset(r"0"), "non-empty list is True")}\n'
998 $ hg log -r 0 -T '{if(revset(r"0"), "non-empty list is True")}\n'
999 non-empty list is True
999 non-empty list is True
1000 $ hg log -r 0 -T '{if(revset(r"0") % "", "list of empty strings is True")}\n'
1000 $ hg log -r 0 -T '{if(revset(r"0") % "", "list of empty strings is True")}\n'
1001 list of empty strings is True
1001 list of empty strings is True
1002 $ hg log -r 0 -T '{if(true, "true is True")}\n'
1002 $ hg log -r 0 -T '{if(true, "true is True")}\n'
1003 true is True
1003 true is True
1004 $ hg log -r 0 -T '{if(false, "", "false is False")}\n'
1004 $ hg log -r 0 -T '{if(false, "", "false is False")}\n'
1005 false is False
1005 false is False
1006 $ hg log -r 0 -T '{if("false", "non-empty string is True")}\n'
1006 $ hg log -r 0 -T '{if("false", "non-empty string is True")}\n'
1007 non-empty string is True
1007 non-empty string is True
1008
1008
1009 Test ifcontains function
1009 Test ifcontains function
1010
1010
1011 $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
1011 $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
1012 2 is in the string
1012 2 is in the string
1013 1 is not
1013 1 is not
1014 0 is in the string
1014 0 is in the string
1015
1015
1016 $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
1016 $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
1017 2 is in the string
1017 2 is in the string
1018 1 is not
1018 1 is not
1019 0 is in the string
1019 0 is in the string
1020
1020
1021 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
1021 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
1022 2 did not add a
1022 2 did not add a
1023 1 did not add a
1023 1 did not add a
1024 0 added a
1024 0 added a
1025
1025
1026 $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
1026 $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
1027 2 is parent of 1
1027 2 is parent of 1
1028 1
1028 1
1029 0
1029 0
1030
1030
1031 $ hg log -l1 -T '{ifcontains("branch", extras, "t", "f")}\n'
1031 $ hg log -l1 -T '{ifcontains("branch", extras, "t", "f")}\n'
1032 t
1032 t
1033 $ hg log -l1 -T '{ifcontains("branch", extras % "{key}", "t", "f")}\n'
1033 $ hg log -l1 -T '{ifcontains("branch", extras % "{key}", "t", "f")}\n'
1034 t
1034 t
1035 $ hg log -l1 -T '{ifcontains("branc", extras % "{key}", "t", "f")}\n'
1035 $ hg log -l1 -T '{ifcontains("branc", extras % "{key}", "t", "f")}\n'
1036 f
1036 f
1037 $ hg log -l1 -T '{ifcontains("branc", stringify(extras % "{key}"), "t", "f")}\n'
1037 $ hg log -l1 -T '{ifcontains("branc", stringify(extras % "{key}"), "t", "f")}\n'
1038 t
1038 t
1039
1039
1040 Test revset function
1040 Test revset function
1041
1041
1042 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
1042 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
1043 2 current rev
1043 2 current rev
1044 1 not current rev
1044 1 not current rev
1045 0 not current rev
1045 0 not current rev
1046
1046
1047 $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
1047 $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
1048 2 match rev
1048 2 match rev
1049 1 match rev
1049 1 match rev
1050 0 not match rev
1050 0 not match rev
1051
1051
1052 $ hg log -T '{ifcontains(desc, revset(":"), "", "type not match")}\n' -l1
1052 $ hg log -T '{ifcontains(desc, revset(":"), "", "type not match")}\n' -l1
1053 type not match
1053 type not match
1054
1054
1055 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
1055 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
1056 2 Parents: 1
1056 2 Parents: 1
1057 1 Parents: 0
1057 1 Parents: 0
1058 0 Parents:
1058 0 Parents:
1059
1059
1060 $ cat >> .hg/hgrc <<EOF
1060 $ cat >> .hg/hgrc <<EOF
1061 > [revsetalias]
1061 > [revsetalias]
1062 > myparents(\$1) = parents(\$1)
1062 > myparents(\$1) = parents(\$1)
1063 > EOF
1063 > EOF
1064 $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
1064 $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
1065 2 Parents: 1
1065 2 Parents: 1
1066 1 Parents: 0
1066 1 Parents: 0
1067 0 Parents:
1067 0 Parents:
1068
1068
1069 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
1069 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
1070 Rev: 2
1070 Rev: 2
1071 Ancestor: 0
1071 Ancestor: 0
1072 Ancestor: 1
1072 Ancestor: 1
1073 Ancestor: 2
1073 Ancestor: 2
1074
1074
1075 Rev: 1
1075 Rev: 1
1076 Ancestor: 0
1076 Ancestor: 0
1077 Ancestor: 1
1077 Ancestor: 1
1078
1078
1079 Rev: 0
1079 Rev: 0
1080 Ancestor: 0
1080 Ancestor: 0
1081
1081
1082 $ hg log --template '{revset("TIP"|lower)}\n' -l1
1082 $ hg log --template '{revset("TIP"|lower)}\n' -l1
1083 2
1083 2
1084
1084
1085 $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
1085 $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
1086 2
1086 2
1087
1087
1088 a list template is evaluated for each item of revset/parents
1088 a list template is evaluated for each item of revset/parents
1089
1089
1090 $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
1090 $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
1091 2 p: 1:bcc7ff960b8e
1091 2 p: 1:bcc7ff960b8e
1092 1 p: 0:f7769ec2ab97
1092 1 p: 0:f7769ec2ab97
1093 0 p:
1093 0 p:
1094
1094
1095 $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
1095 $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
1096 2 p: 1:bcc7ff960b8e -1:000000000000
1096 2 p: 1:bcc7ff960b8e -1:000000000000
1097 1 p: 0:f7769ec2ab97 -1:000000000000
1097 1 p: 0:f7769ec2ab97 -1:000000000000
1098 0 p: -1:000000000000 -1:000000000000
1098 0 p: -1:000000000000 -1:000000000000
1099
1099
1100 therefore, 'revcache' should be recreated for each rev
1100 therefore, 'revcache' should be recreated for each rev
1101
1101
1102 $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
1102 $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
1103 2 aa b
1103 2 aa b
1104 p
1104 p
1105 1
1105 1
1106 p a
1106 p a
1107 0 a
1107 0 a
1108 p
1108 p
1109
1109
1110 $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
1110 $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
1111 2 aa b
1111 2 aa b
1112 p
1112 p
1113 1
1113 1
1114 p a
1114 p a
1115 0 a
1115 0 a
1116 p
1116 p
1117
1117
1118 a revset item must be evaluated as an integer revision, not an offset from tip
1118 a revset item must be evaluated as an integer revision, not an offset from tip
1119
1119
1120 $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
1120 $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
1121 -1:000000000000
1121 -1:000000000000
1122 $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
1122 $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
1123 -1:000000000000
1123 -1:000000000000
1124
1124
1125 join() should pick '{rev}' from revset items:
1125 join() should pick '{rev}' from revset items:
1126
1126
1127 $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
1127 $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
1128 4, 5
1128 4, 5
1129
1129
1130 on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
1130 on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
1131 default. join() should agree with the default formatting:
1131 default. join() should agree with the default formatting:
1132
1132
1133 $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
1133 $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
1134 5:13207e5a10d9, 4:bbe44766e73d
1134 5:13207e5a10d9, 4:bbe44766e73d
1135
1135
1136 $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
1136 $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
1137 5:13207e5a10d9fd28ec424934298e176197f2c67f,
1137 5:13207e5a10d9fd28ec424934298e176197f2c67f,
1138 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1138 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1139
1139
1140 Invalid arguments passed to revset()
1140 Invalid arguments passed to revset()
1141
1141
1142 $ hg log -T '{revset("%whatever", 0)}\n'
1142 $ hg log -T '{revset("%whatever", 0)}\n'
1143 hg: parse error: unexpected revspec format character w
1143 hg: parse error: unexpected revspec format character w
1144 [255]
1144 [255]
1145 $ hg log -T '{revset("%lwhatever", files)}\n'
1145 $ hg log -T '{revset("%lwhatever", files)}\n'
1146 hg: parse error: unexpected revspec format character w
1146 hg: parse error: unexpected revspec format character w
1147 [255]
1147 [255]
1148 $ hg log -T '{revset("%s %s", 0)}\n'
1148 $ hg log -T '{revset("%s %s", 0)}\n'
1149 hg: parse error: missing argument for revspec
1149 hg: parse error: missing argument for revspec
1150 [255]
1150 [255]
1151 $ hg log -T '{revset("", 0)}\n'
1151 $ hg log -T '{revset("", 0)}\n'
1152 hg: parse error: too many revspec arguments specified
1152 hg: parse error: too many revspec arguments specified
1153 [255]
1153 [255]
1154 $ hg log -T '{revset("%s", 0, 1)}\n'
1154 $ hg log -T '{revset("%s", 0, 1)}\n'
1155 hg: parse error: too many revspec arguments specified
1155 hg: parse error: too many revspec arguments specified
1156 [255]
1156 [255]
1157 $ hg log -T '{revset("%", 0)}\n'
1157 $ hg log -T '{revset("%", 0)}\n'
1158 hg: parse error: incomplete revspec format character
1158 hg: parse error: incomplete revspec format character
1159 [255]
1159 [255]
1160 $ hg log -T '{revset("%l", 0)}\n'
1160 $ hg log -T '{revset("%l", 0)}\n'
1161 hg: parse error: incomplete revspec format character
1161 hg: parse error: incomplete revspec format character
1162 [255]
1162 [255]
1163 $ hg log -T '{revset("%d", 'foo')}\n'
1163 $ hg log -T '{revset("%d", 'foo')}\n'
1164 hg: parse error: invalid argument for revspec
1164 hg: parse error: invalid argument for revspec
1165 [255]
1165 [255]
1166 $ hg log -T '{revset("%ld", files)}\n'
1166 $ hg log -T '{revset("%ld", files)}\n'
1167 hg: parse error: invalid argument for revspec
1167 hg: parse error: invalid argument for revspec
1168 [255]
1168 [255]
1169 $ hg log -T '{revset("%ls", 0)}\n'
1169 $ hg log -T '{revset("%ls", 0)}\n'
1170 hg: parse error: invalid argument for revspec
1170 hg: parse error: invalid argument for revspec
1171 [255]
1171 [255]
1172 $ hg log -T '{revset("%b", 'foo')}\n'
1172 $ hg log -T '{revset("%b", 'foo')}\n'
1173 hg: parse error: invalid argument for revspec
1173 hg: parse error: invalid argument for revspec
1174 [255]
1174 [255]
1175 $ hg log -T '{revset("%lb", files)}\n'
1175 $ hg log -T '{revset("%lb", files)}\n'
1176 hg: parse error: invalid argument for revspec
1176 hg: parse error: invalid argument for revspec
1177 [255]
1177 [255]
1178 $ hg log -T '{revset("%r", 0)}\n'
1178 $ hg log -T '{revset("%r", 0)}\n'
1179 hg: parse error: invalid argument for revspec
1179 hg: parse error: invalid argument for revspec
1180 [255]
1180 [255]
1181
1181
1182 Test files function
1182 Test files function
1183
1183
1184 $ hg log -T "{rev}\n{join(files('*'), '\n')}\n"
1184 $ hg log -T "{rev}\n{join(files('*'), '\n')}\n"
1185 2
1185 2
1186 a
1186 a
1187 aa
1187 aa
1188 b
1188 b
1189 1
1189 1
1190 a
1190 a
1191 0
1191 0
1192 a
1192 a
1193
1193
1194 $ hg log -T "{rev}\n{join(files('aa'), '\n')}\n"
1194 $ hg log -T "{rev}\n{join(files('aa'), '\n')}\n"
1195 2
1195 2
1196 aa
1196 aa
1197 1
1197 1
1198
1198
1199 0
1199 0
1200
1200
1201
1202 $ hg log -l1 -T "{files('aa') % '{file}\n'}"
1203 aa
1204 $ hg log -l1 -T "{files('aa') % '{path}\n'}"
1205 aa
1206
1201 $ hg rm a
1207 $ hg rm a
1202 $ hg log -r "wdir()" -T "{rev}\n{join(files('*'), '\n')}\n"
1208 $ hg log -r "wdir()" -T "{rev}\n{join(files('*'), '\n')}\n"
1203 2147483647
1209 2147483647
1204 aa
1210 aa
1205 b
1211 b
1206 $ hg revert a
1212 $ hg revert a
1207
1213
1208 Test relpath function
1214 Test relpath function
1209
1215
1210 $ hg log -r0 -T '{files % "{file|relpath}\n"}'
1216 $ hg log -r0 -T '{files % "{file|relpath}\n"}'
1211 a
1217 a
1212 $ cd ..
1218 $ cd ..
1213 $ hg log -R r -r0 -T '{files % "{file|relpath}\n"}'
1219 $ hg log -R r -r0 -T '{files % "{file|relpath}\n"}'
1214 r/a
1220 r/a
1215
1221
1216 Test stringify on sub expressions
1222 Test stringify on sub expressions
1217
1223
1218 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
1224 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
1219 fourth, second, third
1225 fourth, second, third
1220 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
1226 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
1221 abc
1227 abc
1222
1228
1223 Test splitlines
1229 Test splitlines
1224
1230
1225 $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
1231 $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
1226 @ foo Modify, add, remove, rename
1232 @ foo Modify, add, remove, rename
1227 |
1233 |
1228 o foo future
1234 o foo future
1229 |
1235 |
1230 o foo third
1236 o foo third
1231 |
1237 |
1232 o foo second
1238 o foo second
1233
1239
1234 o foo merge
1240 o foo merge
1235 |\
1241 |\
1236 | o foo new head
1242 | o foo new head
1237 | |
1243 | |
1238 o | foo new branch
1244 o | foo new branch
1239 |/
1245 |/
1240 o foo no user, no domain
1246 o foo no user, no domain
1241 |
1247 |
1242 o foo no person
1248 o foo no person
1243 |
1249 |
1244 o foo other 1
1250 o foo other 1
1245 | foo other 2
1251 | foo other 2
1246 | foo
1252 | foo
1247 | foo other 3
1253 | foo other 3
1248 o foo line 1
1254 o foo line 1
1249 foo line 2
1255 foo line 2
1250
1256
1251 $ hg log -R a -r0 -T '{desc|splitlines}\n'
1257 $ hg log -R a -r0 -T '{desc|splitlines}\n'
1252 line 1 line 2
1258 line 1 line 2
1253 $ hg log -R a -r0 -T '{join(desc|splitlines, "|")}\n'
1259 $ hg log -R a -r0 -T '{join(desc|splitlines, "|")}\n'
1254 line 1|line 2
1260 line 1|line 2
1255
1261
1256 Test startswith
1262 Test startswith
1257 $ hg log -Gv -R a --template "{startswith(desc)}"
1263 $ hg log -Gv -R a --template "{startswith(desc)}"
1258 hg: parse error: startswith expects two arguments
1264 hg: parse error: startswith expects two arguments
1259 [255]
1265 [255]
1260
1266
1261 $ hg log -Gv -R a --template "{startswith('line', desc)}"
1267 $ hg log -Gv -R a --template "{startswith('line', desc)}"
1262 @
1268 @
1263 |
1269 |
1264 o
1270 o
1265 |
1271 |
1266 o
1272 o
1267 |
1273 |
1268 o
1274 o
1269
1275
1270 o
1276 o
1271 |\
1277 |\
1272 | o
1278 | o
1273 | |
1279 | |
1274 o |
1280 o |
1275 |/
1281 |/
1276 o
1282 o
1277 |
1283 |
1278 o
1284 o
1279 |
1285 |
1280 o
1286 o
1281 |
1287 |
1282 o line 1
1288 o line 1
1283 line 2
1289 line 2
1284
1290
1285 Test word function (including index out of bounds graceful failure)
1291 Test word function (including index out of bounds graceful failure)
1286
1292
1287 $ hg log -Gv -R a --template "{word('1', desc)}"
1293 $ hg log -Gv -R a --template "{word('1', desc)}"
1288 @ add,
1294 @ add,
1289 |
1295 |
1290 o
1296 o
1291 |
1297 |
1292 o
1298 o
1293 |
1299 |
1294 o
1300 o
1295
1301
1296 o
1302 o
1297 |\
1303 |\
1298 | o head
1304 | o head
1299 | |
1305 | |
1300 o | branch
1306 o | branch
1301 |/
1307 |/
1302 o user,
1308 o user,
1303 |
1309 |
1304 o person
1310 o person
1305 |
1311 |
1306 o 1
1312 o 1
1307 |
1313 |
1308 o 1
1314 o 1
1309
1315
1310
1316
1311 Test word third parameter used as splitter
1317 Test word third parameter used as splitter
1312
1318
1313 $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
1319 $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
1314 @ M
1320 @ M
1315 |
1321 |
1316 o future
1322 o future
1317 |
1323 |
1318 o third
1324 o third
1319 |
1325 |
1320 o sec
1326 o sec
1321
1327
1322 o merge
1328 o merge
1323 |\
1329 |\
1324 | o new head
1330 | o new head
1325 | |
1331 | |
1326 o | new branch
1332 o | new branch
1327 |/
1333 |/
1328 o n
1334 o n
1329 |
1335 |
1330 o n
1336 o n
1331 |
1337 |
1332 o
1338 o
1333 |
1339 |
1334 o line 1
1340 o line 1
1335 line 2
1341 line 2
1336
1342
1337 Test word error messages for not enough and too many arguments
1343 Test word error messages for not enough and too many arguments
1338
1344
1339 $ hg log -Gv -R a --template "{word('0')}"
1345 $ hg log -Gv -R a --template "{word('0')}"
1340 hg: parse error: word expects two or three arguments, got 1
1346 hg: parse error: word expects two or three arguments, got 1
1341 [255]
1347 [255]
1342
1348
1343 $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
1349 $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
1344 hg: parse error: word expects two or three arguments, got 7
1350 hg: parse error: word expects two or three arguments, got 7
1345 [255]
1351 [255]
1346
1352
1347 Test word for integer literal
1353 Test word for integer literal
1348
1354
1349 $ hg log -R a --template "{word(2, desc)}\n" -r0
1355 $ hg log -R a --template "{word(2, desc)}\n" -r0
1350 line
1356 line
1351
1357
1352 Test word for invalid numbers
1358 Test word for invalid numbers
1353
1359
1354 $ hg log -Gv -R a --template "{word('a', desc)}"
1360 $ hg log -Gv -R a --template "{word('a', desc)}"
1355 hg: parse error: word expects an integer index
1361 hg: parse error: word expects an integer index
1356 [255]
1362 [255]
1357
1363
1358 Test word for out of range
1364 Test word for out of range
1359
1365
1360 $ hg log -R a --template "{word(10000, desc)}"
1366 $ hg log -R a --template "{word(10000, desc)}"
1361 $ hg log -R a --template "{word(-10000, desc)}"
1367 $ hg log -R a --template "{word(-10000, desc)}"
1362
1368
1363 Test indent and not adding to empty lines
1369 Test indent and not adding to empty lines
1364
1370
1365 $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
1371 $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
1366 -----
1372 -----
1367 > line 1
1373 > line 1
1368 >> line 2
1374 >> line 2
1369 -----
1375 -----
1370 > other 1
1376 > other 1
1371 >> other 2
1377 >> other 2
1372
1378
1373 >> other 3
1379 >> other 3
1374
1380
1375 Test with non-strings like dates
1381 Test with non-strings like dates
1376
1382
1377 $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a
1383 $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a
1378 1200000.00
1384 1200000.00
1379 1300000.00
1385 1300000.00
1380
1386
1381 json filter should escape HTML tags so that the output can be embedded in hgweb:
1387 json filter should escape HTML tags so that the output can be embedded in hgweb:
1382
1388
1383 $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
1389 $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
1384 "\u003cfoo@example.org\u003e"
1390 "\u003cfoo@example.org\u003e"
1385
1391
1386 Set up repository for non-ascii encoding tests:
1392 Set up repository for non-ascii encoding tests:
1387
1393
1388 $ hg init nonascii
1394 $ hg init nonascii
1389 $ cd nonascii
1395 $ cd nonascii
1390 $ $PYTHON <<EOF
1396 $ $PYTHON <<EOF
1391 > open('latin1', 'wb').write(b'\xe9')
1397 > open('latin1', 'wb').write(b'\xe9')
1392 > open('utf-8', 'wb').write(b'\xc3\xa9')
1398 > open('utf-8', 'wb').write(b'\xc3\xa9')
1393 > EOF
1399 > EOF
1394 $ HGENCODING=utf-8 hg branch -q `cat utf-8`
1400 $ HGENCODING=utf-8 hg branch -q `cat utf-8`
1395 $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
1401 $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
1396
1402
1397 json filter should try round-trip conversion to utf-8:
1403 json filter should try round-trip conversion to utf-8:
1398
1404
1399 $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
1405 $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
1400 "\u00e9"
1406 "\u00e9"
1401 $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
1407 $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
1402 "non-ascii branch: \u00e9"
1408 "non-ascii branch: \u00e9"
1403
1409
1404 json filter should take input as utf-8 if it was converted from utf-8:
1410 json filter should take input as utf-8 if it was converted from utf-8:
1405
1411
1406 $ HGENCODING=latin-1 hg log -T "{branch|json}\n" -r0
1412 $ HGENCODING=latin-1 hg log -T "{branch|json}\n" -r0
1407 "\u00e9"
1413 "\u00e9"
1408 $ HGENCODING=latin-1 hg log -T "{desc|json}\n" -r0
1414 $ HGENCODING=latin-1 hg log -T "{desc|json}\n" -r0
1409 "non-ascii branch: \u00e9"
1415 "non-ascii branch: \u00e9"
1410
1416
1411 json filter takes input as utf-8b:
1417 json filter takes input as utf-8b:
1412
1418
1413 $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
1419 $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
1414 "\u00e9"
1420 "\u00e9"
1415 $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
1421 $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
1416 "\udce9"
1422 "\udce9"
1417
1423
1418 utf8 filter:
1424 utf8 filter:
1419
1425
1420 $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
1426 $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
1421 round-trip: c3a9
1427 round-trip: c3a9
1422 $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
1428 $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
1423 decoded: c3a9
1429 decoded: c3a9
1424 $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
1430 $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
1425 abort: decoding near * (glob)
1431 abort: decoding near * (glob)
1426 [255]
1432 [255]
1427 $ hg log -T "coerced to string: {rev|utf8}\n" -r0
1433 $ hg log -T "coerced to string: {rev|utf8}\n" -r0
1428 coerced to string: 0
1434 coerced to string: 0
1429
1435
1430 pad width:
1436 pad width:
1431
1437
1432 $ HGENCODING=utf-8 hg debugtemplate "{pad('`cat utf-8`', 2, '-')}\n"
1438 $ HGENCODING=utf-8 hg debugtemplate "{pad('`cat utf-8`', 2, '-')}\n"
1433 \xc3\xa9- (esc)
1439 \xc3\xa9- (esc)
1434
1440
1435 $ cd ..
1441 $ cd ..
@@ -1,1222 +1,1246
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:
749
750 $ hg log -l1 -T '{join(file_mods, " ")}\n'
751 third
752 $ hg log -l1 -T '{file_mods % "{file}\n"}'
753 third
754 $ hg log -l1 -T '{file_mods % "{path}\n"}'
755 third
756
757 $ hg log -l1 -T '{join(files, " ")}\n'
758 a b fifth fourth third
759 $ hg log -l1 -T '{files % "{file}\n"}'
760 a
761 b
762 fifth
763 fourth
764 third
765 $ hg log -l1 -T '{files % "{path}\n"}'
766 a
767 b
768 fifth
769 fourth
770 third
771
748 Test index keyword:
772 Test index keyword:
749
773
750 $ hg log -l 2 -T '{index + 10}{files % " {index}:{file}"}\n'
774 $ hg log -l 2 -T '{index + 10}{files % " {index}:{file}"}\n'
751 10 0:a 1:b 2:fifth 3:fourth 4:third
775 10 0:a 1:b 2:fifth 3:fourth 4:third
752 11 0:a
776 11 0:a
753
777
754 $ hg branches -T '{index} {branch}\n'
778 $ hg branches -T '{index} {branch}\n'
755 0 default
779 0 default
756 1 foo
780 1 foo
757
781
758 ui verbosity:
782 ui verbosity:
759
783
760 $ hg log -l1 -T '{verbosity}\n'
784 $ hg log -l1 -T '{verbosity}\n'
761
785
762 $ hg log -l1 -T '{verbosity}\n' --debug
786 $ hg log -l1 -T '{verbosity}\n' --debug
763 debug
787 debug
764 $ hg log -l1 -T '{verbosity}\n' --quiet
788 $ hg log -l1 -T '{verbosity}\n' --quiet
765 quiet
789 quiet
766 $ hg log -l1 -T '{verbosity}\n' --verbose
790 $ hg log -l1 -T '{verbosity}\n' --verbose
767 verbose
791 verbose
768
792
769 $ cd ..
793 $ cd ..
770
794
771 latesttag:
795 latesttag:
772
796
773 $ hg init latesttag
797 $ hg init latesttag
774 $ cd latesttag
798 $ cd latesttag
775
799
776 $ echo a > file
800 $ echo a > file
777 $ hg ci -Am a -d '0 0'
801 $ hg ci -Am a -d '0 0'
778 adding file
802 adding file
779
803
780 $ echo b >> file
804 $ echo b >> file
781 $ hg ci -m b -d '1 0'
805 $ hg ci -m b -d '1 0'
782
806
783 $ echo c >> head1
807 $ echo c >> head1
784 $ hg ci -Am h1c -d '2 0'
808 $ hg ci -Am h1c -d '2 0'
785 adding head1
809 adding head1
786
810
787 $ hg update -q 1
811 $ hg update -q 1
788 $ echo d >> head2
812 $ echo d >> head2
789 $ hg ci -Am h2d -d '3 0'
813 $ hg ci -Am h2d -d '3 0'
790 adding head2
814 adding head2
791 created new head
815 created new head
792
816
793 $ echo e >> head2
817 $ echo e >> head2
794 $ hg ci -m h2e -d '4 0'
818 $ hg ci -m h2e -d '4 0'
795
819
796 $ hg merge -q
820 $ hg merge -q
797 $ hg ci -m merge -d '5 -3600'
821 $ hg ci -m merge -d '5 -3600'
798
822
799 No tag set:
823 No tag set:
800
824
801 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
825 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
802 @ 5: null+5
826 @ 5: null+5
803 |\
827 |\
804 | o 4: null+4
828 | o 4: null+4
805 | |
829 | |
806 | o 3: null+3
830 | o 3: null+3
807 | |
831 | |
808 o | 2: null+3
832 o | 2: null+3
809 |/
833 |/
810 o 1: null+2
834 o 1: null+2
811 |
835 |
812 o 0: null+1
836 o 0: null+1
813
837
814
838
815 One common tag: longest path wins for {latesttagdistance}:
839 One common tag: longest path wins for {latesttagdistance}:
816
840
817 $ hg tag -r 1 -m t1 -d '6 0' t1
841 $ hg tag -r 1 -m t1 -d '6 0' t1
818 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
842 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
819 @ 6: t1+4
843 @ 6: t1+4
820 |
844 |
821 o 5: t1+3
845 o 5: t1+3
822 |\
846 |\
823 | o 4: t1+2
847 | o 4: t1+2
824 | |
848 | |
825 | o 3: t1+1
849 | o 3: t1+1
826 | |
850 | |
827 o | 2: t1+1
851 o | 2: t1+1
828 |/
852 |/
829 o 1: t1+0
853 o 1: t1+0
830 |
854 |
831 o 0: null+1
855 o 0: null+1
832
856
833
857
834 One ancestor tag: closest wins:
858 One ancestor tag: closest wins:
835
859
836 $ hg tag -r 2 -m t2 -d '7 0' t2
860 $ hg tag -r 2 -m t2 -d '7 0' t2
837 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
861 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
838 @ 7: t2+3
862 @ 7: t2+3
839 |
863 |
840 o 6: t2+2
864 o 6: t2+2
841 |
865 |
842 o 5: t2+1
866 o 5: t2+1
843 |\
867 |\
844 | o 4: t1+2
868 | o 4: t1+2
845 | |
869 | |
846 | o 3: t1+1
870 | o 3: t1+1
847 | |
871 | |
848 o | 2: t2+0
872 o | 2: t2+0
849 |/
873 |/
850 o 1: t1+0
874 o 1: t1+0
851 |
875 |
852 o 0: null+1
876 o 0: null+1
853
877
854
878
855 Two branch tags: more recent wins if same number of changes:
879 Two branch tags: more recent wins if same number of changes:
856
880
857 $ hg tag -r 3 -m t3 -d '8 0' t3
881 $ hg tag -r 3 -m t3 -d '8 0' t3
858 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
882 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
859 @ 8: t3+5
883 @ 8: t3+5
860 |
884 |
861 o 7: t3+4
885 o 7: t3+4
862 |
886 |
863 o 6: t3+3
887 o 6: t3+3
864 |
888 |
865 o 5: t3+2
889 o 5: t3+2
866 |\
890 |\
867 | o 4: t3+1
891 | o 4: t3+1
868 | |
892 | |
869 | o 3: t3+0
893 | o 3: t3+0
870 | |
894 | |
871 o | 2: t2+0
895 o | 2: t2+0
872 |/
896 |/
873 o 1: t1+0
897 o 1: t1+0
874 |
898 |
875 o 0: null+1
899 o 0: null+1
876
900
877
901
878 Two branch tags: fewest changes wins:
902 Two branch tags: fewest changes wins:
879
903
880 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
904 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
881 $ hg log -G --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
905 $ hg log -G --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
882 @ 9: t4+5,6
906 @ 9: t4+5,6
883 |
907 |
884 o 8: t4+4,5
908 o 8: t4+4,5
885 |
909 |
886 o 7: t4+3,4
910 o 7: t4+3,4
887 |
911 |
888 o 6: t4+2,3
912 o 6: t4+2,3
889 |
913 |
890 o 5: t4+1,2
914 o 5: t4+1,2
891 |\
915 |\
892 | o 4: t4+0,0
916 | o 4: t4+0,0
893 | |
917 | |
894 | o 3: t3+0,0
918 | o 3: t3+0,0
895 | |
919 | |
896 o | 2: t2+0,0
920 o | 2: t2+0,0
897 |/
921 |/
898 o 1: t1+0,0
922 o 1: t1+0,0
899 |
923 |
900 o 0: null+1,1
924 o 0: null+1,1
901
925
902
926
903 Merged tag overrides:
927 Merged tag overrides:
904
928
905 $ hg tag -r 5 -m t5 -d '9 0' t5
929 $ hg tag -r 5 -m t5 -d '9 0' t5
906 $ hg tag -r 3 -m at3 -d '10 0' at3
930 $ hg tag -r 3 -m at3 -d '10 0' at3
907 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
931 $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
908 @ 11: t5+6
932 @ 11: t5+6
909 |
933 |
910 o 10: t5+5
934 o 10: t5+5
911 |
935 |
912 o 9: t5+4
936 o 9: t5+4
913 |
937 |
914 o 8: t5+3
938 o 8: t5+3
915 |
939 |
916 o 7: t5+2
940 o 7: t5+2
917 |
941 |
918 o 6: t5+1
942 o 6: t5+1
919 |
943 |
920 o 5: t5+0
944 o 5: t5+0
921 |\
945 |\
922 | o 4: t4+0
946 | o 4: t4+0
923 | |
947 | |
924 | o 3: at3:t3+0
948 | o 3: at3:t3+0
925 | |
949 | |
926 o | 2: t2+0
950 o | 2: t2+0
927 |/
951 |/
928 o 1: t1+0
952 o 1: t1+0
929 |
953 |
930 o 0: null+1
954 o 0: null+1
931
955
932
956
933 $ hg log -G --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
957 $ hg log -G --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
934 @ 11: t5+6,6
958 @ 11: t5+6,6
935 |
959 |
936 o 10: t5+5,5
960 o 10: t5+5,5
937 |
961 |
938 o 9: t5+4,4
962 o 9: t5+4,4
939 |
963 |
940 o 8: t5+3,3
964 o 8: t5+3,3
941 |
965 |
942 o 7: t5+2,2
966 o 7: t5+2,2
943 |
967 |
944 o 6: t5+1,1
968 o 6: t5+1,1
945 |
969 |
946 o 5: t5+0,0
970 o 5: t5+0,0
947 |\
971 |\
948 | o 4: t4+0,0
972 | o 4: t4+0,0
949 | |
973 | |
950 | o 3: at3+0,0 t3+0,0
974 | o 3: at3+0,0 t3+0,0
951 | |
975 | |
952 o | 2: t2+0,0
976 o | 2: t2+0,0
953 |/
977 |/
954 o 1: t1+0,0
978 o 1: t1+0,0
955 |
979 |
956 o 0: null+1,1
980 o 0: null+1,1
957
981
958
982
959 $ cd ..
983 $ cd ..
960
984
961 Set up repository containing template fragments in commit metadata:
985 Set up repository containing template fragments in commit metadata:
962
986
963 $ hg init r
987 $ hg init r
964 $ cd r
988 $ cd r
965 $ echo a > a
989 $ echo a > a
966 $ hg ci -Am '{rev}'
990 $ hg ci -Am '{rev}'
967 adding a
991 adding a
968
992
969 $ hg branch -q 'text.{rev}'
993 $ hg branch -q 'text.{rev}'
970 $ echo aa >> aa
994 $ echo aa >> aa
971 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
995 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
972
996
973 Test termwidth:
997 Test termwidth:
974
998
975 $ COLUMNS=25 hg log -l1 --template '{fill(desc, termwidth, "{node|short}:", "termwidth.{rev}:")}'
999 $ COLUMNS=25 hg log -l1 --template '{fill(desc, termwidth, "{node|short}:", "termwidth.{rev}:")}'
976 bcc7ff960b8e:desc to be
1000 bcc7ff960b8e:desc to be
977 termwidth.1:wrapped desc
1001 termwidth.1:wrapped desc
978 termwidth.1:to be wrapped (no-eol)
1002 termwidth.1:to be wrapped (no-eol)
979
1003
980 Just one more commit:
1004 Just one more commit:
981
1005
982 $ echo b > b
1006 $ echo b > b
983 $ hg ci -qAm b
1007 $ hg ci -qAm b
984
1008
985 Test 'originalnode'
1009 Test 'originalnode'
986
1010
987 $ hg log -r 1 -T '{revset("null") % "{node|short} {originalnode|short}"}\n'
1011 $ hg log -r 1 -T '{revset("null") % "{node|short} {originalnode|short}"}\n'
988 000000000000 bcc7ff960b8e
1012 000000000000 bcc7ff960b8e
989 $ hg log -r 0 -T '{manifest % "{node} {originalnode}"}\n'
1013 $ hg log -r 0 -T '{manifest % "{node} {originalnode}"}\n'
990 a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 f7769ec2ab975ad19684098ad1ffd9b81ecc71a1
1014 a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 f7769ec2ab975ad19684098ad1ffd9b81ecc71a1
991
1015
992 Test active bookmark templating
1016 Test active bookmark templating
993
1017
994 $ hg book foo
1018 $ hg book foo
995 $ hg book bar
1019 $ hg book bar
996 $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, active, \"*\")} '}\n"
1020 $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, active, \"*\")} '}\n"
997 2 bar* foo
1021 2 bar* foo
998 1
1022 1
999 0
1023 0
1000 $ hg log --template "{rev} {activebookmark}\n"
1024 $ hg log --template "{rev} {activebookmark}\n"
1001 2 bar
1025 2 bar
1002 1
1026 1
1003 0
1027 0
1004 $ hg bookmarks --inactive bar
1028 $ hg bookmarks --inactive bar
1005 $ hg log --template "{rev} {activebookmark}\n"
1029 $ hg log --template "{rev} {activebookmark}\n"
1006 2
1030 2
1007 1
1031 1
1008 0
1032 0
1009 $ hg book -r1 baz
1033 $ hg book -r1 baz
1010 $ hg log --template "{rev} {join(bookmarks, ' ')}\n"
1034 $ hg log --template "{rev} {join(bookmarks, ' ')}\n"
1011 2 bar foo
1035 2 bar foo
1012 1 baz
1036 1 baz
1013 0
1037 0
1014 $ hg log --template "{rev} {ifcontains('foo', bookmarks, 't', 'f')}\n"
1038 $ hg log --template "{rev} {ifcontains('foo', bookmarks, 't', 'f')}\n"
1015 2 t
1039 2 t
1016 1 f
1040 1 f
1017 0 f
1041 0 f
1018
1042
1019 Test namespaces dict
1043 Test namespaces dict
1020
1044
1021 $ hg --config extensions.revnamesext=$TESTDIR/revnamesext.py log -T '{rev}\n{namespaces % " {namespace} color={colorname} builtin={builtin}\n {join(names, ",")}\n"}\n'
1045 $ hg --config extensions.revnamesext=$TESTDIR/revnamesext.py log -T '{rev}\n{namespaces % " {namespace} color={colorname} builtin={builtin}\n {join(names, ",")}\n"}\n'
1022 2
1046 2
1023 bookmarks color=bookmark builtin=True
1047 bookmarks color=bookmark builtin=True
1024 bar,foo
1048 bar,foo
1025 tags color=tag builtin=True
1049 tags color=tag builtin=True
1026 tip
1050 tip
1027 branches color=branch builtin=True
1051 branches color=branch builtin=True
1028 text.{rev}
1052 text.{rev}
1029 revnames color=revname builtin=False
1053 revnames color=revname builtin=False
1030 r2
1054 r2
1031
1055
1032 1
1056 1
1033 bookmarks color=bookmark builtin=True
1057 bookmarks color=bookmark builtin=True
1034 baz
1058 baz
1035 tags color=tag builtin=True
1059 tags color=tag builtin=True
1036
1060
1037 branches color=branch builtin=True
1061 branches color=branch builtin=True
1038 text.{rev}
1062 text.{rev}
1039 revnames color=revname builtin=False
1063 revnames color=revname builtin=False
1040 r1
1064 r1
1041
1065
1042 0
1066 0
1043 bookmarks color=bookmark builtin=True
1067 bookmarks color=bookmark builtin=True
1044
1068
1045 tags color=tag builtin=True
1069 tags color=tag builtin=True
1046
1070
1047 branches color=branch builtin=True
1071 branches color=branch builtin=True
1048 default
1072 default
1049 revnames color=revname builtin=False
1073 revnames color=revname builtin=False
1050 r0
1074 r0
1051
1075
1052 $ hg log -r2 -T '{namespaces % "{namespace}: {names}\n"}'
1076 $ hg log -r2 -T '{namespaces % "{namespace}: {names}\n"}'
1053 bookmarks: bar foo
1077 bookmarks: bar foo
1054 tags: tip
1078 tags: tip
1055 branches: text.{rev}
1079 branches: text.{rev}
1056 $ hg log -r2 -T '{namespaces % "{namespace}:\n{names % " {name}\n"}"}'
1080 $ hg log -r2 -T '{namespaces % "{namespace}:\n{names % " {name}\n"}"}'
1057 bookmarks:
1081 bookmarks:
1058 bar
1082 bar
1059 foo
1083 foo
1060 tags:
1084 tags:
1061 tip
1085 tip
1062 branches:
1086 branches:
1063 text.{rev}
1087 text.{rev}
1064 $ hg log -r2 -T '{get(namespaces, "bookmarks") % "{name}\n"}'
1088 $ hg log -r2 -T '{get(namespaces, "bookmarks") % "{name}\n"}'
1065 bar
1089 bar
1066 foo
1090 foo
1067 $ hg log -r2 -T '{namespaces.bookmarks % "{bookmark}\n"}'
1091 $ hg log -r2 -T '{namespaces.bookmarks % "{bookmark}\n"}'
1068 bar
1092 bar
1069 foo
1093 foo
1070
1094
1071 $ cd ..
1095 $ cd ..
1072
1096
1073 Test 'graphwidth' in 'hg log' on various topologies. The key here is that the
1097 Test 'graphwidth' in 'hg log' on various topologies. The key here is that the
1074 printed graphwidths 3, 5, 7, etc. should all line up in their respective
1098 printed graphwidths 3, 5, 7, etc. should all line up in their respective
1075 columns. We don't care about other aspects of the graph rendering here.
1099 columns. We don't care about other aspects of the graph rendering here.
1076
1100
1077 $ hg init graphwidth
1101 $ hg init graphwidth
1078 $ cd graphwidth
1102 $ cd graphwidth
1079
1103
1080 $ wrappabletext="a a a a a a a a a a a a"
1104 $ wrappabletext="a a a a a a a a a a a a"
1081
1105
1082 $ printf "first\n" > file
1106 $ printf "first\n" > file
1083 $ hg add file
1107 $ hg add file
1084 $ hg commit -m "$wrappabletext"
1108 $ hg commit -m "$wrappabletext"
1085
1109
1086 $ printf "first\nsecond\n" > file
1110 $ printf "first\nsecond\n" > file
1087 $ hg commit -m "$wrappabletext"
1111 $ hg commit -m "$wrappabletext"
1088
1112
1089 $ hg checkout 0
1113 $ hg checkout 0
1090 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1114 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1091 $ printf "third\nfirst\n" > file
1115 $ printf "third\nfirst\n" > file
1092 $ hg commit -m "$wrappabletext"
1116 $ hg commit -m "$wrappabletext"
1093 created new head
1117 created new head
1094
1118
1095 $ hg merge
1119 $ hg merge
1096 merging file
1120 merging file
1097 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1121 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1098 (branch merge, don't forget to commit)
1122 (branch merge, don't forget to commit)
1099
1123
1100 $ hg log --graph -T "{graphwidth}"
1124 $ hg log --graph -T "{graphwidth}"
1101 @ 3
1125 @ 3
1102 |
1126 |
1103 | @ 5
1127 | @ 5
1104 |/
1128 |/
1105 o 3
1129 o 3
1106
1130
1107 $ hg commit -m "$wrappabletext"
1131 $ hg commit -m "$wrappabletext"
1108
1132
1109 $ hg log --graph -T "{graphwidth}"
1133 $ hg log --graph -T "{graphwidth}"
1110 @ 5
1134 @ 5
1111 |\
1135 |\
1112 | o 5
1136 | o 5
1113 | |
1137 | |
1114 o | 5
1138 o | 5
1115 |/
1139 |/
1116 o 3
1140 o 3
1117
1141
1118
1142
1119 $ hg checkout 0
1143 $ hg checkout 0
1120 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1144 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1121 $ printf "third\nfirst\nsecond\n" > file
1145 $ printf "third\nfirst\nsecond\n" > file
1122 $ hg commit -m "$wrappabletext"
1146 $ hg commit -m "$wrappabletext"
1123 created new head
1147 created new head
1124
1148
1125 $ hg log --graph -T "{graphwidth}"
1149 $ hg log --graph -T "{graphwidth}"
1126 @ 3
1150 @ 3
1127 |
1151 |
1128 | o 7
1152 | o 7
1129 | |\
1153 | |\
1130 +---o 7
1154 +---o 7
1131 | |
1155 | |
1132 | o 5
1156 | o 5
1133 |/
1157 |/
1134 o 3
1158 o 3
1135
1159
1136
1160
1137 $ hg log --graph -T "{graphwidth}" -r 3
1161 $ hg log --graph -T "{graphwidth}" -r 3
1138 o 5
1162 o 5
1139 |\
1163 |\
1140 ~ ~
1164 ~ ~
1141
1165
1142 $ hg log --graph -T "{graphwidth}" -r 1
1166 $ hg log --graph -T "{graphwidth}" -r 1
1143 o 3
1167 o 3
1144 |
1168 |
1145 ~
1169 ~
1146
1170
1147 $ hg merge
1171 $ hg merge
1148 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1172 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1149 (branch merge, don't forget to commit)
1173 (branch merge, don't forget to commit)
1150 $ hg commit -m "$wrappabletext"
1174 $ hg commit -m "$wrappabletext"
1151
1175
1152 $ printf "seventh\n" >> file
1176 $ printf "seventh\n" >> file
1153 $ hg commit -m "$wrappabletext"
1177 $ hg commit -m "$wrappabletext"
1154
1178
1155 $ hg log --graph -T "{graphwidth}"
1179 $ hg log --graph -T "{graphwidth}"
1156 @ 3
1180 @ 3
1157 |
1181 |
1158 o 5
1182 o 5
1159 |\
1183 |\
1160 | o 5
1184 | o 5
1161 | |
1185 | |
1162 o | 7
1186 o | 7
1163 |\ \
1187 |\ \
1164 | o | 7
1188 | o | 7
1165 | |/
1189 | |/
1166 o / 5
1190 o / 5
1167 |/
1191 |/
1168 o 3
1192 o 3
1169
1193
1170
1194
1171 The point of graphwidth is to allow wrapping that accounts for the space taken
1195 The point of graphwidth is to allow wrapping that accounts for the space taken
1172 by the graph.
1196 by the graph.
1173
1197
1174 $ COLUMNS=10 hg log --graph -T "{fill(desc, termwidth - graphwidth)}"
1198 $ COLUMNS=10 hg log --graph -T "{fill(desc, termwidth - graphwidth)}"
1175 @ a a a a
1199 @ a a a a
1176 | a a a a
1200 | a a a a
1177 | a a a a
1201 | a a a a
1178 o a a a
1202 o a a a
1179 |\ a a a
1203 |\ a a a
1180 | | a a a
1204 | | a a a
1181 | | a a a
1205 | | a a a
1182 | o a a a
1206 | o a a a
1183 | | a a a
1207 | | a a a
1184 | | a a a
1208 | | a a a
1185 | | a a a
1209 | | a a a
1186 o | a a
1210 o | a a
1187 |\ \ a a
1211 |\ \ a a
1188 | | | a a
1212 | | | a a
1189 | | | a a
1213 | | | a a
1190 | | | a a
1214 | | | a a
1191 | | | a a
1215 | | | a a
1192 | o | a a
1216 | o | a a
1193 | |/ a a
1217 | |/ a a
1194 | | a a
1218 | | a a
1195 | | a a
1219 | | a a
1196 | | a a
1220 | | a a
1197 | | a a
1221 | | a a
1198 o | a a a
1222 o | a a a
1199 |/ a a a
1223 |/ a a a
1200 | a a a
1224 | a a a
1201 | a a a
1225 | a a a
1202 o a a a a
1226 o a a a a
1203 a a a a
1227 a a a a
1204 a a a a
1228 a a a a
1205
1229
1206 Something tricky happens when there are elided nodes; the next drawn row of
1230 Something tricky happens when there are elided nodes; the next drawn row of
1207 edges can be more than one column wider, but the graph width only increases by
1231 edges can be more than one column wider, but the graph width only increases by
1208 one column. The remaining columns are added in between the nodes.
1232 one column. The remaining columns are added in between the nodes.
1209
1233
1210 $ hg log --graph -T "{graphwidth}" -r "0|2|4|5"
1234 $ hg log --graph -T "{graphwidth}" -r "0|2|4|5"
1211 o 5
1235 o 5
1212 |\
1236 |\
1213 | \
1237 | \
1214 | :\
1238 | :\
1215 o : : 7
1239 o : : 7
1216 :/ /
1240 :/ /
1217 : o 5
1241 : o 5
1218 :/
1242 :/
1219 o 3
1243 o 3
1220
1244
1221
1245
1222 $ cd ..
1246 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now