tests/test-template-functions.t
author Pierre-Yves David <pierre-yves.david@octobus.net>
Mon, 21 Dec 2020 11:20:31 +0100
changeset 46584 aa19d60ac974
parent 45906 95c4cca641f6
child 48368 8c4881c07f57
permissions -rw-r--r--
copies-rust: use simpler overwrite when value on both side are identical If the value are the same, their "overwritten" set is the same and we don't need to combine them. It helps our slower cases Repo Case Source-Rev Dest-Rev # of revisions old time new time Difference Factor time per rev --------------------------------------------------------------------------------------------------------------------------------------------------------------- mozilla-try x00000_revs_x00000_added_x0000_copies 1b661134e2ca 1ae03d022d6d : 228985 revs, 86.722016 s, 80.828689 s, -5.893327 s, × 0.9320, 352 µs/rev mozilla-try x00000_revs_x00000_added_x000_copies 9b2a99adc05e 8e29777b48e6 : 382065 revs, 35.113727 s, 34.094064 s, -1.019663 s, × 0.9710, 89 µs/rev Full comparison with the previous revision below: Repo Case Source-Rev Dest-Rev # of revisions old time new time Difference Factor time per rev --------------------------------------------------------------------------------------------------------------------------------------------------------------- mercurial x_revs_x_added_0_copies ad6b123de1c7 39cfcef4f463 : 1 revs, 0.000043 s, 0.000043 s, +0.000000 s, × 1.0000, 43 µs/rev mercurial x_revs_x_added_x_copies 2b1c78674230 0c1d10351869 : 6 revs, 0.000114 s, 0.000114 s, +0.000000 s, × 1.0000, 19 µs/rev mercurial x000_revs_x000_added_x_copies 81f8ff2a9bf2 dd3267698d84 : 1032 revs, 0.004899 s, 0.004899 s, +0.000000 s, × 1.0000, 4 µs/rev pypy x_revs_x_added_0_copies aed021ee8ae8 099ed31b181b : 9 revs, 0.000196 s, 0.000196 s, +0.000000 s, × 1.0000, 21 µs/rev pypy x_revs_x000_added_0_copies 4aa4e1f8e19a 359343b9ac0e : 1 revs, 0.000050 s, 0.000049 s, -0.000001 s, × 0.9800, 49 µs/rev pypy x_revs_x_added_x_copies ac52eb7bbbb0 72e022663155 : 7 revs, 0.000125 s, 0.000117 s, -0.000008 s, × 0.9360, 16 µs/rev pypy x_revs_x00_added_x_copies c3b14617fbd7 ace7255d9a26 : 1 revs, 0.000321 s, 0.6f1f4a s, +0.000001 s, × 1.0031, 322 µs/rev pypy x_revs_x000_added_x000_copies df6f7a526b60 a83dc6a2d56f : 6 revs, 0.011948 s, 0.011856 s, -0.000092 s, × 0.9923, 1976 µs/rev pypy x000_revs_xx00_added_0_copies 89a76aede314 2f22446ff07e : 4785 revs, 0.051267 s, 0.050992 s, -0.000275 s, × 0.9946, 10 µs/rev pypy x000_revs_x000_added_x_copies 8a3b5bfd266e 2c68e87c3efe : 6780 revs, 0.087755 s, 0.087444 s, -0.000311 s, × 0.9965, 12 µs/rev pypy x000_revs_x000_added_x000_copies 89a76aede314 7b3dda341c84 : 5441 revs, 0.061818 s, 0.062487 s, +0.000669 s, × 1.0108, 11 µs/rev pypy x0000_revs_x_added_0_copies d1defd0dc478 c9cb1334cc78 : 43645 revs, 0.634253 s, 0.634909 s, +0.000656 s, × 1.0010, 14 µs/rev pypy x0000_revs_xx000_added_0_copies bf2c629d0071 4ffed77c095c : 2 revs, 0.013179 s, 0.013360 s, +0.000181 s, × 1.0137, 6680 µs/rev pypy x0000_revs_xx000_added_x000_copies 08ea3258278e d9fa043f30c0 : 11316 revs, 0.119643 s, 0.120775 s, +0.001132 s, × 1.0095, 10 µs/rev netbeans x_revs_x_added_0_copies fb0955ffcbcd a01e9239f9e7 : 2 revs, 0.000085 s, 0.000085 s, +0.000000 s, × 1.0000, 42 µs/rev netbeans x_revs_x000_added_0_copies 6f360122949f 20eb231cc7d0 : 2 revs, 0.000107 s, 0.000108 s, +0.000001 s, × 1.0093, 54 µs/rev netbeans x_revs_x_added_x_copies 1ada3faf6fb6 5a39d12eecf4 : 3 revs, 0.000176 s, 0.000176 s, +0.000000 s, × 1.0000, 58 µs/rev netbeans x_revs_x00_added_x_copies 35be93ba1e2c 9eec5e90c05f : 9 revs, 0.000743 s, 0.000747 s, +0.000004 s, × 1.0054, 83 µs/rev netbeans x000_revs_xx00_added_0_copies eac3045b4fdd 51d4ae7f1290 : 1421 revs, 0.010246 s, 0.010128 s, -0.000118 s, × 0.9885, 7 µs/rev netbeans x000_revs_x000_added_x_copies e2063d266acd 6081d72689dc : 1533 revs, 0.015853 s, 0.015899 s, +0.000046 s, × 1.0029, 10 µs/rev netbeans x000_revs_x000_added_x000_copies ff453e9fee32 411350406ec2 : 5750 revs, 0.062971 s, 0.062215 s, -0.000756 s, × 0.9880, 10 µs/rev netbeans x0000_revs_xx000_added_x000_copies 588c2d1ced70 1aad62e59ddd : 66949 revs, 0.518337 s, 0.521004 s, +0.002667 s, × 1.0051, 7 µs/rev mozilla-central x_revs_x_added_0_copies 3697f962bb7b 7015fcdd43a2 : 2 revs, 0.000090 s, 0.000090 s, +0.000000 s, × 1.0000, 45 µs/rev mozilla-central x_revs_x000_added_0_copies dd390860c6c9 40d0c5bed75d : 8 revs, 0.000268 s, 0.000264 s, -0.000004 s, × 0.9851, 33 µs/rev mozilla-central x_revs_x_added_x_copies 8d198483ae3b 14207ffc2b2f : 9 revs, 0.000187 s, 0.000186 s, -0.000001 s, × 0.9947, 20 µs/rev mozilla-central x_revs_x00_added_x_copies 98cbc58cc6bc 446a150332c3 : 7 revs, 0.000661 s, 0.000660 s, -0.000001 s, × 0.9985, 94 µs/rev mozilla-central x_revs_x000_added_x000_copies 3c684b4b8f68 0a5e72d1b479 : 3 revs, 0.003494 s, 0.003542 s, +0.000048 s, × 1.0137, 1180 µs/rev mozilla-central x_revs_x0000_added_x0000_copies effb563bb7e5 c07a39dc4e80 : 6 revs, 0.070509 s, 0.071574 s, +0.001065 s, × 1.0151, 11929 µs/rev mozilla-central x000_revs_xx00_added_0_copies 6100d773079a 04a55431795e : 1593 revs, 0.006489 s, 0.006498 s, +0.000009 s, × 1.0014, 4 µs/rev mozilla-central x000_revs_x000_added_x_copies 9f17a6fc04f9 2d37b966abed : 41 revs, 0.005070 s, 0.005206 s, +0.000136 s, × 1.0268, 126 µs/rev mozilla-central x000_revs_x000_added_x000_copies 7c97034feb78 4407bd0c6330 : 7839 revs, 0.065241 s, 0.065535 s, +0.000294 s, × 1.0045, 8 µs/rev mozilla-central x0000_revs_xx000_added_0_copies 9eec5917337d 67118cc6dcad : 615 revs, 0.027284 s, 0.027139 s, -0.000145 s, × 0.9947, 44 µs/rev mozilla-central x0000_revs_xx000_added_x000_copies f78c615a656c 96a38b690156 : 30263 revs, 0.203671 s, 0.201924 s, -0.001747 s, × 0.9914, 6 µs/rev mozilla-central x00000_revs_x0000_added_x0000_copies 6832ae71433c 4c222a1d9a00 : 153721 revs, 1.239373 s, 1.257201 s, +0.017828 s, × 1.0144, 8 µs/rev mozilla-central x00000_revs_x00000_added_x000_copies 76caed42cf7c 1daa622bbe42 : 204976 revs, 1.649803 s, 1.663045 s, +0.013242 s, × 1.0080, 8 µs/rev mozilla-try x_revs_x_added_0_copies aaf6dde0deb8 9790f499805a : 2 revs, 0.000868 s, 0.000866 s, -0.000002 s, × 0.9977, 433 µs/rev mozilla-try x_revs_x000_added_0_copies d8d0222927b4 5bb8ce8c7450 : 2 revs, 0.000885 s, 0.000883 s, -0.000002 s, × 0.9977, 441 µs/rev mozilla-try x_revs_x_added_x_copies 092fcca11bdb 936255a0384a : 4 revs, 0.000165 s, 0.000163 s, -0.000002 s, × 0.9879, 40 µs/rev mozilla-try x_revs_x00_added_x_copies b53d2fadbdb5 017afae788ec : 2 revs, 0.001147 s, 0.001139 s, -0.000008 s, × 0.9930, 569 µs/rev mozilla-try x_revs_x000_added_x000_copies 20408ad61ce5 6f0ee96e21ad : 1 revs, 0.032885 s, 0.032753 s, -0.000132 s, × 0.9960, 32752 µs/rev mozilla-try x_revs_x0000_added_x0000_copies effb563bb7e5 c07a39dc4e80 : 6 revs, 0.071304 s, 0.073266 s, +0.001962 s, × 1.0275, 12211 µs/rev mozilla-try x000_revs_xx00_added_0_copies 6100d773079a 04a55431795e : 1593 revs, 0.006506 s, 0.006567 s, +0.000061 s, × 1.0094, 4 µs/rev mozilla-try x000_revs_x000_added_x_copies 9f17a6fc04f9 2d37b966abed : 41 revs, 0.005486 s, 0.005427 s, -0.000059 s, × 0.9892, 132 µs/rev mozilla-try x000_revs_x000_added_x000_copies 1346fd0130e4 4c65cbdabc1f : 6657 revs, 0.064677 s, 0.064058 s, -0.000619 s, × 0.9904, 9 µs/rev mozilla-try x0000_revs_x_added_0_copies 63519bfd42ee a36a2a865d92 : 40314 revs, 0.306000 s, 0.303320 s, -0.002680 s, × 0.9912, 7 µs/rev mozilla-try x0000_revs_x_added_x_copies 9fe69ff0762d bcabf2a78927 : 38690 revs, 0.288217 s, 0.288456 s, +0.000239 s, × 1.0008, 7 µs/rev mozilla-try x0000_revs_xx000_added_x_copies 156f6e2674f2 4d0f2c178e66 : 8598 revs, 0.086117 s, 0.085925 s, -0.000192 s, × 0.9978, 9 µs/rev mozilla-try x0000_revs_xx000_added_0_copies 9eec5917337d 67118cc6dcad : 615 revs, 0.027512 s, 0.027302 s, -0.000210 s, × 0.9924, 44 µs/rev mozilla-try x0000_revs_xx000_added_x000_copies 89294cd501d9 7ccb2fc7ccb5 : 97052 revs, 1.998239 s, 2.034596 s, +0.036357 s, × 1.0182, 20 µs/rev mozilla-try x0000_revs_x0000_added_x0000_copies e928c65095ed e951f4ad123a : 52031 revs, 0.688201 s, 0.694030 s, +0.005829 s, × 1.0085, 13 µs/rev mozilla-try x00000_revs_x_added_0_copies 6a320851d377 1ebb79acd503 : 363753 revs, 4.389428 s, 4.407723 s, +0.018295 s, × 1.0042, 12 µs/rev mozilla-try x00000_revs_x00000_added_0_copies dc8a3ca7010e d16fde900c9c : 34414 revs, 0.578736 s, 0.574355 s, -0.004381 s, × 0.9924, 16 µs/rev mozilla-try x00000_revs_x_added_x_copies 5173c4b6f97c 95d83ee7242d : 362229 revs, 4.363599 s, 4.457827 s, +0.094228 s, × 1.0216, 12 µs/rev mozilla-try x00000_revs_x000_added_x_copies 9126823d0e9c ca82787bb23c : 359344 revs, 4.324129 s, 4.351696 s, +0.027567 s, × 1.0064, 12 µs/rev mozilla-try x00000_revs_x0000_added_x0000_copies 8d3fafa80d4b eb884023b810 : 192665 revs, 1.565727 s, 1.570065 s, +0.004338 s, × 1.0028, 8 µs/rev mozilla-try x00000_revs_x00000_added_x0000_copies 1b661134e2ca 1ae03d022d6d : 228985 revs, 86.722016 s, 80.828689 s, -5.893327 s, × 0.9320, 352 µs/rev mozilla-try x00000_revs_x00000_added_x000_copies 9b2a99adc05e 8e29777b48e6 : 382065 revs, 35.113727 s, 34.094064 s, -1.019663 s, × 0.9710, 89 µs/rev private : 459513 revs, 27.397070 s, 27.435529 s, +0.038459 s, × 1.0014, 59 µs/rev Differential Revision: https://phab.mercurial-scm.org/D9655

