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