##// END OF EJS Templates
templatefilters: add {x|cbor} filter for custom CBOR output
Yuya Nishihara -
r42164:4df7c4b7 default
parent child Browse files
Show More
@@ -1,475 +1,481
1 # templatefilters.py - common template expansion filters
1 # templatefilters.py - common template expansion filters
2 #
2 #
3 # Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2008 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 os
10 import os
11 import re
11 import re
12 import time
12 import time
13
13
14 from .i18n import _
14 from .i18n import _
15 from . import (
15 from . import (
16 encoding,
16 encoding,
17 error,
17 error,
18 node,
18 node,
19 pycompat,
19 pycompat,
20 registrar,
20 registrar,
21 templateutil,
21 templateutil,
22 url,
22 url,
23 util,
23 util,
24 )
24 )
25 from .utils import (
25 from .utils import (
26 cborutil,
26 dateutil,
27 dateutil,
27 stringutil,
28 stringutil,
28 )
29 )
29
30
30 urlerr = util.urlerr
31 urlerr = util.urlerr
31 urlreq = util.urlreq
32 urlreq = util.urlreq
32
33
33 if pycompat.ispy3:
34 if pycompat.ispy3:
34 long = int
35 long = int
35
36
36 # filters are callables like:
37 # filters are callables like:
37 # fn(obj)
38 # fn(obj)
38 # with:
39 # with:
39 # obj - object to be filtered (text, date, list and so on)
40 # obj - object to be filtered (text, date, list and so on)
40 filters = {}
41 filters = {}
41
42
42 templatefilter = registrar.templatefilter(filters)
43 templatefilter = registrar.templatefilter(filters)
43
44
44 @templatefilter('addbreaks', intype=bytes)
45 @templatefilter('addbreaks', intype=bytes)
45 def addbreaks(text):
46 def addbreaks(text):
46 """Any text. Add an XHTML "<br />" tag before the end of
47 """Any text. Add an XHTML "<br />" tag before the end of
47 every line except the last.
48 every line except the last.
48 """
49 """
49 return text.replace('\n', '<br/>\n')
50 return text.replace('\n', '<br/>\n')
50
51
51 agescales = [("year", 3600 * 24 * 365, 'Y'),
52 agescales = [("year", 3600 * 24 * 365, 'Y'),
52 ("month", 3600 * 24 * 30, 'M'),
53 ("month", 3600 * 24 * 30, 'M'),
53 ("week", 3600 * 24 * 7, 'W'),
54 ("week", 3600 * 24 * 7, 'W'),
54 ("day", 3600 * 24, 'd'),
55 ("day", 3600 * 24, 'd'),
55 ("hour", 3600, 'h'),
56 ("hour", 3600, 'h'),
56 ("minute", 60, 'm'),
57 ("minute", 60, 'm'),
57 ("second", 1, 's')]
58 ("second", 1, 's')]
58
59
59 @templatefilter('age', intype=templateutil.date)
60 @templatefilter('age', intype=templateutil.date)
60 def age(date, abbrev=False):
61 def age(date, abbrev=False):
61 """Date. Returns a human-readable date/time difference between the
62 """Date. Returns a human-readable date/time difference between the
62 given date/time and the current date/time.
63 given date/time and the current date/time.
63 """
64 """
64
65
65 def plural(t, c):
66 def plural(t, c):
66 if c == 1:
67 if c == 1:
67 return t
68 return t
68 return t + "s"
69 return t + "s"
69 def fmt(t, c, a):
70 def fmt(t, c, a):
70 if abbrev:
71 if abbrev:
71 return "%d%s" % (c, a)
72 return "%d%s" % (c, a)
72 return "%d %s" % (c, plural(t, c))
73 return "%d %s" % (c, plural(t, c))
73
74
74 now = time.time()
75 now = time.time()
75 then = date[0]
76 then = date[0]
76 future = False
77 future = False
77 if then > now:
78 if then > now:
78 future = True
79 future = True
79 delta = max(1, int(then - now))
80 delta = max(1, int(then - now))
80 if delta > agescales[0][1] * 30:
81 if delta > agescales[0][1] * 30:
81 return 'in the distant future'
82 return 'in the distant future'
82 else:
83 else:
83 delta = max(1, int(now - then))
84 delta = max(1, int(now - then))
84 if delta > agescales[0][1] * 2:
85 if delta > agescales[0][1] * 2:
85 return dateutil.shortdate(date)
86 return dateutil.shortdate(date)
86
87
87 for t, s, a in agescales:
88 for t, s, a in agescales:
88 n = delta // s
89 n = delta // s
89 if n >= 2 or s == 1:
90 if n >= 2 or s == 1:
90 if future:
91 if future:
91 return '%s from now' % fmt(t, n, a)
92 return '%s from now' % fmt(t, n, a)
92 return '%s ago' % fmt(t, n, a)
93 return '%s ago' % fmt(t, n, a)
93
94
94 @templatefilter('basename', intype=bytes)
95 @templatefilter('basename', intype=bytes)
95 def basename(path):
96 def basename(path):
96 """Any text. Treats the text as a path, and returns the last
97 """Any text. Treats the text as a path, and returns the last
97 component of the path after splitting by the path separator.
98 component of the path after splitting by the path separator.
98 For example, "foo/bar/baz" becomes "baz" and "foo/bar//" becomes "".
99 For example, "foo/bar/baz" becomes "baz" and "foo/bar//" becomes "".
99 """
100 """
100 return os.path.basename(path)
101 return os.path.basename(path)
101
102
103 @templatefilter('cbor')
104 def cbor(obj):
105 """Any object. Serializes the object to CBOR bytes."""
106 return b''.join(cborutil.streamencode(obj))
107
102 @templatefilter('commondir')
108 @templatefilter('commondir')
103 def commondir(filelist):
109 def commondir(filelist):
104 """List of text. Treats each list item as file name with /
110 """List of text. Treats each list item as file name with /
105 as path separator and returns the longest common directory
111 as path separator and returns the longest common directory
106 prefix shared by all list items.
112 prefix shared by all list items.
107 Returns the empty string if no common prefix exists.
113 Returns the empty string if no common prefix exists.
108
114
109 The list items are not normalized, i.e. "foo/../bar" is handled as
115 The list items are not normalized, i.e. "foo/../bar" is handled as
110 file "bar" in the directory "foo/..". Leading slashes are ignored.
116 file "bar" in the directory "foo/..". Leading slashes are ignored.
111
117
112 For example, ["foo/bar/baz", "foo/baz/bar"] becomes "foo" and
118 For example, ["foo/bar/baz", "foo/baz/bar"] becomes "foo" and
113 ["foo/bar", "baz"] becomes "".
119 ["foo/bar", "baz"] becomes "".
114 """
120 """
115 def common(a, b):
121 def common(a, b):
116 if len(a) > len(b):
122 if len(a) > len(b):
117 a = b[:len(a)]
123 a = b[:len(a)]
118 elif len(b) > len(a):
124 elif len(b) > len(a):
119 b = b[:len(a)]
125 b = b[:len(a)]
120 if a == b:
126 if a == b:
121 return a
127 return a
122 for i in pycompat.xrange(len(a)):
128 for i in pycompat.xrange(len(a)):
123 if a[i] != b[i]:
129 if a[i] != b[i]:
124 return a[:i]
130 return a[:i]
125 return a
131 return a
126 try:
132 try:
127 if not filelist:
133 if not filelist:
128 return ""
134 return ""
129 dirlist = [f.lstrip('/').split('/')[:-1] for f in filelist]
135 dirlist = [f.lstrip('/').split('/')[:-1] for f in filelist]
130 if len(dirlist) == 1:
136 if len(dirlist) == 1:
131 return '/'.join(dirlist[0])
137 return '/'.join(dirlist[0])
132 a = min(dirlist)
138 a = min(dirlist)
133 b = max(dirlist)
139 b = max(dirlist)
134 # The common prefix of a and b is shared with all
140 # The common prefix of a and b is shared with all
135 # elements of the list since Python sorts lexicographical
141 # elements of the list since Python sorts lexicographical
136 # and [1, x] after [1].
142 # and [1, x] after [1].
137 return '/'.join(common(a, b))
143 return '/'.join(common(a, b))
138 except TypeError:
144 except TypeError:
139 raise error.ParseError(_('argument is not a list of text'))
145 raise error.ParseError(_('argument is not a list of text'))
140
146
141 @templatefilter('count')
147 @templatefilter('count')
142 def count(i):
148 def count(i):
143 """List or text. Returns the length as an integer."""
149 """List or text. Returns the length as an integer."""
144 try:
150 try:
145 return len(i)
151 return len(i)
146 except TypeError:
152 except TypeError:
147 raise error.ParseError(_('not countable'))
153 raise error.ParseError(_('not countable'))
148
154
149 @templatefilter('dirname', intype=bytes)
155 @templatefilter('dirname', intype=bytes)
150 def dirname(path):
156 def dirname(path):
151 """Any text. Treats the text as a path, and strips the last
157 """Any text. Treats the text as a path, and strips the last
152 component of the path after splitting by the path separator.
158 component of the path after splitting by the path separator.
153 """
159 """
154 return os.path.dirname(path)
160 return os.path.dirname(path)
155
161
156 @templatefilter('domain', intype=bytes)
162 @templatefilter('domain', intype=bytes)
157 def domain(author):
163 def domain(author):
158 """Any text. Finds the first string that looks like an email
164 """Any text. Finds the first string that looks like an email
159 address, and extracts just the domain component. Example: ``User
165 address, and extracts just the domain component. Example: ``User
160 <user@example.com>`` becomes ``example.com``.
166 <user@example.com>`` becomes ``example.com``.
161 """
167 """
162 f = author.find('@')
168 f = author.find('@')
163 if f == -1:
169 if f == -1:
164 return ''
170 return ''
165 author = author[f + 1:]
171 author = author[f + 1:]
166 f = author.find('>')
172 f = author.find('>')
167 if f >= 0:
173 if f >= 0:
168 author = author[:f]
174 author = author[:f]
169 return author
175 return author
170
176
171 @templatefilter('email', intype=bytes)
177 @templatefilter('email', intype=bytes)
172 def email(text):
178 def email(text):
173 """Any text. Extracts the first string that looks like an email
179 """Any text. Extracts the first string that looks like an email
174 address. Example: ``User <user@example.com>`` becomes
180 address. Example: ``User <user@example.com>`` becomes
175 ``user@example.com``.
181 ``user@example.com``.
176 """
182 """
177 return stringutil.email(text)
183 return stringutil.email(text)
178
184
179 @templatefilter('escape', intype=bytes)
185 @templatefilter('escape', intype=bytes)
180 def escape(text):
186 def escape(text):
181 """Any text. Replaces the special XML/XHTML characters "&", "<"
187 """Any text. Replaces the special XML/XHTML characters "&", "<"
182 and ">" with XML entities, and filters out NUL characters.
188 and ">" with XML entities, and filters out NUL characters.
183 """
189 """
184 return url.escape(text.replace('\0', ''), True)
190 return url.escape(text.replace('\0', ''), True)
185
191
186 para_re = None
192 para_re = None
187 space_re = None
193 space_re = None
188
194
189 def fill(text, width, initindent='', hangindent=''):
195 def fill(text, width, initindent='', hangindent=''):
190 '''fill many paragraphs with optional indentation.'''
196 '''fill many paragraphs with optional indentation.'''
191 global para_re, space_re
197 global para_re, space_re
192 if para_re is None:
198 if para_re is None:
193 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
199 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
194 space_re = re.compile(br' +')
200 space_re = re.compile(br' +')
195
201
196 def findparas():
202 def findparas():
197 start = 0
203 start = 0
198 while True:
204 while True:
199 m = para_re.search(text, start)
205 m = para_re.search(text, start)
200 if not m:
206 if not m:
201 uctext = encoding.unifromlocal(text[start:])
207 uctext = encoding.unifromlocal(text[start:])
202 w = len(uctext)
208 w = len(uctext)
203 while w > 0 and uctext[w - 1].isspace():
209 while w > 0 and uctext[w - 1].isspace():
204 w -= 1
210 w -= 1
205 yield (encoding.unitolocal(uctext[:w]),
211 yield (encoding.unitolocal(uctext[:w]),
206 encoding.unitolocal(uctext[w:]))
212 encoding.unitolocal(uctext[w:]))
207 break
213 break
208 yield text[start:m.start(0)], m.group(1)
214 yield text[start:m.start(0)], m.group(1)
209 start = m.end(1)
215 start = m.end(1)
210
216
211 return "".join([stringutil.wrap(space_re.sub(' ',
217 return "".join([stringutil.wrap(space_re.sub(' ',
212 stringutil.wrap(para, width)),
218 stringutil.wrap(para, width)),
213 width, initindent, hangindent) + rest
219 width, initindent, hangindent) + rest
214 for para, rest in findparas()])
220 for para, rest in findparas()])
215
221
216 @templatefilter('fill68', intype=bytes)
222 @templatefilter('fill68', intype=bytes)
217 def fill68(text):
223 def fill68(text):
218 """Any text. Wraps the text to fit in 68 columns."""
224 """Any text. Wraps the text to fit in 68 columns."""
219 return fill(text, 68)
225 return fill(text, 68)
220
226
221 @templatefilter('fill76', intype=bytes)
227 @templatefilter('fill76', intype=bytes)
222 def fill76(text):
228 def fill76(text):
223 """Any text. Wraps the text to fit in 76 columns."""
229 """Any text. Wraps the text to fit in 76 columns."""
224 return fill(text, 76)
230 return fill(text, 76)
225
231
226 @templatefilter('firstline', intype=bytes)
232 @templatefilter('firstline', intype=bytes)
227 def firstline(text):
233 def firstline(text):
228 """Any text. Returns the first line of text."""
234 """Any text. Returns the first line of text."""
229 try:
235 try:
230 return text.splitlines(True)[0].rstrip('\r\n')
236 return text.splitlines(True)[0].rstrip('\r\n')
231 except IndexError:
237 except IndexError:
232 return ''
238 return ''
233
239
234 @templatefilter('hex', intype=bytes)
240 @templatefilter('hex', intype=bytes)
235 def hexfilter(text):
241 def hexfilter(text):
236 """Any text. Convert a binary Mercurial node identifier into
242 """Any text. Convert a binary Mercurial node identifier into
237 its long hexadecimal representation.
243 its long hexadecimal representation.
238 """
244 """
239 return node.hex(text)
245 return node.hex(text)
240
246
241 @templatefilter('hgdate', intype=templateutil.date)
247 @templatefilter('hgdate', intype=templateutil.date)
242 def hgdate(text):
248 def hgdate(text):
243 """Date. Returns the date as a pair of numbers: "1157407993
249 """Date. Returns the date as a pair of numbers: "1157407993
244 25200" (Unix timestamp, timezone offset).
250 25200" (Unix timestamp, timezone offset).
245 """
251 """
246 return "%d %d" % text
252 return "%d %d" % text
247
253
248 @templatefilter('isodate', intype=templateutil.date)
254 @templatefilter('isodate', intype=templateutil.date)
249 def isodate(text):
255 def isodate(text):
250 """Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
256 """Date. Returns the date in ISO 8601 format: "2009-08-18 13:00
251 +0200".
257 +0200".
252 """
258 """
253 return dateutil.datestr(text, '%Y-%m-%d %H:%M %1%2')
259 return dateutil.datestr(text, '%Y-%m-%d %H:%M %1%2')
254
260
255 @templatefilter('isodatesec', intype=templateutil.date)
261 @templatefilter('isodatesec', intype=templateutil.date)
256 def isodatesec(text):
262 def isodatesec(text):
257 """Date. Returns the date in ISO 8601 format, including
263 """Date. Returns the date in ISO 8601 format, including
258 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
264 seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date
259 filter.
265 filter.
260 """
266 """
261 return dateutil.datestr(text, '%Y-%m-%d %H:%M:%S %1%2')
267 return dateutil.datestr(text, '%Y-%m-%d %H:%M:%S %1%2')
262
268
263 def indent(text, prefix):
269 def indent(text, prefix):
264 '''indent each non-empty line of text after first with prefix.'''
270 '''indent each non-empty line of text after first with prefix.'''
265 lines = text.splitlines()
271 lines = text.splitlines()
266 num_lines = len(lines)
272 num_lines = len(lines)
267 endswithnewline = text[-1:] == '\n'
273 endswithnewline = text[-1:] == '\n'
268 def indenter():
274 def indenter():
269 for i in pycompat.xrange(num_lines):
275 for i in pycompat.xrange(num_lines):
270 l = lines[i]
276 l = lines[i]
271 if i and l.strip():
277 if i and l.strip():
272 yield prefix
278 yield prefix
273 yield l
279 yield l
274 if i < num_lines - 1 or endswithnewline:
280 if i < num_lines - 1 or endswithnewline:
275 yield '\n'
281 yield '\n'
276 return "".join(indenter())
282 return "".join(indenter())
277
283
278 @templatefilter('json')
284 @templatefilter('json')
279 def json(obj, paranoid=True):
285 def json(obj, paranoid=True):
280 """Any object. Serializes the object to a JSON formatted text."""
286 """Any object. Serializes the object to a JSON formatted text."""
281 if obj is None:
287 if obj is None:
282 return 'null'
288 return 'null'
283 elif obj is False:
289 elif obj is False:
284 return 'false'
290 return 'false'
285 elif obj is True:
291 elif obj is True:
286 return 'true'
292 return 'true'
287 elif isinstance(obj, (int, long, float)):
293 elif isinstance(obj, (int, long, float)):
288 return pycompat.bytestr(obj)
294 return pycompat.bytestr(obj)
289 elif isinstance(obj, bytes):
295 elif isinstance(obj, bytes):
290 return '"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
296 return '"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
291 elif isinstance(obj, type(u'')):
297 elif isinstance(obj, type(u'')):
292 raise error.ProgrammingError(
298 raise error.ProgrammingError(
293 'Mercurial only does output with bytes: %r' % obj)
299 'Mercurial only does output with bytes: %r' % obj)
294 elif util.safehasattr(obj, 'keys'):
300 elif util.safehasattr(obj, 'keys'):
295 out = ['"%s": %s' % (encoding.jsonescape(k, paranoid=paranoid),
301 out = ['"%s": %s' % (encoding.jsonescape(k, paranoid=paranoid),
296 json(v, paranoid))
302 json(v, paranoid))
297 for k, v in sorted(obj.iteritems())]
303 for k, v in sorted(obj.iteritems())]
298 return '{' + ', '.join(out) + '}'
304 return '{' + ', '.join(out) + '}'
299 elif util.safehasattr(obj, '__iter__'):
305 elif util.safehasattr(obj, '__iter__'):
300 out = [json(i, paranoid) for i in obj]
306 out = [json(i, paranoid) for i in obj]
301 return '[' + ', '.join(out) + ']'
307 return '[' + ', '.join(out) + ']'
302 raise error.ProgrammingError('cannot encode %r' % obj)
308 raise error.ProgrammingError('cannot encode %r' % obj)
303
309
304 @templatefilter('lower', intype=bytes)
310 @templatefilter('lower', intype=bytes)
305 def lower(text):
311 def lower(text):
306 """Any text. Converts the text to lowercase."""
312 """Any text. Converts the text to lowercase."""
307 return encoding.lower(text)
313 return encoding.lower(text)
308
314
309 @templatefilter('nonempty', intype=bytes)
315 @templatefilter('nonempty', intype=bytes)
310 def nonempty(text):
316 def nonempty(text):
311 """Any text. Returns '(none)' if the string is empty."""
317 """Any text. Returns '(none)' if the string is empty."""
312 return text or "(none)"
318 return text or "(none)"
313
319
314 @templatefilter('obfuscate', intype=bytes)
320 @templatefilter('obfuscate', intype=bytes)
315 def obfuscate(text):
321 def obfuscate(text):
316 """Any text. Returns the input text rendered as a sequence of
322 """Any text. Returns the input text rendered as a sequence of
317 XML entities.
323 XML entities.
318 """
324 """
319 text = unicode(text, pycompat.sysstr(encoding.encoding), r'replace')
325 text = unicode(text, pycompat.sysstr(encoding.encoding), r'replace')
320 return ''.join(['&#%d;' % ord(c) for c in text])
326 return ''.join(['&#%d;' % ord(c) for c in text])
321
327
322 @templatefilter('permissions', intype=bytes)
328 @templatefilter('permissions', intype=bytes)
323 def permissions(flags):
329 def permissions(flags):
324 if "l" in flags:
330 if "l" in flags:
325 return "lrwxrwxrwx"
331 return "lrwxrwxrwx"
326 if "x" in flags:
332 if "x" in flags:
327 return "-rwxr-xr-x"
333 return "-rwxr-xr-x"
328 return "-rw-r--r--"
334 return "-rw-r--r--"
329
335
330 @templatefilter('person', intype=bytes)
336 @templatefilter('person', intype=bytes)
331 def person(author):
337 def person(author):
332 """Any text. Returns the name before an email address,
338 """Any text. Returns the name before an email address,
333 interpreting it as per RFC 5322.
339 interpreting it as per RFC 5322.
334 """
340 """
335 return stringutil.person(author)
341 return stringutil.person(author)
336
342
337 @templatefilter('revescape', intype=bytes)
343 @templatefilter('revescape', intype=bytes)
338 def revescape(text):
344 def revescape(text):
339 """Any text. Escapes all "special" characters, except @.
345 """Any text. Escapes all "special" characters, except @.
340 Forward slashes are escaped twice to prevent web servers from prematurely
346 Forward slashes are escaped twice to prevent web servers from prematurely
341 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
347 unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
342 """
348 """
343 return urlreq.quote(text, safe='/@').replace('/', '%252F')
349 return urlreq.quote(text, safe='/@').replace('/', '%252F')
344
350
345 @templatefilter('rfc3339date', intype=templateutil.date)
351 @templatefilter('rfc3339date', intype=templateutil.date)
346 def rfc3339date(text):
352 def rfc3339date(text):
347 """Date. Returns a date using the Internet date format
353 """Date. Returns a date using the Internet date format
348 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
354 specified in RFC 3339: "2009-08-18T13:00:13+02:00".
349 """
355 """
350 return dateutil.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2")
356 return dateutil.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2")
351
357
352 @templatefilter('rfc822date', intype=templateutil.date)
358 @templatefilter('rfc822date', intype=templateutil.date)
353 def rfc822date(text):
359 def rfc822date(text):
354 """Date. Returns a date using the same format used in email
360 """Date. Returns a date using the same format used in email
355 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
361 headers: "Tue, 18 Aug 2009 13:00:13 +0200".
356 """
362 """
357 return dateutil.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2")
363 return dateutil.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2")
358
364
359 @templatefilter('short', intype=bytes)
365 @templatefilter('short', intype=bytes)
360 def short(text):
366 def short(text):
361 """Changeset hash. Returns the short form of a changeset hash,
367 """Changeset hash. Returns the short form of a changeset hash,
362 i.e. a 12 hexadecimal digit string.
368 i.e. a 12 hexadecimal digit string.
363 """
369 """
364 return text[:12]
370 return text[:12]
365
371
366 @templatefilter('shortbisect', intype=bytes)
372 @templatefilter('shortbisect', intype=bytes)
367 def shortbisect(label):
373 def shortbisect(label):
368 """Any text. Treats `label` as a bisection status, and
374 """Any text. Treats `label` as a bisection status, and
369 returns a single-character representing the status (G: good, B: bad,
375 returns a single-character representing the status (G: good, B: bad,
370 S: skipped, U: untested, I: ignored). Returns single space if `text`
376 S: skipped, U: untested, I: ignored). Returns single space if `text`
371 is not a valid bisection status.
377 is not a valid bisection status.
372 """
378 """
373 if label:
379 if label:
374 return label[0:1].upper()
380 return label[0:1].upper()
375 return ' '
381 return ' '
376
382
377 @templatefilter('shortdate', intype=templateutil.date)
383 @templatefilter('shortdate', intype=templateutil.date)
378 def shortdate(text):
384 def shortdate(text):
379 """Date. Returns a date like "2006-09-18"."""
385 """Date. Returns a date like "2006-09-18"."""
380 return dateutil.shortdate(text)
386 return dateutil.shortdate(text)
381
387
382 @templatefilter('slashpath', intype=bytes)
388 @templatefilter('slashpath', intype=bytes)
383 def slashpath(path):
389 def slashpath(path):
384 """Any text. Replaces the native path separator with slash."""
390 """Any text. Replaces the native path separator with slash."""
385 return util.pconvert(path)
391 return util.pconvert(path)
386
392
387 @templatefilter('splitlines', intype=bytes)
393 @templatefilter('splitlines', intype=bytes)
388 def splitlines(text):
394 def splitlines(text):
389 """Any text. Split text into a list of lines."""
395 """Any text. Split text into a list of lines."""
390 return templateutil.hybridlist(text.splitlines(), name='line')
396 return templateutil.hybridlist(text.splitlines(), name='line')
391
397
392 @templatefilter('stringescape', intype=bytes)
398 @templatefilter('stringescape', intype=bytes)
393 def stringescape(text):
399 def stringescape(text):
394 return stringutil.escapestr(text)
400 return stringutil.escapestr(text)
395
401
396 @templatefilter('stringify', intype=bytes)
402 @templatefilter('stringify', intype=bytes)
397 def stringify(thing):
403 def stringify(thing):
398 """Any type. Turns the value into text by converting values into
404 """Any type. Turns the value into text by converting values into
399 text and concatenating them.
405 text and concatenating them.
400 """
406 """
401 return thing # coerced by the intype
407 return thing # coerced by the intype
402
408
403 @templatefilter('stripdir', intype=bytes)
409 @templatefilter('stripdir', intype=bytes)
404 def stripdir(text):
410 def stripdir(text):
405 """Treat the text as path and strip a directory level, if
411 """Treat the text as path and strip a directory level, if
406 possible. For example, "foo" and "foo/bar" becomes "foo".
412 possible. For example, "foo" and "foo/bar" becomes "foo".
407 """
413 """
408 dir = os.path.dirname(text)
414 dir = os.path.dirname(text)
409 if dir == "":
415 if dir == "":
410 return os.path.basename(text)
416 return os.path.basename(text)
411 else:
417 else:
412 return dir
418 return dir
413
419
414 @templatefilter('tabindent', intype=bytes)
420 @templatefilter('tabindent', intype=bytes)
415 def tabindent(text):
421 def tabindent(text):
416 """Any text. Returns the text, with every non-empty line
422 """Any text. Returns the text, with every non-empty line
417 except the first starting with a tab character.
423 except the first starting with a tab character.
418 """
424 """
419 return indent(text, '\t')
425 return indent(text, '\t')
420
426
421 @templatefilter('upper', intype=bytes)
427 @templatefilter('upper', intype=bytes)
422 def upper(text):
428 def upper(text):
423 """Any text. Converts the text to uppercase."""
429 """Any text. Converts the text to uppercase."""
424 return encoding.upper(text)
430 return encoding.upper(text)
425
431
426 @templatefilter('urlescape', intype=bytes)
432 @templatefilter('urlescape', intype=bytes)
427 def urlescape(text):
433 def urlescape(text):
428 """Any text. Escapes all "special" characters. For example,
434 """Any text. Escapes all "special" characters. For example,
429 "foo bar" becomes "foo%20bar".
435 "foo bar" becomes "foo%20bar".
430 """
436 """
431 return urlreq.quote(text)
437 return urlreq.quote(text)
432
438
433 @templatefilter('user', intype=bytes)
439 @templatefilter('user', intype=bytes)
434 def userfilter(text):
440 def userfilter(text):
435 """Any text. Returns a short representation of a user name or email
441 """Any text. Returns a short representation of a user name or email
436 address."""
442 address."""
437 return stringutil.shortuser(text)
443 return stringutil.shortuser(text)
438
444
439 @templatefilter('emailuser', intype=bytes)
445 @templatefilter('emailuser', intype=bytes)
440 def emailuser(text):
446 def emailuser(text):
441 """Any text. Returns the user portion of an email address."""
447 """Any text. Returns the user portion of an email address."""
442 return stringutil.emailuser(text)
448 return stringutil.emailuser(text)
443
449
444 @templatefilter('utf8', intype=bytes)
450 @templatefilter('utf8', intype=bytes)
445 def utf8(text):
451 def utf8(text):
446 """Any text. Converts from the local character encoding to UTF-8."""
452 """Any text. Converts from the local character encoding to UTF-8."""
447 return encoding.fromlocal(text)
453 return encoding.fromlocal(text)
448
454
449 @templatefilter('xmlescape', intype=bytes)
455 @templatefilter('xmlescape', intype=bytes)
450 def xmlescape(text):
456 def xmlescape(text):
451 text = (text
457 text = (text
452 .replace('&', '&amp;')
458 .replace('&', '&amp;')
453 .replace('<', '&lt;')
459 .replace('<', '&lt;')
454 .replace('>', '&gt;')
460 .replace('>', '&gt;')
455 .replace('"', '&quot;')
461 .replace('"', '&quot;')
456 .replace("'", '&#39;')) # &apos; invalid in HTML
462 .replace("'", '&#39;')) # &apos; invalid in HTML
457 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
463 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
458
464
459 def websub(text, websubtable):
465 def websub(text, websubtable):
460 """:websub: Any text. Only applies to hgweb. Applies the regular
466 """:websub: Any text. Only applies to hgweb. Applies the regular
461 expression replacements defined in the websub section.
467 expression replacements defined in the websub section.
462 """
468 """
463 if websubtable:
469 if websubtable:
464 for regexp, format in websubtable:
470 for regexp, format in websubtable:
465 text = regexp.sub(format, text)
471 text = regexp.sub(format, text)
466 return text
472 return text
467
473
468 def loadfilter(ui, extname, registrarobj):
474 def loadfilter(ui, extname, registrarobj):
469 """Load template filter from specified registrarobj
475 """Load template filter from specified registrarobj
470 """
476 """
471 for name, func in registrarobj._table.iteritems():
477 for name, func in registrarobj._table.iteritems():
472 filters[name] = func
478 filters[name] = func
473
479
474 # tell hggettext to extract docstrings from these functions:
480 # tell hggettext to extract docstrings from these functions:
475 i18nfunctions = filters.values()
481 i18nfunctions = filters.values()
@@ -1,1579 +1,1605
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 search() function:
606 Test search() function:
607
607
608 $ hg log -R a -r2 -T '{desc}\n'
608 $ hg log -R a -r2 -T '{desc}\n'
609 no person
609 no person
610
610
611 $ hg log -R a -r2 -T '{search(r"p.*", desc)}\n'
611 $ hg log -R a -r2 -T '{search(r"p.*", desc)}\n'
612 person
612 person
613
613
614 as bool
614 as bool
615
615
616 $ hg log -R a -r2 -T '{if(search(r"p.*", desc), "", "not ")}found\n'
616 $ hg log -R a -r2 -T '{if(search(r"p.*", desc), "", "not ")}found\n'
617 found
617 found
618 $ hg log -R a -r2 -T '{if(search(r"q", desc), "", "not ")}found\n'
618 $ hg log -R a -r2 -T '{if(search(r"q", desc), "", "not ")}found\n'
619 not found
619 not found
620
620
621 match as json
621 match as json
622
622
623 $ hg log -R a -r2 -T '{search(r"(no) p.*", desc)|json}\n'
623 $ hg log -R a -r2 -T '{search(r"(no) p.*", desc)|json}\n'
624 {"0": "no person", "1": "no"}
624 {"0": "no person", "1": "no"}
625 $ hg log -R a -r2 -T '{search(r"q", desc)|json}\n'
625 $ hg log -R a -r2 -T '{search(r"q", desc)|json}\n'
626 null
626 null
627
627
628 group reference
628 group reference
629
629
630 $ hg log -R a -r2 -T '{search(r"(no) (p.*)", desc) % "{1|upper} {2|hex}"}\n'
630 $ hg log -R a -r2 -T '{search(r"(no) (p.*)", desc) % "{1|upper} {2|hex}"}\n'
631 NO 706572736f6e
631 NO 706572736f6e
632 $ hg log -R a -r2 -T '{search(r"(?P<foo>[a-z]*)", desc) % "{foo}"}\n'
632 $ hg log -R a -r2 -T '{search(r"(?P<foo>[a-z]*)", desc) % "{foo}"}\n'
633 no
633 no
634 $ hg log -R a -r2 -T '{search(r"(?P<foo>[a-z]*)", desc).foo}\n'
634 $ hg log -R a -r2 -T '{search(r"(?P<foo>[a-z]*)", desc).foo}\n'
635 no
635 no
636
636
637 group reference with no match
637 group reference with no match
638
638
639 $ hg log -R a -r2 -T '{search(r"q", desc) % "match: {0}"}\n'
639 $ hg log -R a -r2 -T '{search(r"q", desc) % "match: {0}"}\n'
640
640
641
641
642 bad group names
642 bad group names
643
643
644 $ hg log -R a -r2 -T '{search(r"(?P<0>.)", desc) % "{0}"}\n'
644 $ hg log -R a -r2 -T '{search(r"(?P<0>.)", desc) % "{0}"}\n'
645 hg: parse error: search got an invalid pattern: (?P<0>.)
645 hg: parse error: search got an invalid pattern: (?P<0>.)
646 [255]
646 [255]
647 $ hg log -R a -r2 -T '{search(r"(?P<repo>.)", desc) % "{repo}"}\n'
647 $ hg log -R a -r2 -T '{search(r"(?P<repo>.)", desc) % "{repo}"}\n'
648 hg: parse error: invalid group 'repo' in search pattern: (?P<repo>.)
648 hg: parse error: invalid group 'repo' in search pattern: (?P<repo>.)
649 [255]
649 [255]
650
650
651 Test the sub function of templating for expansion:
651 Test the sub function of templating for expansion:
652
652
653 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
653 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
654 xx
654 xx
655
655
656 $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
656 $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
657 hg: parse error: sub got an invalid pattern: [
657 hg: parse error: sub got an invalid pattern: [
658 [255]
658 [255]
659 $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
659 $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
660 hg: parse error: sub got an invalid replacement: \1
660 hg: parse error: sub got an invalid replacement: \1
661 [255]
661 [255]
662
662
663 Test the strip function with chars specified:
663 Test the strip function with chars specified:
664
664
665 $ hg log -R latesttag --template '{desc}\n'
665 $ hg log -R latesttag --template '{desc}\n'
666 at3
666 at3
667 t5
667 t5
668 t4
668 t4
669 t3
669 t3
670 t2
670 t2
671 t1
671 t1
672 merge
672 merge
673 h2e
673 h2e
674 h2d
674 h2d
675 h1c
675 h1c
676 b
676 b
677 a
677 a
678
678
679 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
679 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
680 at3
680 at3
681 5
681 5
682 4
682 4
683 3
683 3
684 2
684 2
685 1
685 1
686 merg
686 merg
687 h2
687 h2
688 h2d
688 h2d
689 h1c
689 h1c
690 b
690 b
691 a
691 a
692
692
693 Test date format:
693 Test date format:
694
694
695 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
695 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
696 date: 70 01 01 10 +0000
696 date: 70 01 01 10 +0000
697 date: 70 01 01 09 +0000
697 date: 70 01 01 09 +0000
698 date: 70 01 01 04 +0000
698 date: 70 01 01 04 +0000
699 date: 70 01 01 08 +0000
699 date: 70 01 01 08 +0000
700 date: 70 01 01 07 +0000
700 date: 70 01 01 07 +0000
701 date: 70 01 01 06 +0000
701 date: 70 01 01 06 +0000
702 date: 70 01 01 05 +0100
702 date: 70 01 01 05 +0100
703 date: 70 01 01 04 +0000
703 date: 70 01 01 04 +0000
704 date: 70 01 01 03 +0000
704 date: 70 01 01 03 +0000
705 date: 70 01 01 02 +0000
705 date: 70 01 01 02 +0000
706 date: 70 01 01 01 +0000
706 date: 70 01 01 01 +0000
707 date: 70 01 01 00 +0000
707 date: 70 01 01 00 +0000
708
708
709 Test invalid date:
709 Test invalid date:
710
710
711 $ hg log -R latesttag -T '{date(rev)}\n'
711 $ hg log -R latesttag -T '{date(rev)}\n'
712 hg: parse error: date expects a date information
712 hg: parse error: date expects a date information
713 [255]
713 [255]
714
714
715 Set up repository containing template fragments in commit metadata:
715 Set up repository containing template fragments in commit metadata:
716
716
717 $ hg init r
717 $ hg init r
718 $ cd r
718 $ cd r
719 $ echo a > a
719 $ echo a > a
720 $ hg ci -Am '{rev}'
720 $ hg ci -Am '{rev}'
721 adding a
721 adding a
722
722
723 $ hg branch -q 'text.{rev}'
723 $ hg branch -q 'text.{rev}'
724 $ echo aa >> aa
724 $ echo aa >> aa
725 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
725 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
726
726
727 color effect can be specified without quoting:
727 color effect can be specified without quoting:
728
728
729 $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
729 $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
730 \x1b[0;31mtext\x1b[0m (esc)
730 \x1b[0;31mtext\x1b[0m (esc)
731
731
732 color effects can be nested (issue5413)
732 color effects can be nested (issue5413)
733
733
734 $ hg debugtemplate --color=always \
734 $ hg debugtemplate --color=always \
735 > '{label(red, "red{label(magenta, "ma{label(cyan, "cyan")}{label(yellow, "yellow")}genta")}")}\n'
735 > '{label(red, "red{label(magenta, "ma{label(cyan, "cyan")}{label(yellow, "yellow")}genta")}")}\n'
736 \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)
736 \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)
737
737
738 pad() should interact well with color codes (issue5416)
738 pad() should interact well with color codes (issue5416)
739
739
740 $ hg debugtemplate --color=always \
740 $ hg debugtemplate --color=always \
741 > '{pad(label(red, "red"), 5, label(cyan, "-"))}\n'
741 > '{pad(label(red, "red"), 5, label(cyan, "-"))}\n'
742 \x1b[0;31mred\x1b[0m\x1b[0;36m-\x1b[0m\x1b[0;36m-\x1b[0m (esc)
742 \x1b[0;31mred\x1b[0m\x1b[0;36m-\x1b[0m\x1b[0;36m-\x1b[0m (esc)
743
743
744 pad() with truncate has to strip color codes, though
744 pad() with truncate has to strip color codes, though
745
745
746 $ hg debugtemplate --color=always \
746 $ hg debugtemplate --color=always \
747 > '{pad(label(red, "scarlet"), 5, truncate=true)}\n'
747 > '{pad(label(red, "scarlet"), 5, truncate=true)}\n'
748 scarl
748 scarl
749
749
750 label should be no-op if color is disabled:
750 label should be no-op if color is disabled:
751
751
752 $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
752 $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
753 text
753 text
754 $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
754 $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
755 text
755 text
756
756
757 Test branches inside if statement:
757 Test branches inside if statement:
758
758
759 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
759 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
760 no
760 no
761
761
762 Test dict constructor:
762 Test dict constructor:
763
763
764 $ hg log -r 0 -T '{dict(y=node|short, x=rev)}\n'
764 $ hg log -r 0 -T '{dict(y=node|short, x=rev)}\n'
765 y=f7769ec2ab97 x=0
765 y=f7769ec2ab97 x=0
766 $ hg log -r 0 -T '{dict(x=rev, y=node|short) % "{key}={value}\n"}'
766 $ hg log -r 0 -T '{dict(x=rev, y=node|short) % "{key}={value}\n"}'
767 x=0
767 x=0
768 y=f7769ec2ab97
768 y=f7769ec2ab97
769 $ hg log -r 0 -T '{dict(x=rev, y=node|short)|json}\n'
769 $ hg log -r 0 -T '{dict(x=rev, y=node|short)|json}\n'
770 {"x": 0, "y": "f7769ec2ab97"}
770 {"x": 0, "y": "f7769ec2ab97"}
771 $ hg log -r 0 -T '{dict()|json}\n'
771 $ hg log -r 0 -T '{dict()|json}\n'
772 {}
772 {}
773
773
774 $ hg log -r 0 -T '{dict(rev, node=node|short)}\n'
774 $ hg log -r 0 -T '{dict(rev, node=node|short)}\n'
775 rev=0 node=f7769ec2ab97
775 rev=0 node=f7769ec2ab97
776 $ hg log -r 0 -T '{dict(rev, node|short)}\n'
776 $ hg log -r 0 -T '{dict(rev, node|short)}\n'
777 rev=0 node=f7769ec2ab97
777 rev=0 node=f7769ec2ab97
778
778
779 $ hg log -r 0 -T '{dict(rev, rev=rev)}\n'
779 $ hg log -r 0 -T '{dict(rev, rev=rev)}\n'
780 hg: parse error: duplicated dict key 'rev' inferred
780 hg: parse error: duplicated dict key 'rev' inferred
781 [255]
781 [255]
782 $ hg log -r 0 -T '{dict(node, node|short)}\n'
782 $ hg log -r 0 -T '{dict(node, node|short)}\n'
783 hg: parse error: duplicated dict key 'node' inferred
783 hg: parse error: duplicated dict key 'node' inferred
784 [255]
784 [255]
785 $ hg log -r 0 -T '{dict(1 + 2)}'
785 $ hg log -r 0 -T '{dict(1 + 2)}'
786 hg: parse error: dict key cannot be inferred
786 hg: parse error: dict key cannot be inferred
787 [255]
787 [255]
788
788
789 $ hg log -r 0 -T '{dict(x=rev, x=node)}'
789 $ hg log -r 0 -T '{dict(x=rev, x=node)}'
790 hg: parse error: dict got multiple values for keyword argument 'x'
790 hg: parse error: dict got multiple values for keyword argument 'x'
791 [255]
791 [255]
792
792
793 Test get function:
793 Test get function:
794
794
795 $ hg log -r 0 --template '{get(extras, "branch")}\n'
795 $ hg log -r 0 --template '{get(extras, "branch")}\n'
796 default
796 default
797 $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
797 $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
798 default
798 default
799 $ hg log -r 0 --template '{get(files, "should_fail")}\n'
799 $ hg log -r 0 --template '{get(files, "should_fail")}\n'
800 hg: parse error: not a dictionary
800 hg: parse error: not a dictionary
801 (get() expects a dict as first argument)
801 (get() expects a dict as first argument)
802 [255]
802 [255]
803
803
804 Test json filter applied to wrapped object:
804 Test json filter applied to wrapped object:
805
805
806 $ hg log -r0 -T '{files|json}\n'
806 $ hg log -r0 -T '{files|json}\n'
807 ["a"]
807 ["a"]
808 $ hg log -r0 -T '{extras|json}\n'
808 $ hg log -r0 -T '{extras|json}\n'
809 {"branch": "default"}
809 {"branch": "default"}
810 $ hg log -r0 -T '{date|json}\n'
810 $ hg log -r0 -T '{date|json}\n'
811 [0, 0]
811 [0, 0]
812
812
813 Test json filter applied to map result:
813 Test json filter applied to map result:
814
814
815 $ hg log -r0 -T '{json(extras % "{key}")}\n'
815 $ hg log -r0 -T '{json(extras % "{key}")}\n'
816 ["branch"]
816 ["branch"]
817
817
818 Test localdate(date, tz) function:
818 Test localdate(date, tz) function:
819
819
820 $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
820 $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
821 1970-01-01 09:00 +0900
821 1970-01-01 09:00 +0900
822 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
822 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
823 1970-01-01 00:00 +0000
823 1970-01-01 00:00 +0000
824 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
824 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
825 hg: parse error: localdate expects a timezone
825 hg: parse error: localdate expects a timezone
826 [255]
826 [255]
827 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
827 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
828 1970-01-01 02:00 +0200
828 1970-01-01 02:00 +0200
829 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
829 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
830 1970-01-01 00:00 +0000
830 1970-01-01 00:00 +0000
831 $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
831 $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
832 1970-01-01 00:00 +0000
832 1970-01-01 00:00 +0000
833 $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
833 $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
834 hg: parse error: localdate expects a timezone
834 hg: parse error: localdate expects a timezone
835 [255]
835 [255]
836 $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
836 $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
837 hg: parse error: localdate expects a timezone
837 hg: parse error: localdate expects a timezone
838 [255]
838 [255]
839
839
840 Test shortest(node) function:
840 Test shortest(node) function:
841
841
842 $ echo b > b
842 $ echo b > b
843 $ hg ci -qAm b
843 $ hg ci -qAm b
844 $ hg log --template '{shortest(node)}\n'
844 $ hg log --template '{shortest(node)}\n'
845 e777
845 e777
846 bcc7
846 bcc7
847 f776
847 f776
848 $ hg log --template '{shortest(node, 10)}\n'
848 $ hg log --template '{shortest(node, 10)}\n'
849 e777603221
849 e777603221
850 bcc7ff960b
850 bcc7ff960b
851 f7769ec2ab
851 f7769ec2ab
852 $ hg log --template '{shortest(node, 1)}\n' -r null
852 $ hg log --template '{shortest(node, 1)}\n' -r null
853 00
853 00
854 $ hg log --template '{node|shortest}\n' -l1
854 $ hg log --template '{node|shortest}\n' -l1
855 e777
855 e777
856
856
857 $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
857 $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
858 f7769ec2ab
858 f7769ec2ab
859 $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
859 $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
860 hg: parse error: shortest() expects an integer minlength
860 hg: parse error: shortest() expects an integer minlength
861 [255]
861 [255]
862
862
863 $ hg log -r 'wdir()' -T '{node|shortest}\n'
863 $ hg log -r 'wdir()' -T '{node|shortest}\n'
864 ffff
864 ffff
865
865
866 $ hg log --template '{shortest("f")}\n' -l1
866 $ hg log --template '{shortest("f")}\n' -l1
867 f
867 f
868
868
869 $ hg log --template '{shortest("0123456789012345678901234567890123456789")}\n' -l1
869 $ hg log --template '{shortest("0123456789012345678901234567890123456789")}\n' -l1
870 0123456789012345678901234567890123456789
870 0123456789012345678901234567890123456789
871
871
872 $ hg log --template '{shortest("01234567890123456789012345678901234567890123456789")}\n' -l1
872 $ hg log --template '{shortest("01234567890123456789012345678901234567890123456789")}\n' -l1
873 01234567890123456789012345678901234567890123456789
873 01234567890123456789012345678901234567890123456789
874
874
875 $ hg log --template '{shortest("not a hex string")}\n' -l1
875 $ hg log --template '{shortest("not a hex string")}\n' -l1
876 not a hex string
876 not a hex string
877
877
878 $ hg log --template '{shortest("not a hex string, but it'\''s 40 bytes long")}\n' -l1
878 $ hg log --template '{shortest("not a hex string, but it'\''s 40 bytes long")}\n' -l1
879 not a hex string, but it's 40 bytes long
879 not a hex string, but it's 40 bytes long
880
880
881 $ hg log --template '{shortest("ffffffffffffffffffffffffffffffffffffffff")}\n' -l1
881 $ hg log --template '{shortest("ffffffffffffffffffffffffffffffffffffffff")}\n' -l1
882 ffff
882 ffff
883
883
884 $ hg log --template '{shortest("fffffff")}\n' -l1
884 $ hg log --template '{shortest("fffffff")}\n' -l1
885 ffff
885 ffff
886
886
887 $ hg log --template '{shortest("ff")}\n' -l1
887 $ hg log --template '{shortest("ff")}\n' -l1
888 ffff
888 ffff
889
889
890 $ cd ..
890 $ cd ..
891
891
892 Test shortest(node) with the repo having short hash collision:
892 Test shortest(node) with the repo having short hash collision:
893
893
894 $ hg init hashcollision
894 $ hg init hashcollision
895 $ cd hashcollision
895 $ cd hashcollision
896 $ cat <<EOF >> .hg/hgrc
896 $ cat <<EOF >> .hg/hgrc
897 > [experimental]
897 > [experimental]
898 > evolution.createmarkers=True
898 > evolution.createmarkers=True
899 > EOF
899 > EOF
900 $ echo 0 > a
900 $ echo 0 > a
901 $ hg ci -qAm 0
901 $ hg ci -qAm 0
902 $ for i in 17 129 248 242 480 580 617 1057 2857 4025; do
902 $ for i in 17 129 248 242 480 580 617 1057 2857 4025; do
903 > hg up -q 0
903 > hg up -q 0
904 > echo $i > a
904 > echo $i > a
905 > hg ci -qm $i
905 > hg ci -qm $i
906 > done
906 > done
907 $ hg up -q null
907 $ hg up -q null
908 $ hg log -r0: -T '{rev}:{node}\n'
908 $ hg log -r0: -T '{rev}:{node}\n'
909 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
909 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
910 1:11424df6dc1dd4ea255eae2b58eaca7831973bbc
910 1:11424df6dc1dd4ea255eae2b58eaca7831973bbc
911 2:11407b3f1b9c3e76a79c1ec5373924df096f0499
911 2:11407b3f1b9c3e76a79c1ec5373924df096f0499
912 3:11dd92fe0f39dfdaacdaa5f3997edc533875cfc4
912 3:11dd92fe0f39dfdaacdaa5f3997edc533875cfc4
913 4:10776689e627b465361ad5c296a20a487e153ca4
913 4:10776689e627b465361ad5c296a20a487e153ca4
914 5:a00be79088084cb3aff086ab799f8790e01a976b
914 5:a00be79088084cb3aff086ab799f8790e01a976b
915 6:a0b0acd79b4498d0052993d35a6a748dd51d13e6
915 6:a0b0acd79b4498d0052993d35a6a748dd51d13e6
916 7:a0457b3450b8e1b778f1163b31a435802987fe5d
916 7:a0457b3450b8e1b778f1163b31a435802987fe5d
917 8:c56256a09cd28e5764f32e8e2810d0f01e2e357a
917 8:c56256a09cd28e5764f32e8e2810d0f01e2e357a
918 9:c5623987d205cd6d9d8389bfc40fff9dbb670b48
918 9:c5623987d205cd6d9d8389bfc40fff9dbb670b48
919 10:c562ddd9c94164376c20b86b0b4991636a3bf84f
919 10:c562ddd9c94164376c20b86b0b4991636a3bf84f
920 $ hg debugobsolete a00be79088084cb3aff086ab799f8790e01a976b
920 $ hg debugobsolete a00be79088084cb3aff086ab799f8790e01a976b
921 obsoleted 1 changesets
921 obsoleted 1 changesets
922 $ hg debugobsolete c5623987d205cd6d9d8389bfc40fff9dbb670b48
922 $ hg debugobsolete c5623987d205cd6d9d8389bfc40fff9dbb670b48
923 obsoleted 1 changesets
923 obsoleted 1 changesets
924 $ hg debugobsolete c562ddd9c94164376c20b86b0b4991636a3bf84f
924 $ hg debugobsolete c562ddd9c94164376c20b86b0b4991636a3bf84f
925 obsoleted 1 changesets
925 obsoleted 1 changesets
926
926
927 nodes starting with '11' (we don't have the revision number '11' though)
927 nodes starting with '11' (we don't have the revision number '11' though)
928
928
929 $ hg log -r 1:3 -T '{rev}:{shortest(node, 0)}\n'
929 $ hg log -r 1:3 -T '{rev}:{shortest(node, 0)}\n'
930 1:1142
930 1:1142
931 2:1140
931 2:1140
932 3:11d
932 3:11d
933
933
934 '5:a00' is hidden, but still we have two nodes starting with 'a0'
934 '5:a00' is hidden, but still we have two nodes starting with 'a0'
935
935
936 $ hg log -r 6:7 -T '{rev}:{shortest(node, 0)}\n'
936 $ hg log -r 6:7 -T '{rev}:{shortest(node, 0)}\n'
937 6:a0b
937 6:a0b
938 7:a04
938 7:a04
939
939
940 node '10' conflicts with the revision number '10' even if it is hidden
940 node '10' conflicts with the revision number '10' even if it is hidden
941 (we could exclude hidden revision numbers, but currently we don't)
941 (we could exclude hidden revision numbers, but currently we don't)
942
942
943 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n'
943 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n'
944 4:107
944 4:107
945 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
945 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
946 4:107
946 4:107
947
947
948 $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n'
948 $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n'
949 4:x10
949 4:x10
950 $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
950 $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
951 4:x10
951 4:x10
952
952
953 node 'c562' should be unique if the other 'c562' nodes are hidden
953 node 'c562' should be unique if the other 'c562' nodes are hidden
954 (but we don't try the slow path to filter out hidden nodes for now)
954 (but we don't try the slow path to filter out hidden nodes for now)
955
955
956 $ hg log -r 8 -T '{rev}:{node|shortest}\n'
956 $ hg log -r 8 -T '{rev}:{node|shortest}\n'
957 8:c5625
957 8:c5625
958 $ hg log -r 8:10 -T '{rev}:{node|shortest}\n' --hidden
958 $ hg log -r 8:10 -T '{rev}:{node|shortest}\n' --hidden
959 8:c5625
959 8:c5625
960 9:c5623
960 9:c5623
961 10:c562d
961 10:c562d
962
962
963 $ cd ..
963 $ cd ..
964
964
965 Test prefixhexnode when the first character of the hash is 0.
965 Test prefixhexnode when the first character of the hash is 0.
966 $ hg init hashcollision2
966 $ hg init hashcollision2
967 $ cd hashcollision2
967 $ cd hashcollision2
968 $ cat <<EOF >> .hg/hgrc
968 $ cat <<EOF >> .hg/hgrc
969 > [experimental]
969 > [experimental]
970 > evolution.createmarkers=True
970 > evolution.createmarkers=True
971 > EOF
971 > EOF
972 $ echo 0 > a
972 $ echo 0 > a
973 $ hg ci -qAm 0
973 $ hg ci -qAm 0
974 $ echo 21 > a
974 $ echo 21 > a
975 $ hg ci -qm 21
975 $ hg ci -qm 21
976 $ hg up -q null
976 $ hg up -q null
977 $ hg log -r0: -T '{rev}:{node}\n'
977 $ hg log -r0: -T '{rev}:{node}\n'
978 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
978 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
979 1:0cf177ba2b1dc3862a00fb81715fec90950201be
979 1:0cf177ba2b1dc3862a00fb81715fec90950201be
980
980
981 we need the 'x' prefix to ensure we aren't colliding with rev0. We identify
981 we need the 'x' prefix to ensure we aren't colliding with rev0. We identify
982 the collision with nullid if we aren't using disambiguatewithin, so we need to set
982 the collision with nullid if we aren't using disambiguatewithin, so we need to set
983 that as well.
983 that as well.
984 $ hg --config experimental.revisions.disambiguatewithin='descendants(0)' \
984 $ hg --config experimental.revisions.disambiguatewithin='descendants(0)' \
985 > --config experimental.revisions.prefixhexnode=yes \
985 > --config experimental.revisions.prefixhexnode=yes \
986 > log -r 1 -T '{rev}:{shortest(node, 0)}\n'
986 > log -r 1 -T '{rev}:{shortest(node, 0)}\n'
987 1:x0
987 1:x0
988
988
989 $ hg debugobsolete 0cf177ba2b1dc3862a00fb81715fec90950201be
989 $ hg debugobsolete 0cf177ba2b1dc3862a00fb81715fec90950201be
990 obsoleted 1 changesets
990 obsoleted 1 changesets
991 $ hg up -q 0
991 $ hg up -q 0
992 $ echo 61 > a
992 $ echo 61 > a
993 $ hg ci -m 61
993 $ hg ci -m 61
994 $ hg log -r0: -T '{rev}:{node}\n'
994 $ hg log -r0: -T '{rev}:{node}\n'
995 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
995 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
996 2:01384dde84b3a511ae0835f35ac40bd806c99bb8
996 2:01384dde84b3a511ae0835f35ac40bd806c99bb8
997
997
998 we still have the 'x' prefix because '0' is still the shortest prefix, since
998 we still have the 'x' prefix because '0' is still the shortest prefix, since
999 rev1's '0c' is hidden.
999 rev1's '0c' is hidden.
1000 $ hg --config experimental.revisions.disambiguatewithin=0:-1-0 \
1000 $ hg --config experimental.revisions.disambiguatewithin=0:-1-0 \
1001 > --config experimental.revisions.prefixhexnode=yes \
1001 > --config experimental.revisions.prefixhexnode=yes \
1002 > log -r 0:-1-0 -T '{rev}:{shortest(node, 0)}\n'
1002 > log -r 0:-1-0 -T '{rev}:{shortest(node, 0)}\n'
1003 2:x0
1003 2:x0
1004
1004
1005 we don't have the 'x' prefix on 2 because '01' is not a synonym for rev1.
1005 we don't have the 'x' prefix on 2 because '01' is not a synonym for rev1.
1006 $ hg --config experimental.revisions.disambiguatewithin=0:-1-0 \
1006 $ hg --config experimental.revisions.disambiguatewithin=0:-1-0 \
1007 > --config experimental.revisions.prefixhexnode=yes \
1007 > --config experimental.revisions.prefixhexnode=yes \
1008 > log -r 0:-1-0 -T '{rev}:{shortest(node, 0)}\n' --hidden
1008 > log -r 0:-1-0 -T '{rev}:{shortest(node, 0)}\n' --hidden
1009 1:0c
1009 1:0c
1010 2:01
1010 2:01
1011
1011
1012 $ cd ..
1012 $ cd ..
1013
1013
1014 Test pad function
1014 Test pad function
1015
1015
1016 $ cd r
1016 $ cd r
1017
1017
1018 $ hg log --template '{pad(rev, 20)} {author|user}\n'
1018 $ hg log --template '{pad(rev, 20)} {author|user}\n'
1019 2 test
1019 2 test
1020 1 {node|short}
1020 1 {node|short}
1021 0 test
1021 0 test
1022
1022
1023 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
1023 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
1024 2 test
1024 2 test
1025 1 {node|short}
1025 1 {node|short}
1026 0 test
1026 0 test
1027
1027
1028 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
1028 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
1029 2------------------- test
1029 2------------------- test
1030 1------------------- {node|short}
1030 1------------------- {node|short}
1031 0------------------- test
1031 0------------------- test
1032
1032
1033 $ hg log --template '{pad(author, 5, "-", False, True)}\n'
1033 $ hg log --template '{pad(author, 5, "-", False, True)}\n'
1034 test-
1034 test-
1035 {node
1035 {node
1036 test-
1036 test-
1037 $ hg log --template '{pad(author, 5, "-", True, True)}\n'
1037 $ hg log --template '{pad(author, 5, "-", True, True)}\n'
1038 -test
1038 -test
1039 hort}
1039 hort}
1040 -test
1040 -test
1041
1041
1042 Test template string in pad function
1042 Test template string in pad function
1043
1043
1044 $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
1044 $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
1045 {0} test
1045 {0} test
1046
1046
1047 $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
1047 $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
1048 \{rev} test
1048 \{rev} test
1049
1049
1050 Test width argument passed to pad function
1050 Test width argument passed to pad function
1051
1051
1052 $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
1052 $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
1053 0 test
1053 0 test
1054 $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
1054 $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
1055 hg: parse error: pad() expects an integer width
1055 hg: parse error: pad() expects an integer width
1056 [255]
1056 [255]
1057
1057
1058 Test invalid fillchar passed to pad function
1058 Test invalid fillchar passed to pad function
1059
1059
1060 $ hg log -r 0 -T '{pad(rev, 10, "")}\n'
1060 $ hg log -r 0 -T '{pad(rev, 10, "")}\n'
1061 hg: parse error: pad() expects a single fill character
1061 hg: parse error: pad() expects a single fill character
1062 [255]
1062 [255]
1063 $ hg log -r 0 -T '{pad(rev, 10, "--")}\n'
1063 $ hg log -r 0 -T '{pad(rev, 10, "--")}\n'
1064 hg: parse error: pad() expects a single fill character
1064 hg: parse error: pad() expects a single fill character
1065 [255]
1065 [255]
1066
1066
1067 Test boolean argument passed to pad function
1067 Test boolean argument passed to pad function
1068
1068
1069 no crash
1069 no crash
1070
1070
1071 $ hg log -r 0 -T '{pad(rev, 10, "-", "f{"oo"}")}\n'
1071 $ hg log -r 0 -T '{pad(rev, 10, "-", "f{"oo"}")}\n'
1072 ---------0
1072 ---------0
1073
1073
1074 string/literal
1074 string/literal
1075
1075
1076 $ hg log -r 0 -T '{pad(rev, 10, "-", "false")}\n'
1076 $ hg log -r 0 -T '{pad(rev, 10, "-", "false")}\n'
1077 ---------0
1077 ---------0
1078 $ hg log -r 0 -T '{pad(rev, 10, "-", false)}\n'
1078 $ hg log -r 0 -T '{pad(rev, 10, "-", false)}\n'
1079 0---------
1079 0---------
1080 $ hg log -r 0 -T '{pad(rev, 10, "-", "")}\n'
1080 $ hg log -r 0 -T '{pad(rev, 10, "-", "")}\n'
1081 0---------
1081 0---------
1082
1082
1083 unknown keyword is evaluated to ''
1083 unknown keyword is evaluated to ''
1084
1084
1085 $ hg log -r 0 -T '{pad(rev, 10, "-", unknownkeyword)}\n'
1085 $ hg log -r 0 -T '{pad(rev, 10, "-", unknownkeyword)}\n'
1086 0---------
1086 0---------
1087
1087
1088 Test separate function
1088 Test separate function
1089
1089
1090 $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
1090 $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
1091 a-b-c
1091 a-b-c
1092 $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
1092 $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
1093 0:f7769ec2ab97 test default
1093 0:f7769ec2ab97 test default
1094 $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
1094 $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
1095 a \x1b[0;31mb\x1b[0m c d (esc)
1095 a \x1b[0;31mb\x1b[0m c d (esc)
1096
1096
1097 Test boolean expression/literal passed to if function
1097 Test boolean expression/literal passed to if function
1098
1098
1099 $ hg log -r 0 -T '{if(rev, "rev 0 is True")}\n'
1099 $ hg log -r 0 -T '{if(rev, "rev 0 is True")}\n'
1100 rev 0 is True
1100 rev 0 is True
1101 $ hg log -r 0 -T '{if(0, "literal 0 is True as well")}\n'
1101 $ hg log -r 0 -T '{if(0, "literal 0 is True as well")}\n'
1102 literal 0 is True as well
1102 literal 0 is True as well
1103 $ hg log -r 0 -T '{if(min(revset(r"0")), "0 of hybriditem is also True")}\n'
1103 $ hg log -r 0 -T '{if(min(revset(r"0")), "0 of hybriditem is also True")}\n'
1104 0 of hybriditem is also True
1104 0 of hybriditem is also True
1105 $ hg log -r 0 -T '{if("", "", "empty string is False")}\n'
1105 $ hg log -r 0 -T '{if("", "", "empty string is False")}\n'
1106 empty string is False
1106 empty string is False
1107 $ hg log -r 0 -T '{if(revset(r"0 - 0"), "", "empty list is False")}\n'
1107 $ hg log -r 0 -T '{if(revset(r"0 - 0"), "", "empty list is False")}\n'
1108 empty list is False
1108 empty list is False
1109 $ hg log -r 0 -T '{if(revset(r"0"), "non-empty list is True")}\n'
1109 $ hg log -r 0 -T '{if(revset(r"0"), "non-empty list is True")}\n'
1110 non-empty list is True
1110 non-empty list is True
1111 $ hg log -r 0 -T '{if(revset(r"0") % "", "list of empty strings is True")}\n'
1111 $ hg log -r 0 -T '{if(revset(r"0") % "", "list of empty strings is True")}\n'
1112 list of empty strings is True
1112 list of empty strings is True
1113 $ hg log -r 0 -T '{if(true, "true is True")}\n'
1113 $ hg log -r 0 -T '{if(true, "true is True")}\n'
1114 true is True
1114 true is True
1115 $ hg log -r 0 -T '{if(false, "", "false is False")}\n'
1115 $ hg log -r 0 -T '{if(false, "", "false is False")}\n'
1116 false is False
1116 false is False
1117 $ hg log -r 0 -T '{if("false", "non-empty string is True")}\n'
1117 $ hg log -r 0 -T '{if("false", "non-empty string is True")}\n'
1118 non-empty string is True
1118 non-empty string is True
1119
1119
1120 Test ifcontains function
1120 Test ifcontains function
1121
1121
1122 $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
1122 $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
1123 2 is in the string
1123 2 is in the string
1124 1 is not
1124 1 is not
1125 0 is in the string
1125 0 is in the string
1126
1126
1127 $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
1127 $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
1128 2 is in the string
1128 2 is in the string
1129 1 is not
1129 1 is not
1130 0 is in the string
1130 0 is in the string
1131
1131
1132 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
1132 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
1133 2 did not add a
1133 2 did not add a
1134 1 did not add a
1134 1 did not add a
1135 0 added a
1135 0 added a
1136
1136
1137 $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
1137 $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
1138 2 is parent of 1
1138 2 is parent of 1
1139 1
1139 1
1140 0
1140 0
1141
1141
1142 $ hg log -l1 -T '{ifcontains("branch", extras, "t", "f")}\n'
1142 $ hg log -l1 -T '{ifcontains("branch", extras, "t", "f")}\n'
1143 t
1143 t
1144 $ hg log -l1 -T '{ifcontains("branch", extras % "{key}", "t", "f")}\n'
1144 $ hg log -l1 -T '{ifcontains("branch", extras % "{key}", "t", "f")}\n'
1145 t
1145 t
1146 $ hg log -l1 -T '{ifcontains("branc", extras % "{key}", "t", "f")}\n'
1146 $ hg log -l1 -T '{ifcontains("branc", extras % "{key}", "t", "f")}\n'
1147 f
1147 f
1148 $ hg log -l1 -T '{ifcontains("branc", stringify(extras % "{key}"), "t", "f")}\n'
1148 $ hg log -l1 -T '{ifcontains("branc", stringify(extras % "{key}"), "t", "f")}\n'
1149 t
1149 t
1150
1150
1151 Test revset function
1151 Test revset function
1152
1152
1153 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
1153 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
1154 2 current rev
1154 2 current rev
1155 1 not current rev
1155 1 not current rev
1156 0 not current rev
1156 0 not current rev
1157
1157
1158 $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
1158 $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
1159 2 match rev
1159 2 match rev
1160 1 match rev
1160 1 match rev
1161 0 not match rev
1161 0 not match rev
1162
1162
1163 $ hg log -T '{ifcontains(desc, revset(":"), "", "type not match")}\n' -l1
1163 $ hg log -T '{ifcontains(desc, revset(":"), "", "type not match")}\n' -l1
1164 type not match
1164 type not match
1165
1165
1166 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
1166 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
1167 2 Parents: 1
1167 2 Parents: 1
1168 1 Parents: 0
1168 1 Parents: 0
1169 0 Parents:
1169 0 Parents:
1170
1170
1171 $ cat >> .hg/hgrc <<EOF
1171 $ cat >> .hg/hgrc <<EOF
1172 > [revsetalias]
1172 > [revsetalias]
1173 > myparents(\$1) = parents(\$1)
1173 > myparents(\$1) = parents(\$1)
1174 > EOF
1174 > EOF
1175 $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
1175 $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
1176 2 Parents: 1
1176 2 Parents: 1
1177 1 Parents: 0
1177 1 Parents: 0
1178 0 Parents:
1178 0 Parents:
1179
1179
1180 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
1180 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
1181 Rev: 2
1181 Rev: 2
1182 Ancestor: 0
1182 Ancestor: 0
1183 Ancestor: 1
1183 Ancestor: 1
1184 Ancestor: 2
1184 Ancestor: 2
1185
1185
1186 Rev: 1
1186 Rev: 1
1187 Ancestor: 0
1187 Ancestor: 0
1188 Ancestor: 1
1188 Ancestor: 1
1189
1189
1190 Rev: 0
1190 Rev: 0
1191 Ancestor: 0
1191 Ancestor: 0
1192
1192
1193 $ hg log --template '{revset("TIP"|lower)}\n' -l1
1193 $ hg log --template '{revset("TIP"|lower)}\n' -l1
1194 2
1194 2
1195
1195
1196 $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
1196 $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
1197 2
1197 2
1198
1198
1199 a list template is evaluated for each item of revset/parents
1199 a list template is evaluated for each item of revset/parents
1200
1200
1201 $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
1201 $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
1202 2 p: 1:bcc7ff960b8e
1202 2 p: 1:bcc7ff960b8e
1203 1 p: 0:f7769ec2ab97
1203 1 p: 0:f7769ec2ab97
1204 0 p:
1204 0 p:
1205
1205
1206 $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
1206 $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
1207 2 p: 1:bcc7ff960b8e -1:000000000000
1207 2 p: 1:bcc7ff960b8e -1:000000000000
1208 1 p: 0:f7769ec2ab97 -1:000000000000
1208 1 p: 0:f7769ec2ab97 -1:000000000000
1209 0 p: -1:000000000000 -1:000000000000
1209 0 p: -1:000000000000 -1:000000000000
1210
1210
1211 therefore, 'revcache' should be recreated for each rev
1211 therefore, 'revcache' should be recreated for each rev
1212
1212
1213 $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
1213 $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
1214 2 aa b
1214 2 aa b
1215 p
1215 p
1216 1
1216 1
1217 p a
1217 p a
1218 0 a
1218 0 a
1219 p
1219 p
1220
1220
1221 $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
1221 $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
1222 2 aa b
1222 2 aa b
1223 p
1223 p
1224 1
1224 1
1225 p a
1225 p a
1226 0 a
1226 0 a
1227 p
1227 p
1228
1228
1229 a revset item must be evaluated as an integer revision, not an offset from tip
1229 a revset item must be evaluated as an integer revision, not an offset from tip
1230
1230
1231 $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
1231 $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
1232 -1:000000000000
1232 -1:000000000000
1233 $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
1233 $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
1234 -1:000000000000
1234 -1:000000000000
1235
1235
1236 join() should pick '{rev}' from revset items:
1236 join() should pick '{rev}' from revset items:
1237
1237
1238 $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
1238 $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
1239 4, 5
1239 4, 5
1240
1240
1241 on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
1241 on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
1242 default. join() should agree with the default formatting:
1242 default. join() should agree with the default formatting:
1243
1243
1244 $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
1244 $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
1245 5:13207e5a10d9, 4:bbe44766e73d
1245 5:13207e5a10d9, 4:bbe44766e73d
1246
1246
1247 $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
1247 $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
1248 5:13207e5a10d9fd28ec424934298e176197f2c67f,
1248 5:13207e5a10d9fd28ec424934298e176197f2c67f,
1249 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1249 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1250
1250
1251 Invalid arguments passed to revset()
1251 Invalid arguments passed to revset()
1252
1252
1253 $ hg log -T '{revset("%whatever", 0)}\n'
1253 $ hg log -T '{revset("%whatever", 0)}\n'
1254 hg: parse error: unexpected revspec format character w
1254 hg: parse error: unexpected revspec format character w
1255 [255]
1255 [255]
1256 $ hg log -T '{revset("%lwhatever", files)}\n'
1256 $ hg log -T '{revset("%lwhatever", files)}\n'
1257 hg: parse error: unexpected revspec format character w
1257 hg: parse error: unexpected revspec format character w
1258 [255]
1258 [255]
1259 $ hg log -T '{revset("%s %s", 0)}\n'
1259 $ hg log -T '{revset("%s %s", 0)}\n'
1260 hg: parse error: missing argument for revspec
1260 hg: parse error: missing argument for revspec
1261 [255]
1261 [255]
1262 $ hg log -T '{revset("", 0)}\n'
1262 $ hg log -T '{revset("", 0)}\n'
1263 hg: parse error: too many revspec arguments specified
1263 hg: parse error: too many revspec arguments specified
1264 [255]
1264 [255]
1265 $ hg log -T '{revset("%s", 0, 1)}\n'
1265 $ hg log -T '{revset("%s", 0, 1)}\n'
1266 hg: parse error: too many revspec arguments specified
1266 hg: parse error: too many revspec arguments specified
1267 [255]
1267 [255]
1268 $ hg log -T '{revset("%", 0)}\n'
1268 $ hg log -T '{revset("%", 0)}\n'
1269 hg: parse error: incomplete revspec format character
1269 hg: parse error: incomplete revspec format character
1270 [255]
1270 [255]
1271 $ hg log -T '{revset("%l", 0)}\n'
1271 $ hg log -T '{revset("%l", 0)}\n'
1272 hg: parse error: incomplete revspec format character
1272 hg: parse error: incomplete revspec format character
1273 [255]
1273 [255]
1274 $ hg log -T '{revset("%d", 'foo')}\n'
1274 $ hg log -T '{revset("%d", 'foo')}\n'
1275 hg: parse error: invalid argument for revspec
1275 hg: parse error: invalid argument for revspec
1276 [255]
1276 [255]
1277 $ hg log -T '{revset("%ld", files)}\n'
1277 $ hg log -T '{revset("%ld", files)}\n'
1278 hg: parse error: invalid argument for revspec
1278 hg: parse error: invalid argument for revspec
1279 [255]
1279 [255]
1280 $ hg log -T '{revset("%ls", 0)}\n'
1280 $ hg log -T '{revset("%ls", 0)}\n'
1281 hg: parse error: invalid argument for revspec
1281 hg: parse error: invalid argument for revspec
1282 [255]
1282 [255]
1283 $ hg log -T '{revset("%b", 'foo')}\n'
1283 $ hg log -T '{revset("%b", 'foo')}\n'
1284 hg: parse error: invalid argument for revspec
1284 hg: parse error: invalid argument for revspec
1285 [255]
1285 [255]
1286 $ hg log -T '{revset("%lb", files)}\n'
1286 $ hg log -T '{revset("%lb", files)}\n'
1287 hg: parse error: invalid argument for revspec
1287 hg: parse error: invalid argument for revspec
1288 [255]
1288 [255]
1289 $ hg log -T '{revset("%r", 0)}\n'
1289 $ hg log -T '{revset("%r", 0)}\n'
1290 hg: parse error: invalid argument for revspec
1290 hg: parse error: invalid argument for revspec
1291 [255]
1291 [255]
1292
1292
1293 Test files function
1293 Test files function
1294
1294
1295 $ hg log -T "{rev}\n{join(files('*'), '\n')}\n"
1295 $ hg log -T "{rev}\n{join(files('*'), '\n')}\n"
1296 2
1296 2
1297 a
1297 a
1298 aa
1298 aa
1299 b
1299 b
1300 1
1300 1
1301 a
1301 a
1302 0
1302 0
1303 a
1303 a
1304
1304
1305 $ hg log -T "{rev}\n{join(files('aa'), '\n')}\n"
1305 $ hg log -T "{rev}\n{join(files('aa'), '\n')}\n"
1306 2
1306 2
1307 aa
1307 aa
1308 1
1308 1
1309
1309
1310 0
1310 0
1311
1311
1312
1312
1313 $ hg log -l1 -T "{files('aa') % '{file}\n'}"
1313 $ hg log -l1 -T "{files('aa') % '{file}\n'}"
1314 aa
1314 aa
1315 $ hg log -l1 -T "{files('aa') % '{path}\n'}"
1315 $ hg log -l1 -T "{files('aa') % '{path}\n'}"
1316 aa
1316 aa
1317
1317
1318 $ hg rm a
1318 $ hg rm a
1319 $ hg log -r "wdir()" -T "{rev}\n{join(files('*'), '\n')}\n"
1319 $ hg log -r "wdir()" -T "{rev}\n{join(files('*'), '\n')}\n"
1320 2147483647
1320 2147483647
1321 aa
1321 aa
1322 b
1322 b
1323 $ hg revert a
1323 $ hg revert a
1324
1324
1325 Test relpath function
1325 Test relpath function
1326
1326
1327 $ hg log -r0 -T '{files % "{file|relpath}\n"}'
1327 $ hg log -r0 -T '{files % "{file|relpath}\n"}'
1328 a
1328 a
1329 $ cd ..
1329 $ cd ..
1330 $ hg log -R r -r0 -T '{files % "{file|relpath}\n"}'
1330 $ hg log -R r -r0 -T '{files % "{file|relpath}\n"}'
1331 r/a
1331 r/a
1332
1332
1333 Test stringify on sub expressions
1333 Test stringify on sub expressions
1334
1334
1335 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
1335 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
1336 fourth, second, third
1336 fourth, second, third
1337 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
1337 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
1338 abc
1338 abc
1339
1339
1340 Test splitlines
1340 Test splitlines
1341
1341
1342 $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
1342 $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
1343 @ foo Modify, add, remove, rename
1343 @ foo Modify, add, remove, rename
1344 |
1344 |
1345 o foo future
1345 o foo future
1346 |
1346 |
1347 o foo third
1347 o foo third
1348 |
1348 |
1349 o foo second
1349 o foo second
1350
1350
1351 o foo merge
1351 o foo merge
1352 |\
1352 |\
1353 | o foo new head
1353 | o foo new head
1354 | |
1354 | |
1355 o | foo new branch
1355 o | foo new branch
1356 |/
1356 |/
1357 o foo no user, no domain
1357 o foo no user, no domain
1358 |
1358 |
1359 o foo no person
1359 o foo no person
1360 |
1360 |
1361 o foo other 1
1361 o foo other 1
1362 | foo other 2
1362 | foo other 2
1363 | foo
1363 | foo
1364 | foo other 3
1364 | foo other 3
1365 o foo line 1
1365 o foo line 1
1366 foo line 2
1366 foo line 2
1367
1367
1368 $ hg log -R a -r0 -T '{desc|splitlines}\n'
1368 $ hg log -R a -r0 -T '{desc|splitlines}\n'
1369 line 1 line 2
1369 line 1 line 2
1370 $ hg log -R a -r0 -T '{join(desc|splitlines, "|")}\n'
1370 $ hg log -R a -r0 -T '{join(desc|splitlines, "|")}\n'
1371 line 1|line 2
1371 line 1|line 2
1372
1372
1373 Test startswith
1373 Test startswith
1374 $ hg log -Gv -R a --template "{startswith(desc)}"
1374 $ hg log -Gv -R a --template "{startswith(desc)}"
1375 hg: parse error: startswith expects two arguments
1375 hg: parse error: startswith expects two arguments
1376 [255]
1376 [255]
1377
1377
1378 $ hg log -Gv -R a --template "{startswith('line', desc)}"
1378 $ hg log -Gv -R a --template "{startswith('line', desc)}"
1379 @
1379 @
1380 |
1380 |
1381 o
1381 o
1382 |
1382 |
1383 o
1383 o
1384 |
1384 |
1385 o
1385 o
1386
1386
1387 o
1387 o
1388 |\
1388 |\
1389 | o
1389 | o
1390 | |
1390 | |
1391 o |
1391 o |
1392 |/
1392 |/
1393 o
1393 o
1394 |
1394 |
1395 o
1395 o
1396 |
1396 |
1397 o
1397 o
1398 |
1398 |
1399 o line 1
1399 o line 1
1400 line 2
1400 line 2
1401
1401
1402 Test word function (including index out of bounds graceful failure)
1402 Test word function (including index out of bounds graceful failure)
1403
1403
1404 $ hg log -Gv -R a --template "{word('1', desc)}"
1404 $ hg log -Gv -R a --template "{word('1', desc)}"
1405 @ add,
1405 @ add,
1406 |
1406 |
1407 o
1407 o
1408 |
1408 |
1409 o
1409 o
1410 |
1410 |
1411 o
1411 o
1412
1412
1413 o
1413 o
1414 |\
1414 |\
1415 | o head
1415 | o head
1416 | |
1416 | |
1417 o | branch
1417 o | branch
1418 |/
1418 |/
1419 o user,
1419 o user,
1420 |
1420 |
1421 o person
1421 o person
1422 |
1422 |
1423 o 1
1423 o 1
1424 |
1424 |
1425 o 1
1425 o 1
1426
1426
1427
1427
1428 Test word third parameter used as splitter
1428 Test word third parameter used as splitter
1429
1429
1430 $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
1430 $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
1431 @ M
1431 @ M
1432 |
1432 |
1433 o future
1433 o future
1434 |
1434 |
1435 o third
1435 o third
1436 |
1436 |
1437 o sec
1437 o sec
1438
1438
1439 o merge
1439 o merge
1440 |\
1440 |\
1441 | o new head
1441 | o new head
1442 | |
1442 | |
1443 o | new branch
1443 o | new branch
1444 |/
1444 |/
1445 o n
1445 o n
1446 |
1446 |
1447 o n
1447 o n
1448 |
1448 |
1449 o
1449 o
1450 |
1450 |
1451 o line 1
1451 o line 1
1452 line 2
1452 line 2
1453
1453
1454 Test word error messages for not enough and too many arguments
1454 Test word error messages for not enough and too many arguments
1455
1455
1456 $ hg log -Gv -R a --template "{word('0')}"
1456 $ hg log -Gv -R a --template "{word('0')}"
1457 hg: parse error: word expects two or three arguments, got 1
1457 hg: parse error: word expects two or three arguments, got 1
1458 [255]
1458 [255]
1459
1459
1460 $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
1460 $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
1461 hg: parse error: word expects two or three arguments, got 7
1461 hg: parse error: word expects two or three arguments, got 7
1462 [255]
1462 [255]
1463
1463
1464 Test word for integer literal
1464 Test word for integer literal
1465
1465
1466 $ hg log -R a --template "{word(2, desc)}\n" -r0
1466 $ hg log -R a --template "{word(2, desc)}\n" -r0
1467 line
1467 line
1468
1468
1469 Test word for invalid numbers
1469 Test word for invalid numbers
1470
1470
1471 $ hg log -Gv -R a --template "{word('a', desc)}"
1471 $ hg log -Gv -R a --template "{word('a', desc)}"
1472 hg: parse error: word expects an integer index
1472 hg: parse error: word expects an integer index
1473 [255]
1473 [255]
1474
1474
1475 Test word for out of range
1475 Test word for out of range
1476
1476
1477 $ hg log -R a --template "{word(10000, desc)}"
1477 $ hg log -R a --template "{word(10000, desc)}"
1478 $ hg log -R a --template "{word(-10000, desc)}"
1478 $ hg log -R a --template "{word(-10000, desc)}"
1479
1479
1480 Test indent and not adding to empty lines
1480 Test indent and not adding to empty lines
1481
1481
1482 $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
1482 $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
1483 -----
1483 -----
1484 > line 1
1484 > line 1
1485 >> line 2
1485 >> line 2
1486 -----
1486 -----
1487 > other 1
1487 > other 1
1488 >> other 2
1488 >> other 2
1489
1489
1490 >> other 3
1490 >> other 3
1491
1491
1492 Test with non-strings like dates
1492 Test with non-strings like dates
1493
1493
1494 $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a
1494 $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a
1495 1200000.00
1495 1200000.00
1496 1300000.00
1496 1300000.00
1497
1497
1498 Test cbor filter:
1499
1500 $ cat <<'EOF' > "$TESTTMP/decodecbor.py"
1501 > from __future__ import absolute_import
1502 > from mercurial import pycompat
1503 > from mercurial.utils import (
1504 > cborutil,
1505 > stringutil,
1506 > )
1507 > items = cborutil.decodeall(pycompat.stdin.read())
1508 > pycompat.stdout.write(stringutil.pprint(items, indent=1) + b'\n')
1509 > EOF
1510
1511 $ hg log -T "{rev|cbor}" -R a -l2 | "$PYTHON" "$TESTTMP/decodecbor.py"
1512 [
1513 10,
1514 9
1515 ]
1516
1517 $ hg log -T "{extras|cbor}" -R a -l1 | "$PYTHON" "$TESTTMP/decodecbor.py"
1518 [
1519 {
1520 'branch': 'default'
1521 }
1522 ]
1523
1498 json filter should escape HTML tags so that the output can be embedded in hgweb:
1524 json filter should escape HTML tags so that the output can be embedded in hgweb:
1499
1525
1500 $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
1526 $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
1501 "\u003cfoo@example.org\u003e"
1527 "\u003cfoo@example.org\u003e"
1502
1528
1503 Set up repository for non-ascii encoding tests:
1529 Set up repository for non-ascii encoding tests:
1504
1530
1505 $ hg init nonascii
1531 $ hg init nonascii
1506 $ cd nonascii
1532 $ cd nonascii
1507 $ "$PYTHON" <<EOF
1533 $ "$PYTHON" <<EOF
1508 > open('latin1', 'wb').write(b'\xe9')
1534 > open('latin1', 'wb').write(b'\xe9')
1509 > open('utf-8', 'wb').write(b'\xc3\xa9')
1535 > open('utf-8', 'wb').write(b'\xc3\xa9')
1510 > EOF
1536 > EOF
1511 $ HGENCODING=utf-8 hg branch -q `cat utf-8`
1537 $ HGENCODING=utf-8 hg branch -q `cat utf-8`
1512 $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
1538 $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
1513
1539
1514 json filter should try round-trip conversion to utf-8:
1540 json filter should try round-trip conversion to utf-8:
1515
1541
1516 $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
1542 $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
1517 "\u00e9"
1543 "\u00e9"
1518 $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
1544 $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
1519 "non-ascii branch: \u00e9"
1545 "non-ascii branch: \u00e9"
1520
1546
1521 json filter should take input as utf-8 if it was converted from utf-8:
1547 json filter should take input as utf-8 if it was converted from utf-8:
1522
1548
1523 $ HGENCODING=latin-1 hg log -T "{branch|json}\n" -r0
1549 $ HGENCODING=latin-1 hg log -T "{branch|json}\n" -r0
1524 "\u00e9"
1550 "\u00e9"
1525 $ HGENCODING=latin-1 hg log -T "{desc|json}\n" -r0
1551 $ HGENCODING=latin-1 hg log -T "{desc|json}\n" -r0
1526 "non-ascii branch: \u00e9"
1552 "non-ascii branch: \u00e9"
1527
1553
1528 json filter takes input as utf-8b:
1554 json filter takes input as utf-8b:
1529
1555
1530 $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
1556 $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
1531 "\u00e9"
1557 "\u00e9"
1532 $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
1558 $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
1533 "\udce9"
1559 "\udce9"
1534
1560
1535 utf8 filter:
1561 utf8 filter:
1536
1562
1537 $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
1563 $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
1538 round-trip: c3a9
1564 round-trip: c3a9
1539 $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
1565 $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
1540 decoded: c3a9
1566 decoded: c3a9
1541 $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
1567 $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
1542 abort: decoding near * (glob)
1568 abort: decoding near * (glob)
1543 [255]
1569 [255]
1544 $ hg log -T "coerced to string: {rev|utf8}\n" -r0
1570 $ hg log -T "coerced to string: {rev|utf8}\n" -r0
1545 coerced to string: 0
1571 coerced to string: 0
1546
1572
1547 pad width:
1573 pad width:
1548
1574
1549 $ HGENCODING=utf-8 hg debugtemplate "{pad('`cat utf-8`', 2, '-')}\n"
1575 $ HGENCODING=utf-8 hg debugtemplate "{pad('`cat utf-8`', 2, '-')}\n"
1550 \xc3\xa9- (esc)
1576 \xc3\xa9- (esc)
1551
1577
1552 read config options:
1578 read config options:
1553
1579
1554 $ hg log -T "{config('templateconfig', 'knob', 'foo')}\n"
1580 $ hg log -T "{config('templateconfig', 'knob', 'foo')}\n"
1555 foo
1581 foo
1556 $ hg log -T "{config('templateconfig', 'knob', 'foo')}\n" \
1582 $ hg log -T "{config('templateconfig', 'knob', 'foo')}\n" \
1557 > --config templateconfig.knob=bar
1583 > --config templateconfig.knob=bar
1558 bar
1584 bar
1559 $ hg log -T "{configbool('templateconfig', 'knob', True)}\n"
1585 $ hg log -T "{configbool('templateconfig', 'knob', True)}\n"
1560 True
1586 True
1561 $ hg log -T "{configbool('templateconfig', 'knob', True)}\n" \
1587 $ hg log -T "{configbool('templateconfig', 'knob', True)}\n" \
1562 > --config templateconfig.knob=0
1588 > --config templateconfig.knob=0
1563 False
1589 False
1564 $ hg log -T "{configint('templateconfig', 'knob', 123)}\n"
1590 $ hg log -T "{configint('templateconfig', 'knob', 123)}\n"
1565 123
1591 123
1566 $ hg log -T "{configint('templateconfig', 'knob', 123)}\n" \
1592 $ hg log -T "{configint('templateconfig', 'knob', 123)}\n" \
1567 > --config templateconfig.knob=456
1593 > --config templateconfig.knob=456
1568 456
1594 456
1569 $ hg log -T "{config('templateconfig', 'knob')}\n"
1595 $ hg log -T "{config('templateconfig', 'knob')}\n"
1570 devel-warn: config item requires an explicit default value: 'templateconfig.knob' at: * (glob)
1596 devel-warn: config item requires an explicit default value: 'templateconfig.knob' at: * (glob)
1571
1597
1572 $ hg log -T "{configbool('ui', 'interactive')}\n"
1598 $ hg log -T "{configbool('ui', 'interactive')}\n"
1573 False
1599 False
1574 $ hg log -T "{configbool('ui', 'interactive')}\n" --config ui.interactive=1
1600 $ hg log -T "{configbool('ui', 'interactive')}\n" --config ui.interactive=1
1575 True
1601 True
1576 $ hg log -T "{config('templateconfig', 'knob', if(true, 'foo', 'bar'))}\n"
1602 $ hg log -T "{config('templateconfig', 'knob', if(true, 'foo', 'bar'))}\n"
1577 foo
1603 foo
1578
1604
1579 $ cd ..
1605 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now