Test template filters and functions
===================================

  $ hg init a
  $ cd a
  $ echo a > a
  $ hg add a
  $ echo line 1 > b
  $ echo line 2 >> b
  $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'

  $ hg add b
  $ echo other 1 > c
  $ echo other 2 >> c
  $ echo >> c
  $ echo other 3 >> c
  $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'

  $ hg add c
  $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
  $ echo c >> c
  $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'

  $ echo foo > .hg/branch
  $ hg commit -m 'new branch' -d '1400000 0' -u 'person'

  $ hg co -q 3
  $ echo other 4 >> d
  $ hg add d
  $ hg commit -m 'new head' -d '1500000 0' -u 'person'

  $ hg merge -q foo
  $ hg commit -m 'merge' -d '1500001 0' -u 'person'

Second branch starting at nullrev:

  $ hg update null
  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
  $ echo second > second
  $ hg add second
  $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
  created new head

  $ echo third > third
  $ hg add third
  $ hg mv second fourth
  $ hg commit -m third -d "2020-01-01 10:01"

  $ hg phase -r 5 --public
  $ hg phase -r 7 --secret --force

Filters work:

  $ hg log --template '{author|domain}\n'
  
  hostname
  
  
  
  
  place
  place
  hostname

  $ hg log --template '{author|person}\n'
  test
  User Name
  person
  person
  person
  person
  other
  A. N. Other
  User Name

  $ hg log --template '{author|user}\n'
  test
  user
  person
  person
  person
  person
  other
  other
  user

  $ hg log --template '{date|date}\n'
  Wed Jan 01 10:01:00 2020 +0000
  Mon Jan 12 13:46:40 1970 +0000
  Sun Jan 18 08:40:01 1970 +0000
  Sun Jan 18 08:40:00 1970 +0000
  Sat Jan 17 04:53:20 1970 +0000
  Fri Jan 16 01:06:40 1970 +0000
  Wed Jan 14 21:20:00 1970 +0000
  Tue Jan 13 17:33:20 1970 +0000
  Mon Jan 12 13:46:40 1970 +0000

  $ hg log --template '{date|isodate}\n'
  2020-01-01 10:01 +0000
  1970-01-12 13:46 +0000
  1970-01-18 08:40 +0000
  1970-01-18 08:40 +0000
  1970-01-17 04:53 +0000
  1970-01-16 01:06 +0000
  1970-01-14 21:20 +0000
  1970-01-13 17:33 +0000
  1970-01-12 13:46 +0000

  $ hg log --template '{date|isodatesec}\n'
  2020-01-01 10:01:00 +0000
  1970-01-12 13:46:40 +0000
  1970-01-18 08:40:01 +0000
  1970-01-18 08:40:00 +0000
  1970-01-17 04:53:20 +0000
  1970-01-16 01:06:40 +0000
  1970-01-14 21:20:00 +0000
  1970-01-13 17:33:20 +0000
  1970-01-12 13:46:40 +0000

  $ hg log --template '{date|rfc822date}\n'
  Wed, 01 Jan 2020 10:01:00 +0000
  Mon, 12 Jan 1970 13:46:40 +0000
  Sun, 18 Jan 1970 08:40:01 +0000
  Sun, 18 Jan 1970 08:40:00 +0000
  Sat, 17 Jan 1970 04:53:20 +0000
  Fri, 16 Jan 1970 01:06:40 +0000
  Wed, 14 Jan 1970 21:20:00 +0000
  Tue, 13 Jan 1970 17:33:20 +0000
  Mon, 12 Jan 1970 13:46:40 +0000

  $ hg log --template '{desc|firstline}\n'
  third
  second
  merge
  new head
  new branch
  no user, no domain
  no person
  other 1
  line 1

  $ hg log --template '{node|short}\n'
  95c24699272e
  29114dbae42b
  d41e714fe50d
  13207e5a10d9
  bbe44766e73d
  10e46f2dcbf4
  97054abb4ab8
  b608e9d1a3f0
  1e4e1b8f71e0

  $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
  <changeset author="test"/>
  <changeset author="User Name &lt;user@hostname&gt;"/>
  <changeset author="person"/>
  <changeset author="person"/>
  <changeset author="person"/>
  <changeset author="person"/>
  <changeset author="other@place"/>
  <changeset author="A. N. Other &lt;other@place&gt;"/>
  <changeset author="User Name &lt;user@hostname&gt;"/>

  $ hg log --template '{rev}: {children}\n'
  8: 
  7: 8:95c24699272e
  6: 
  5: 6:d41e714fe50d
  4: 6:d41e714fe50d
  3: 4:bbe44766e73d 5:13207e5a10d9
  2: 3:10e46f2dcbf4
  1: 2:97054abb4ab8
  0: 1:b608e9d1a3f0

Formatnode filter works:

  $ hg -q log -r 0 --template '{node|formatnode}\n'
  1e4e1b8f71e0

  $ hg log -r 0 --template '{node|formatnode}\n'
  1e4e1b8f71e0

  $ hg -v log -r 0 --template '{node|formatnode}\n'
  1e4e1b8f71e0

  $ hg --debug log -r 0 --template '{node|formatnode}\n'
  1e4e1b8f71e05681d422154f5421e385fec3454f

Age filter:

  $ hg init unstable-hash
  $ cd unstable-hash
  $ hg log --template '{date|age}\n' > /dev/null || exit 1

  >>> from __future__ import absolute_import
  >>> import datetime
  >>> fp = open('a', 'wb')
  >>> n = datetime.datetime.now() + datetime.timedelta(366 * 7)
  >>> fp.write(b'%d-%d-%d 00:00' % (n.year, n.month, n.day)) and None
  >>> fp.close()
  $ hg add a
  $ hg commit -m future -d "`cat a`"

  $ hg log -l1 --template '{date|age}\n'
  7 years from now

  $ cd ..
  $ rm -rf unstable-hash

Filename filters:

  $ hg debugtemplate '{"foo/bar"|basename}|{"foo/"|basename}|{"foo"|basename}|\n'
  bar||foo|
  $ hg debugtemplate '{"foo/bar"|dirname}|{"foo/"|dirname}|{"foo"|dirname}|\n'
  foo|foo||
  $ hg debugtemplate '{"foo/bar"|stripdir}|{"foo/"|stripdir}|{"foo"|stripdir}|\n'
  foo|foo|foo|

commondir() filter:

  $ hg debugtemplate '{""|splitlines|commondir}\n'
  
  $ hg debugtemplate '{"foo/bar\nfoo/baz\nfoo/foobar\n"|splitlines|commondir}\n'
  foo
  $ hg debugtemplate '{"foo/bar\nfoo/bar\n"|splitlines|commondir}\n'
  foo
  $ hg debugtemplate '{"/foo/bar\n/foo/bar\n"|splitlines|commondir}\n'
  foo
  $ hg debugtemplate '{"/foo\n/foo\n"|splitlines|commondir}\n'
  
  $ hg debugtemplate '{"foo/bar\nbar/baz"|splitlines|commondir}\n'
  
  $ hg debugtemplate '{"foo/bar\nbar/baz\nbar/foo\n"|splitlines|commondir}\n'
  
  $ hg debugtemplate '{"foo/../bar\nfoo/bar"|splitlines|commondir}\n'
  foo
  $ hg debugtemplate '{"foo\n/foo"|splitlines|commondir}\n'
  

  $ hg log -r null -T '{rev|commondir}'
  hg: parse error: argument is not a list of text
  (template filter 'commondir' is not compatible with keyword 'rev')
  [10]

Add a dummy commit to make up for the instability of the above:

  $ echo a > a
  $ hg add a
  $ hg ci -m future

Count filter:

  $ hg log -l1 --template '{node|count} {node|short|count}\n'
  40 12

  $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
  0 1 4

  $ hg log -G --template '{rev}: children: {children|count}, \
  > tags: {tags|count}, file_adds: {file_adds|count}, \
  > ancestors: {revset("ancestors(%s)", rev)|count}'
  @  9: children: 0, tags: 1, file_adds: 1, ancestors: 3
  |
  o  8: children: 1, tags: 0, file_adds: 2, ancestors: 2
  |
  o  7: children: 1, tags: 0, file_adds: 1, ancestors: 1
  
  o    6: children: 0, tags: 0, file_adds: 0, ancestors: 7
  |\
  | o  5: children: 1, tags: 0, file_adds: 1, ancestors: 5
  | |
  o |  4: children: 1, tags: 0, file_adds: 0, ancestors: 5
  |/
  o  3: children: 2, tags: 0, file_adds: 0, ancestors: 4
  |
  o  2: children: 1, tags: 0, file_adds: 1, ancestors: 3
  |
  o  1: children: 1, tags: 0, file_adds: 1, ancestors: 2
  |
  o  0: children: 1, tags: 0, file_adds: 1, ancestors: 1
  

  $ hg log -l1 -T '{termwidth|count}\n'
  hg: parse error: not countable
  (template filter 'count' is not compatible with keyword 'termwidth')
  [10]

Upper/lower filters:

  $ hg log -r0 --template '{branch|upper}\n'
  DEFAULT
  $ hg log -r0 --template '{author|lower}\n'
  user name <user@hostname>
  $ hg log -r0 --template '{date|upper}\n'
  1000000.00

Add a commit that does all possible modifications at once

  $ echo modify >> third
  $ touch b
  $ hg add b
  $ hg mv fourth fifth
  $ hg rm a
  $ hg ci -m "Modify, add, remove, rename"

Pass generator object created by template function to filter

  $ hg log -l 1 --template '{if(author, author)|user}\n'
  test

Test diff function:

  $ hg diff -c 8
  diff -r 29114dbae42b -r 95c24699272e fourth
  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
  +++ b/fourth	Wed Jan 01 10:01:00 2020 +0000
  @@ -0,0 +1,1 @@
  +second
  diff -r 29114dbae42b -r 95c24699272e second
  --- a/second	Mon Jan 12 13:46:40 1970 +0000
  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
  @@ -1,1 +0,0 @@
  -second
  diff -r 29114dbae42b -r 95c24699272e third
  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
  +++ b/third	Wed Jan 01 10:01:00 2020 +0000
  @@ -0,0 +1,1 @@
  +third

  $ hg log -r 8 -T "{diff()}"
  diff -r 29114dbae42b -r 95c24699272e fourth
  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
  +++ b/fourth	Wed Jan 01 10:01:00 2020 +0000
  @@ -0,0 +1,1 @@
  +second
  diff -r 29114dbae42b -r 95c24699272e second
  --- a/second	Mon Jan 12 13:46:40 1970 +0000
  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
  @@ -1,1 +0,0 @@
  -second
  diff -r 29114dbae42b -r 95c24699272e third
  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
  +++ b/third	Wed Jan 01 10:01:00 2020 +0000
  @@ -0,0 +1,1 @@
  +third

  $ hg log -r 8 -T "{diff('glob:f*')}"
  diff -r 29114dbae42b -r 95c24699272e fourth
  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
  +++ b/fourth	Wed Jan 01 10:01:00 2020 +0000
  @@ -0,0 +1,1 @@
  +second

  $ hg log -r 8 -T "{diff('', 'glob:f*')}"
  diff -r 29114dbae42b -r 95c24699272e second
  --- a/second	Mon Jan 12 13:46:40 1970 +0000
  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
  @@ -1,1 +0,0 @@
  -second
  diff -r 29114dbae42b -r 95c24699272e third
  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
  +++ b/third	Wed Jan 01 10:01:00 2020 +0000
  @@ -0,0 +1,1 @@
  +third

  $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
  diff -r 29114dbae42b -r 95c24699272e fourth
  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
  +++ b/fourth	Wed Jan 01 10:01:00 2020 +0000
  @@ -0,0 +1,1 @@
  +second

  $ hg --config diff.git=true log -r 8 -T "{diff()}"
  diff --git a/second b/fourth
  rename from second
  rename to fourth
  diff --git a/third b/third
  new file mode 100644
  --- /dev/null
  +++ b/third
  @@ -0,0 +1,1 @@
  +third

  $ cd ..

latesttag() function:

  $ hg init latesttag
  $ cd latesttag

  $ echo a > file
  $ hg ci -Am a -d '0 0'
  adding file

  $ echo b >> file
  $ hg ci -m b -d '1 0'

  $ echo c >> head1
  $ hg ci -Am h1c -d '2 0'
  adding head1

  $ hg update -q 1
  $ echo d >> head2
  $ hg ci -Am h2d -d '3 0'
  adding head2
  created new head

  $ echo e >> head2
  $ hg ci -m h2e -d '4 0'

  $ hg merge -q
  $ hg ci -m merge -d '5 -3600'

  $ hg tag -r 1 -m t1 -d '6 0' t1
  $ hg tag -r 2 -m t2 -d '7 0' t2
  $ hg tag -r 3 -m t3 -d '8 0' t3
  $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
  $ hg tag -r 5 -m t5 -d '9 0' t5
  $ hg tag -r 3 -m at3 -d '10 0' at3

  $ hg log -G --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
  @  11: t3, C: 9, D: 8
  |
  o  10: t3, C: 8, D: 7
  |
  o  9: t3, C: 7, D: 6
  |
  o  8: t3, C: 6, D: 5
  |
  o  7: t3, C: 5, D: 4
  |
  o  6: t3, C: 4, D: 3
  |
  o    5: t3, C: 3, D: 2
  |\
  | o  4: t3, C: 1, D: 1
  | |
  | o  3: t3, C: 0, D: 0
  | |
  o |  2: t1, C: 1, D: 1
  |/
  o  1: t1, C: 0, D: 0
  |
  o  0: null, C: 1, D: 1
  

  $ cd ..

Test filter() empty values:

  $ hg log -R a -r 1 -T '{filter(desc|splitlines) % "{line}\n"}'
  other 1
  other 2
  other 3
  $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1) % "{ifeq(key, "a", "{value}\n")}")}'
  0

 0 should not be falsy

  $ hg log -R a -r 0 -T '{filter(revset("0:2"))}\n'
  0 1 2

Test filter() by expression:

  $ hg log -R a -r 1 -T '{filter(desc|splitlines, ifcontains("1", line, "t"))}\n'
  other 1
  $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1), ifeq(key, "b", "t"))}\n'
  b=1

Test filter() shouldn't crash:

  $ hg log -R a -r 0 -T '{filter(extras)}\n'
  branch=default
  $ hg log -R a -r 0 -T '{filter(files)}\n'
  a

Test filter() unsupported arguments:

  $ hg log -R a -r 0 -T '{filter()}\n'
  hg: parse error: filter expects one or two arguments
  [10]
  $ hg log -R a -r 0 -T '{filter(date)}\n'
  hg: parse error: date is not iterable
  [10]
  $ hg log -R a -r 0 -T '{filter(rev)}\n'
  hg: parse error: 0 is not iterable
  [10]
  $ hg log -R a -r 0 -T '{filter(desc|firstline)}\n'
  hg: parse error: 'line 1' is not filterable
  [10]
  $ hg log -R a -r 0 -T '{filter(manifest)}\n'
  hg: parse error: '0:a0c8bcbbb45c' is not filterable
  [10]
  $ hg log -R a -r 0 -T '{filter(succsandmarkers)}\n'
  hg: parse error: not filterable without template
  [10]
  $ hg log -R a -r 0 -T '{filter(desc|splitlines % "{line}", "")}\n'
  hg: parse error: not filterable by expression
  [10]

Test manifest/get() can be join()-ed as string, though it's silly:

  $ hg log -R latesttag -r tip -T '{join(manifest, ".")}\n'
  1.1.:.2.b.c.6.e.9.0.0.6.c.e.2
  $ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), ".")}\n'
  d.e.f.a.u.l.t

Test join() over string

  $ hg log -R latesttag -r tip -T '{join(rev|stringify, ".")}\n'
  1.1

Test join() over uniterable

  $ hg log -R latesttag -r tip -T '{join(rev, "")}\n'
  hg: parse error: 11 is not iterable
  [10]

Test min/max of integers

  $ hg log -R latesttag -l1 -T '{min(revset("9:10"))}\n'
  9
  $ hg log -R latesttag -l1 -T '{max(revset("9:10"))}\n'
  10

Test min/max over map operation:

  $ hg log -R latesttag -r3 -T '{min(tags % "{tag}")}\n'
  at3
  $ hg log -R latesttag -r3 -T '{max(tags % "{tag}")}\n'
  t3

Test min/max of strings:

  $ hg log -R latesttag -l1 -T '{min(desc)}\n'
  3
  $ hg log -R latesttag -l1 -T '{max(desc)}\n'
  t

Test min/max of non-iterable:

  $ hg debugtemplate '{min(1)}'
  hg: parse error: 1 is not iterable
  (min first argument should be an iterable)
  [10]
  $ hg debugtemplate '{max(2)}'
  hg: parse error: 2 is not iterable
  (max first argument should be an iterable)
  [10]

  $ hg log -R latesttag -l1 -T '{min(date)}'
  hg: parse error: date is not iterable
  (min first argument should be an iterable)
  [10]
  $ hg log -R latesttag -l1 -T '{max(date)}'
  hg: parse error: date is not iterable
  (max first argument should be an iterable)
  [10]

Test min/max of empty sequence:

  $ hg debugtemplate '{min("")}'
  hg: parse error: empty string
  (min first argument should be an iterable)
  [10]
  $ hg debugtemplate '{max("")}'
  hg: parse error: empty string
  (max first argument should be an iterable)
  [10]
  $ hg debugtemplate '{min(dict())}'
  hg: parse error: empty sequence
  (min first argument should be an iterable)
  [10]
  $ hg debugtemplate '{max(dict())}'
  hg: parse error: empty sequence
  (max first argument should be an iterable)
  [10]
  $ hg debugtemplate '{min(dict() % "")}'
  hg: parse error: empty sequence
  (min first argument should be an iterable)
  [10]
  $ hg debugtemplate '{max(dict() % "")}'
  hg: parse error: empty sequence
  (max first argument should be an iterable)
  [10]

Test min/max of if() result

  $ cd latesttag
  $ hg log -l1 -T '{min(if(true, revset("9:10"), ""))}\n'
  9
  $ hg log -l1 -T '{max(if(false, "", revset("9:10")))}\n'
  10
  $ hg log -l1 -T '{min(ifcontains("a", "aa", revset("9:10"), ""))}\n'
  9
  $ hg log -l1 -T '{max(ifcontains("a", "bb", "", revset("9:10")))}\n'
  10
  $ hg log -l1 -T '{min(ifeq(0, 0, revset("9:10"), ""))}\n'
  9
  $ hg log -l1 -T '{max(ifeq(0, 1, "", revset("9:10")))}\n'
  10
  $ cd ..

Test laziness of if() then/else clause

  $ hg debugtemplate '{count(0)}'
  hg: parse error: not countable
  (incompatible use of template filter 'count')
  [10]
  $ hg debugtemplate '{if(true, "", count(0))}'
  $ hg debugtemplate '{if(false, count(0), "")}'
  $ hg debugtemplate '{ifcontains("a", "aa", "", count(0))}'
  $ hg debugtemplate '{ifcontains("a", "bb", count(0), "")}'
  $ hg debugtemplate '{ifeq(0, 0, "", count(0))}'
  $ hg debugtemplate '{ifeq(0, 1, count(0), "")}'

Test search() function:

  $ hg log -R a -r2 -T '{desc}\n'
  no person

  $ hg log -R a -r2 -T '{search(r"p.*", desc)}\n'
  person

 as bool

  $ hg log -R a -r2 -T '{if(search(r"p.*", desc), "", "not ")}found\n'
  found
  $ hg log -R a -r2 -T '{if(search(r"q", desc), "", "not ")}found\n'
  not found

 match as json

  $ hg log -R a -r2 -T '{search(r"(no) p.*", desc)|json}\n'
  {"0": "no person", "1": "no"}
  $ hg log -R a -r2 -T '{search(r"q", desc)|json}\n'
  null

 group reference

  $ hg log -R a -r2 -T '{search(r"(no) (p.*)", desc) % "{1|upper} {2|hex}"}\n'
  NO 706572736f6e
  $ hg log -R a -r2 -T '{search(r"(?P<foo>[a-z]*)", desc) % "{foo}"}\n'
  no
  $ hg log -R a -r2 -T '{search(r"(?P<foo>[a-z]*)", desc).foo}\n'
  no

 group reference with no match

  $ hg log -R a -r2 -T '{search(r"q", desc) % "match: {0}"}\n'
  

 bad group names

  $ hg log -R a -r2 -T '{search(r"(?P<0>.)", desc) % "{0}"}\n'
  hg: parse error: search got an invalid pattern: (?P<0>.)
  [10]
  $ hg log -R a -r2 -T '{search(r"(?P<repo>.)", desc) % "{repo}"}\n'
  hg: parse error: invalid group 'repo' in search pattern: (?P<repo>.)
  [10]

Test the sub function of templating for expansion:

  $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
  xx

  $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
  hg: parse error: sub got an invalid pattern: [
  [10]
  $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
  hg: parse error: sub got an invalid replacement: \1
  [10]

Test the strip function with chars specified:

  $ hg log -R latesttag --template '{desc}\n'
  at3
  t5
  t4
  t3
  t2
  t1
  merge
  h2e
  h2d
  h1c
  b
  a

  $ hg log -R latesttag --template '{strip(desc, "te")}\n'
  at3
  5
  4
  3
  2
  1
  merg
  h2
  h2d
  h1c
  b
  a

Test date format:

  $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
  date: 70 01 01 10 +0000
  date: 70 01 01 09 +0000
  date: 70 01 01 04 +0000
  date: 70 01 01 08 +0000
  date: 70 01 01 07 +0000
  date: 70 01 01 06 +0000
  date: 70 01 01 05 +0100
  date: 70 01 01 04 +0000
  date: 70 01 01 03 +0000
  date: 70 01 01 02 +0000
  date: 70 01 01 01 +0000
  date: 70 01 01 00 +0000

Test invalid date:

  $ hg log -R latesttag -T '{date(rev)}\n'
  hg: parse error: date expects a date information
  [10]

Set up repository containing template fragments in commit metadata:

  $ hg init r
  $ cd r
  $ echo a > a
  $ hg ci -Am '{rev}'
  adding a

  $ hg branch -q 'text.{rev}'
  $ echo aa >> aa
  $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'

color effect can be specified without quoting:

  $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
  \x1b[0;31mtext\x1b[0m (esc)

color effects can be nested (issue5413)

  $ hg debugtemplate --color=always \
  > '{label(red, "red{label(magenta, "ma{label(cyan, "cyan")}{label(yellow, "yellow")}genta")}")}\n'
  \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)

pad() should interact well with color codes (issue5416)

  $ hg debugtemplate --color=always \
  > '{pad(label(red, "red"), 5, label(cyan, "-"))}\n'
  \x1b[0;31mred\x1b[0m\x1b[0;36m-\x1b[0m\x1b[0;36m-\x1b[0m (esc)

pad() with truncate has to strip color codes, though

  $ hg debugtemplate --color=always \
  > '{pad(label(red, "scarlet"), 5, truncate=true)}\n'
  scarl

label should be no-op if color is disabled:

  $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
  text
  $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
  text

Test branches inside if statement:

  $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
  no

Test dict constructor:

  $ hg log -r 0 -T '{dict(y=node|short, x=rev)}\n'
  y=f7769ec2ab97 x=0
  $ hg log -r 0 -T '{dict(x=rev, y=node|short) % "{key}={value}\n"}'
  x=0
  y=f7769ec2ab97
  $ hg log -r 0 -T '{dict(x=rev, y=node|short)|json}\n'
  {"x": 0, "y": "f7769ec2ab97"}
  $ hg log -r 0 -T '{dict()|json}\n'
  {}

  $ hg log -r 0 -T '{dict(rev, node=node|short)}\n'
  rev=0 node=f7769ec2ab97
  $ hg log -r 0 -T '{dict(rev, node|short)}\n'
  rev=0 node=f7769ec2ab97

  $ hg log -r 0 -T '{dict(rev, rev=rev)}\n'
  hg: parse error: duplicated dict key 'rev' inferred
  [10]
  $ hg log -r 0 -T '{dict(node, node|short)}\n'
  hg: parse error: duplicated dict key 'node' inferred
  [10]
  $ hg log -r 0 -T '{dict(1 + 2)}'
  hg: parse error: dict key cannot be inferred
  [10]

  $ hg log -r 0 -T '{dict(x=rev, x=node)}'
  hg: parse error: dict got multiple values for keyword argument 'x'
  [10]

Test get function:

  $ hg log -r 0 --template '{get(extras, "branch")}\n'
  default
  $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
  default
  $ hg log -r 0 --template '{get(files, "should_fail")}\n'
  hg: parse error: not a dictionary
  (get() expects a dict as first argument)
  [10]

Test json filter applied to wrapped object:

  $ hg log -r0 -T '{files|json}\n'
  ["a"]
  $ hg log -r0 -T '{extras|json}\n'
  {"branch": "default"}
  $ hg log -r0 -T '{date|json}\n'
  [0, 0]
  $ hg log -r0 -T '{revset(":")|json}\n'
  [0, 1]

Test json filter applied to map result:

  $ hg log -r0 -T '{json(extras % "{key}")}\n'
  ["branch"]

Test localdate(date, tz) function:

  $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
  1970-01-01 09:00 +0900
  $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
  1970-01-01 00:00 +0000
  $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
  hg: parse error: localdate expects a timezone
  [10]
  $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
  1970-01-01 02:00 +0200
  $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
  1970-01-01 00:00 +0000
  $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
  1970-01-01 00:00 +0000
  $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
  hg: parse error: localdate expects a timezone
  [10]
  $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
  hg: parse error: localdate expects a timezone
  [10]

Test shortest(node) function:

  $ echo b > b
  $ hg ci -qAm b
  $ hg log --template '{shortest(node)}\n'
  e777
  bcc7
  f776
  $ hg log --template '{shortest(node, 10)}\n'
  e777603221
  bcc7ff960b
  f7769ec2ab
  $ hg log --template '{shortest(node, 1)}\n' -r null
  00
  $ hg log --template '{node|shortest}\n' -l1
  e777

  $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
  f7769ec2ab
  $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
  hg: parse error: shortest() expects an integer minlength
  [10]

  $ hg log -r 'wdir()' -T '{node|shortest}\n'
  ffff

  $ hg log --template '{shortest("f")}\n' -l1
  f

  $ hg log --template '{shortest("0123456789012345678901234567890123456789")}\n' -l1
  0123456789012345678901234567890123456789

  $ hg log --template '{shortest("01234567890123456789012345678901234567890123456789")}\n' -l1
  01234567890123456789012345678901234567890123456789

  $ hg log --template '{shortest("not a hex string")}\n' -l1
  not a hex string

  $ hg log --template '{shortest("not a hex string, but it'\''s 40 bytes long")}\n' -l1
  not a hex string, but it's 40 bytes long

  $ hg log --template '{shortest("ffffffffffffffffffffffffffffffffffffffff")}\n' -l1
  ffff

  $ hg log --template '{shortest("fffffff")}\n' -l1
  ffff

  $ hg log --template '{shortest("ff")}\n' -l1
  ffff

  $ cd ..

Test shortest(node) with the repo having short hash collision:

  $ hg init hashcollision
  $ cd hashcollision
  $ cat <<EOF >> .hg/hgrc
  > [experimental]
  > evolution.createmarkers=True
  > EOF
  $ echo 0 > a
  $ hg ci -qAm 0
  $ for i in 17 129 248 242 480 580 617 1057 2857 4025; do
  >   hg up -q 0
  >   echo $i > a
  >   hg ci -qm $i
  > done
  $ hg up -q null
  $ hg log -r0: -T '{rev}:{node}\n'
  0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
  1:11424df6dc1dd4ea255eae2b58eaca7831973bbc
  2:11407b3f1b9c3e76a79c1ec5373924df096f0499
  3:11dd92fe0f39dfdaacdaa5f3997edc533875cfc4
  4:10776689e627b465361ad5c296a20a487e153ca4
  5:a00be79088084cb3aff086ab799f8790e01a976b
  6:a0b0acd79b4498d0052993d35a6a748dd51d13e6
  7:a0457b3450b8e1b778f1163b31a435802987fe5d
  8:c56256a09cd28e5764f32e8e2810d0f01e2e357a
  9:c5623987d205cd6d9d8389bfc40fff9dbb670b48
  10:c562ddd9c94164376c20b86b0b4991636a3bf84f
  $ hg debugobsolete a00be79088084cb3aff086ab799f8790e01a976b
  1 new obsolescence markers
  obsoleted 1 changesets
  $ hg debugobsolete c5623987d205cd6d9d8389bfc40fff9dbb670b48
  1 new obsolescence markers
  obsoleted 1 changesets
  $ hg debugobsolete c562ddd9c94164376c20b86b0b4991636a3bf84f
  1 new obsolescence markers
  obsoleted 1 changesets

 nodes starting with '11' (we don't have the revision number '11' though)

  $ hg log -r 1:3 -T '{rev}:{shortest(node, 0)}\n'
  1:1142
  2:1140
  3:11d

 '5:a00' is hidden, but still we have two nodes starting with 'a0'

  $ hg log -r 6:7 -T '{rev}:{shortest(node, 0)}\n'
  6:a0b
  7:a04

 node '10' conflicts with the revision number '10' even if it is hidden
 (we could exclude hidden revision numbers, but currently we don't)

  $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n'
  4:107
  $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
  4:107

  $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n'
  4:x10
  $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
  4:x10

 node 'c562' should be unique if the other 'c562' nodes are hidden
 (but we don't try the slow path to filter out hidden nodes for now)

  $ hg log -r 8 -T '{rev}:{node|shortest}\n'
  8:c5625
  $ hg log -r 8:10 -T '{rev}:{node|shortest}\n' --hidden
  8:c5625
  9:c5623
  10:c562d

  $ cd ..

Test prefixhexnode when the first character of the hash is 0.
  $ hg init hashcollision2
  $ cd hashcollision2
  $ cat <<EOF >> .hg/hgrc
  > [experimental]
  > evolution.createmarkers=True
  > EOF
  $ echo 0 > a
  $ hg ci -qAm 0
  $ echo 21 > a
  $ hg ci -qm 21
  $ hg up -q null
  $ hg log -r0: -T '{rev}:{node}\n'
  0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
  1:0cf177ba2b1dc3862a00fb81715fec90950201be

 we need the 'x' prefix to ensure we aren't colliding with rev0. We identify
 the collision with nullid if we aren't using disambiguatewithin, so we need to set
 that as well.
  $ hg --config experimental.revisions.disambiguatewithin='descendants(0)' \
  >    --config experimental.revisions.prefixhexnode=yes \
  >    log -r 1 -T '{rev}:{shortest(node, 0)}\n'
  1:x0

  $ hg debugobsolete 0cf177ba2b1dc3862a00fb81715fec90950201be
  1 new obsolescence markers
  obsoleted 1 changesets
  $ hg up -q 0
  $ echo 61 > a
  $ hg ci -m 61
  $ hg log -r0: -T '{rev}:{node}\n'
  0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
  2:01384dde84b3a511ae0835f35ac40bd806c99bb8

 we still have the 'x' prefix because '0' is still the shortest prefix, since
 rev1's '0c' is hidden.
  $ hg --config experimental.revisions.disambiguatewithin=0:-1-0 \
  >    --config experimental.revisions.prefixhexnode=yes \
  >    log -r 0:-1-0 -T '{rev}:{shortest(node, 0)}\n'
  2:x0

 we don't have the 'x' prefix on 2 because '01' is not a synonym for rev1.
  $ hg --config experimental.revisions.disambiguatewithin=0:-1-0 \
  >    --config experimental.revisions.prefixhexnode=yes \
  >    log -r 0:-1-0 -T '{rev}:{shortest(node, 0)}\n' --hidden
  1:0c
  2:01

  $ cd ..

Test pad function

  $ cd r

  $ hg log --template '{pad(rev, 20)} {author|user}\n'
  2                    test
  1                    {node|short}
  0                    test

  $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
                     2 test
                     1 {node|short}
                     0 test

  $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
  2------------------- test
  1------------------- {node|short}
  0------------------- test

  $ hg log --template '{pad(author, 5, "-", False, True)}\n'
  test-
  {node
  test-
  $ hg log --template '{pad(author, 5, "-", True, True)}\n'
  -test
  hort}
  -test

Test template string in pad function

  $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
  {0}        test

  $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
  \{rev}     test

Test width argument passed to pad function

  $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
  0          test
  $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
  hg: parse error: pad() expects an integer width
  [10]

Test invalid fillchar passed to pad function

  $ hg log -r 0 -T '{pad(rev, 10, "")}\n'
  hg: parse error: pad() expects a single fill character
  [10]
  $ hg log -r 0 -T '{pad(rev, 10, "--")}\n'
  hg: parse error: pad() expects a single fill character
  [10]

Test boolean argument passed to pad function

 no crash

  $ hg log -r 0 -T '{pad(rev, 10, "-", "f{"oo"}")}\n'
  ---------0

 string/literal

  $ hg log -r 0 -T '{pad(rev, 10, "-", "false")}\n'
  ---------0
  $ hg log -r 0 -T '{pad(rev, 10, "-", false)}\n'
  0---------
  $ hg log -r 0 -T '{pad(rev, 10, "-", "")}\n'
  0---------

 unknown keyword is evaluated to ''

  $ hg log -r 0 -T '{pad(rev, 10, "-", unknownkeyword)}\n'
  0---------

Test separate function

  $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
  a-b-c
  $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
  0:f7769ec2ab97 test default
  $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
  a \x1b[0;31mb\x1b[0m c d (esc)

Test boolean expression/literal passed to if function

  $ hg log -r 0 -T '{if(rev, "rev 0 is True")}\n'
  rev 0 is True
  $ hg log -r 0 -T '{if(0, "literal 0 is True as well")}\n'
  literal 0 is True as well
  $ hg log -r 0 -T '{if(min(revset(r"0")), "0 of hybriditem is also True")}\n'
  0 of hybriditem is also True
  $ hg log -r 0 -T '{if("", "", "empty string is False")}\n'
  empty string is False
  $ hg log -r 0 -T '{if(revset(r"0 - 0"), "", "empty list is False")}\n'
  empty list is False
  $ hg log -r 0 -T '{if(revset(r"0"), "non-empty list is True")}\n'
  non-empty list is True
  $ hg log -r 0 -T '{if(revset(r"0") % "", "list of empty strings is True")}\n'
  list of empty strings is True
  $ hg log -r 0 -T '{if(true, "true is True")}\n'
  true is True
  $ hg log -r 0 -T '{if(false, "", "false is False")}\n'
  false is False
  $ hg log -r 0 -T '{if("false", "non-empty string is True")}\n'
  non-empty string is True

Test ifcontains function

  $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
  2 is in the string
  1 is not
  0 is in the string

  $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
  2 is in the string
  1 is not
  0 is in the string

  $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
  2 did not add a
  1 did not add a
  0 added a

  $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
  2 is parent of 1
  1
  0

  $ hg log -l1 -T '{ifcontains("branch", extras, "t", "f")}\n'
  t
  $ hg log -l1 -T '{ifcontains("branch", extras % "{key}", "t", "f")}\n'
  t
  $ hg log -l1 -T '{ifcontains("branc", extras % "{key}", "t", "f")}\n'
  f
  $ hg log -l1 -T '{ifcontains("branc", stringify(extras % "{key}"), "t", "f")}\n'
  t

Test revset function

  $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
  2 current rev
  1 not current rev
  0 not current rev

  $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
  2 match rev
  1 match rev
  0 not match rev

  $ hg log -T '{ifcontains(desc, revset(":"), "", "type not match")}\n' -l1
  type not match

  $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
  2 Parents: 1
  1 Parents: 0
  0 Parents: 

  $ cat >> .hg/hgrc <<EOF
  > [revsetalias]
  > myparents(\$1) = parents(\$1)
  > EOF
  $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
  2 Parents: 1
  1 Parents: 0
  0 Parents: 

  $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
  Rev: 2
  Ancestor: 0
  Ancestor: 1
  Ancestor: 2
  
  Rev: 1
  Ancestor: 0
  Ancestor: 1
  
  Rev: 0
  Ancestor: 0
  
  $ hg log --template '{revset("TIP"|lower)}\n' -l1
  2

  $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
  2

 a list template is evaluated for each item of revset/parents

  $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
  2 p: 1:bcc7ff960b8e
  1 p: 0:f7769ec2ab97
  0 p: 

  $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
  2 p: 1:bcc7ff960b8e -1:000000000000
  1 p: 0:f7769ec2ab97 -1:000000000000
  0 p: -1:000000000000 -1:000000000000

 therefore, 'revcache' should be recreated for each rev

  $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
  2 aa b
  p 
  1 
  p a
  0 a
  p 

  $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
  2 aa b
  p 
  1 
  p a
  0 a
  p 

a revset item must be evaluated as an integer revision, not an offset from tip

  $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
  -1:000000000000
  $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
  -1:000000000000

join() should pick '{rev}' from revset items:

  $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
  4, 5

on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
default. join() should agree with the default formatting:

  $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
  5:13207e5a10d9, 4:bbe44766e73d

  $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
  5:13207e5a10d9fd28ec424934298e176197f2c67f,
  4:bbe44766e73d5f11ed2177f1838de10c53ef3e74

for historical reasons, revset() supports old-style list template

  $ hg log -T '{revset(":")}\n' -l1 \
  >        --config templates.start_revisions='"["' \
  >        --config templates.end_revisions='"]"' \
  >        --config templates.revision='"{revision}, "' \
  >        --config templates.last_revision='"{revision}"'
  [0, 1, 2]
  $ hg log -T '{revset(":") % " {revision}"}\n' -l1
   0 1 2

but a filtered one doesn't

  $ hg log -T '{filter(revset(":"), ifeq(rev, 1, "", "y"))}\n' -l1 \
  >        --config templates.start_revisions='"["' \
  >        --config templates.end_revisions='"]"' \
  >        --config templates.revision='"{revision}, "' \
  >        --config templates.last_revision='"{revision}"'
  0 2
  $ hg log -T '{filter(revset(":"), ifeq(rev, 1, "", "y")) % "x{revision}"}\n' -l1
  xx

%d parameter handling:

  $ hg log -T '{revset("%d", rev)}\n' -r'wdir()'
  2147483647
  $ hg log -T '{revset("%d", rev)}\n' -r'null'
  -1
  $ hg log -T '{revset("%d", rev + 1)}\n' -r'tip'
  abort: unknown revision '3'
  [255]
  $ hg log -T '{revset("%d", rev - 1)}\n' -r'null'
  abort: unknown revision '-2'
  [255]

Invalid arguments passed to revset()

  $ hg log -T '{revset("%whatever", 0)}\n'
  hg: parse error: unexpected revspec format character w
  [10]
  $ hg log -T '{revset("%lwhatever", files)}\n'
  hg: parse error: unexpected revspec format character w
  [10]
  $ hg log -T '{revset("%s %s", 0)}\n'
  hg: parse error: missing argument for revspec
  [10]
  $ hg log -T '{revset("", 0)}\n'
  hg: parse error: too many revspec arguments specified
  [10]
  $ hg log -T '{revset("%s", 0, 1)}\n'
  hg: parse error: too many revspec arguments specified
  [10]
  $ hg log -T '{revset("%", 0)}\n'
  hg: parse error: incomplete revspec format character
  [10]
  $ hg log -T '{revset("%l", 0)}\n'
  hg: parse error: incomplete revspec format character
  [10]
  $ hg log -T '{revset("%d", 'foo')}\n'
  hg: parse error: invalid argument for revspec
  [10]
  $ hg log -T '{revset("%ld", files)}\n'
  hg: parse error: invalid argument for revspec
  [10]
  $ hg log -T '{revset("%ls", 0)}\n'
  hg: parse error: invalid argument for revspec
  [10]
  $ hg log -T '{revset("%b", 'foo')}\n'
  hg: parse error: invalid argument for revspec
  [10]
  $ hg log -T '{revset("%lb", files)}\n'
  hg: parse error: invalid argument for revspec
  [10]
  $ hg log -T '{revset("%r", 0)}\n'
  hg: parse error: invalid argument for revspec
  [10]

Invalid operation on revset()

  $ hg log -T '{get(revset(":"), "foo")}\n'
  hg: parse error: not a dictionary
  (get() expects a dict as first argument)
  [10]

Test files function

  $ hg log -T "{rev}\n{join(files('*'), '\n')}\n"
  2
  a
  aa
  b
  1
  a
  0
  a

  $ hg log -T "{rev}\n{join(files('aa'), '\n')}\n"
  2
  aa
  1
  
  0
  

  $ hg log -l1 -T "{files('aa') % '{file}\n'}"
  aa
  $ hg log -l1 -T "{files('aa') % '{path}\n'}"
  aa

  $ hg rm a
  $ hg log -r "wdir()" -T "{rev}\n{join(files('*'), '\n')}\n"
  2147483647
  aa
  b
  $ hg revert a

Test relpath function

  $ hg log -r0 -T '{files % "{file|relpath}\n"}'
  a
  $ cd ..
  $ hg log -R r -r0 -T '{files % "{file|relpath}\n"}'
  r/a

Test stringify on sub expressions

  $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
  fourth, second, third
  $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
  abc

Test splitlines

  $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
  @  foo Modify, add, remove, rename
  |
  o  foo future
  |
  o  foo third
  |
  o  foo second
  
  o    foo merge
  |\
  | o  foo new head
  | |
  o |  foo new branch
  |/
  o  foo no user, no domain
  |
  o  foo no person
  |
  o  foo other 1
  |  foo other 2
  |  foo
  |  foo other 3
  o  foo line 1
     foo line 2

  $ hg log -R a -r0 -T '{desc|splitlines}\n'
  line 1 line 2
  $ hg log -R a -r0 -T '{join(desc|splitlines, "|")}\n'
  line 1|line 2

Test startswith
  $ hg log -Gv -R a --template "{startswith(desc)}"
  hg: parse error: startswith expects two arguments
  [10]

  $ hg log -Gv -R a --template "{startswith('line', desc)}"
  @
  |
  o
  |
  o
  |
  o
  
  o
  |\
  | o
  | |
  o |
  |/
  o
  |
  o
  |
  o
  |
  o  line 1
     line 2

Test word function (including index out of bounds graceful failure)

  $ hg log -Gv -R a --template "{word('1', desc)}"
  @  add,
  |
  o
  |
  o
  |
  o
  
  o
  |\
  | o  head
  | |
  o |  branch
  |/
  o  user,
  |
  o  person
  |
  o  1
  |
  o  1
  

Test word third parameter used as splitter

  $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
  @  M
  |
  o  future
  |
  o  third
  |
  o  sec
  
  o    merge
  |\
  | o  new head
  | |
  o |  new branch
  |/
  o  n
  |
  o  n
  |
  o
  |
  o  line 1
     line 2

Test word error messages for not enough and too many arguments

  $ hg log -Gv -R a --template "{word('0')}"
  hg: parse error: word expects two or three arguments, got 1
  [10]

  $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
  hg: parse error: word expects two or three arguments, got 7
  [10]

Test word for integer literal

  $ hg log -R a --template "{word(2, desc)}\n" -r0
  line

Test word for invalid numbers

  $ hg log -Gv -R a --template "{word('a', desc)}"
  hg: parse error: word expects an integer index
  [10]

Test word for out of range

  $ hg log -R a --template "{word(10000, desc)}"
  $ hg log -R a --template "{word(-10000, desc)}"

Test indent and not adding to empty lines

  $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
  -----
   > line 1
  >> line 2
  -----
   > other 1
  >> other 2
  
  >> other 3

Test indent with empty first line

  $ hg version -T "{indent('', '>> ')}\n"
  

  $ hg version -T "{indent('
  > second', '>> ')}\n"
  
  >> second

  $ hg version -T "{indent('
  > second', '>> ', ' > ')}\n"
  
  >> second

Test with non-strings like dates

  $ hg log -T "{indent(date, '   ')}\n" -r 2:3 -R a
     1200000.00
     1300000.00

Test cbor filter:

  $ cat <<'EOF' > "$TESTTMP/decodecbor.py"
  > from __future__ import absolute_import
  > from mercurial import (
  >     dispatch,
  > )
  > from mercurial.utils import (
  >     cborutil,
  >     procutil,
  >     stringutil,
  > )
  > dispatch.initstdio()
  > items = cborutil.decodeall(procutil.stdin.read())
  > procutil.stdout.write(stringutil.pprint(items, indent=1) + b'\n')
  > EOF

  $ hg log -T "{rev|cbor}" -R a -l2 | "$PYTHON" "$TESTTMP/decodecbor.py"
  [
   10,
   9
  ]

  $ hg log -T "{extras|cbor}" -R a -l1 | "$PYTHON" "$TESTTMP/decodecbor.py"
  [
   {
    'branch': 'default'
   }
  ]

  $ hg log -T "{revset(':')|cbor}" -R a -l1 | "$PYTHON" "$TESTTMP/decodecbor.py"
  [
   [
    0,
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10
   ]
  ]

  $ hg log -T "{dict(foo=revset('.'))|cbor}" -R a -l1 | "$PYTHON" "$TESTTMP/decodecbor.py"
  [
   {
    'foo': [
     10
    ]
   }
  ]

json filter should escape HTML tags so that the output can be embedded in hgweb:

  $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
  "\u003cfoo@example.org\u003e"

Set up repository for non-ascii encoding tests:

  $ hg init nonascii
  $ cd nonascii
  $ "$PYTHON" <<EOF
  > open('latin1', 'wb').write(b'\xe9')
  > open('utf-8', 'wb').write(b'\xc3\xa9')
  > EOF
  $ HGENCODING=utf-8 hg branch -q `cat utf-8`
  $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8

json filter should try round-trip conversion to utf-8:

  $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
  "\u00e9"
  $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
  "non-ascii branch: \u00e9"

json filter should take input as utf-8 if it was converted from utf-8:

  $ HGENCODING=latin-1 hg log -T "{branch|json}\n" -r0
  "\u00e9"
  $ HGENCODING=latin-1 hg log -T "{desc|json}\n" -r0
  "non-ascii branch: \u00e9"

json filter takes input as utf-8b:

  $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
  "\u00e9"
  $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
  "\udce9"

cbor filter is bytes transparent, which should handle bytes subtypes
as bytes:

  $ HGENCODING=ascii hg log -T "{branch|cbor}" -r0 \
  > | "$PYTHON" "$TESTTMP/decodecbor.py"
  [
   '?'
  ]
  $ HGENCODING=latin-1 hg log -T "{branch|cbor}" -r0 \
  > | "$PYTHON" "$TESTTMP/decodecbor.py"
  [
   '\xe9'
  ]

utf8 filter:

  $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
  round-trip: c3a9
  $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
  decoded: c3a9
  $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
  abort: decoding near * (glob)
  [255]
  $ hg log -T "coerced to string: {rev|utf8}\n" -r0
  coerced to string: 0

pad width:

  $ HGENCODING=utf-8 hg debugtemplate "{pad('`cat utf-8`', 2, '-')}\n"
  \xc3\xa9- (esc)

read config options:

  $ hg log -T "{config('templateconfig', 'knob', 'foo')}\n"
  foo
  $ hg log -T "{config('templateconfig', 'knob', 'foo')}\n" \
  > --config templateconfig.knob=bar
  bar
  $ hg log -T "{configbool('templateconfig', 'knob', True)}\n"
  True
  $ hg log -T "{configbool('templateconfig', 'knob', True)}\n" \
  > --config templateconfig.knob=0
  False
  $ hg log -T "{configint('templateconfig', 'knob', 123)}\n"
  123
  $ hg log -T "{configint('templateconfig', 'knob', 123)}\n" \
  > --config templateconfig.knob=456
  456
  $ hg log -T "{config('templateconfig', 'knob')}\n"
  devel-warn: config item requires an explicit default value: 'templateconfig.knob' at: * (glob)
  
  $ hg log -T "{configbool('ui', 'interactive')}\n"
  False
  $ hg log -T "{configbool('ui', 'interactive')}\n" --config ui.interactive=1
  True
  $ hg log -T "{config('templateconfig', 'knob', if(true, 'foo', 'bar'))}\n"
  foo

  $ cd ..