##// END OF EJS Templates
revset: allow repo.revs('%d', wdirrev)...
Yuya Nishihara -
r45073:482a6aac default
parent child Browse files
Show More
@@ -1,2725 +1,2726 b''
1 # revset.py - revision set queries for mercurial
1 # revset.py - revision set queries for mercurial
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13 from .pycompat import getattr
13 from .pycompat import getattr
14 from . import (
14 from . import (
15 dagop,
15 dagop,
16 destutil,
16 destutil,
17 diffutil,
17 diffutil,
18 encoding,
18 encoding,
19 error,
19 error,
20 hbisect,
20 hbisect,
21 match as matchmod,
21 match as matchmod,
22 node,
22 node,
23 obsolete as obsmod,
23 obsolete as obsmod,
24 obsutil,
24 obsutil,
25 pathutil,
25 pathutil,
26 phases,
26 phases,
27 pycompat,
27 pycompat,
28 registrar,
28 registrar,
29 repoview,
29 repoview,
30 revsetlang,
30 revsetlang,
31 scmutil,
31 scmutil,
32 smartset,
32 smartset,
33 stack as stackmod,
33 stack as stackmod,
34 util,
34 util,
35 )
35 )
36 from .utils import (
36 from .utils import (
37 dateutil,
37 dateutil,
38 stringutil,
38 stringutil,
39 )
39 )
40
40
41 # helpers for processing parsed tree
41 # helpers for processing parsed tree
42 getsymbol = revsetlang.getsymbol
42 getsymbol = revsetlang.getsymbol
43 getstring = revsetlang.getstring
43 getstring = revsetlang.getstring
44 getinteger = revsetlang.getinteger
44 getinteger = revsetlang.getinteger
45 getboolean = revsetlang.getboolean
45 getboolean = revsetlang.getboolean
46 getlist = revsetlang.getlist
46 getlist = revsetlang.getlist
47 getintrange = revsetlang.getintrange
47 getintrange = revsetlang.getintrange
48 getargs = revsetlang.getargs
48 getargs = revsetlang.getargs
49 getargsdict = revsetlang.getargsdict
49 getargsdict = revsetlang.getargsdict
50
50
51 baseset = smartset.baseset
51 baseset = smartset.baseset
52 generatorset = smartset.generatorset
52 generatorset = smartset.generatorset
53 spanset = smartset.spanset
53 spanset = smartset.spanset
54 fullreposet = smartset.fullreposet
54 fullreposet = smartset.fullreposet
55
55
56 # revisions not included in all(), but populated if specified
56 # revisions not included in all(), but populated if specified
57 _virtualrevs = (node.nullrev, node.wdirrev)
57 _virtualrevs = (node.nullrev, node.wdirrev)
58
58
59 # Constants for ordering requirement, used in getset():
59 # Constants for ordering requirement, used in getset():
60 #
60 #
61 # If 'define', any nested functions and operations MAY change the ordering of
61 # If 'define', any nested functions and operations MAY change the ordering of
62 # the entries in the set (but if changes the ordering, it MUST ALWAYS change
62 # the entries in the set (but if changes the ordering, it MUST ALWAYS change
63 # it). If 'follow', any nested functions and operations MUST take the ordering
63 # it). If 'follow', any nested functions and operations MUST take the ordering
64 # specified by the first operand to the '&' operator.
64 # specified by the first operand to the '&' operator.
65 #
65 #
66 # For instance,
66 # For instance,
67 #
67 #
68 # X & (Y | Z)
68 # X & (Y | Z)
69 # ^ ^^^^^^^
69 # ^ ^^^^^^^
70 # | follow
70 # | follow
71 # define
71 # define
72 #
72 #
73 # will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
73 # will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
74 # of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
74 # of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
75 #
75 #
76 # 'any' means the order doesn't matter. For instance,
76 # 'any' means the order doesn't matter. For instance,
77 #
77 #
78 # (X & !Y) | ancestors(Z)
78 # (X & !Y) | ancestors(Z)
79 # ^ ^
79 # ^ ^
80 # any any
80 # any any
81 #
81 #
82 # For 'X & !Y', 'X' decides the order and 'Y' is subtracted from 'X', so the
82 # For 'X & !Y', 'X' decides the order and 'Y' is subtracted from 'X', so the
83 # order of 'Y' does not matter. For 'ancestors(Z)', Z's order does not matter
83 # order of 'Y' does not matter. For 'ancestors(Z)', Z's order does not matter
84 # since 'ancestors' does not care about the order of its argument.
84 # since 'ancestors' does not care about the order of its argument.
85 #
85 #
86 # Currently, most revsets do not care about the order, so 'define' is
86 # Currently, most revsets do not care about the order, so 'define' is
87 # equivalent to 'follow' for them, and the resulting order is based on the
87 # equivalent to 'follow' for them, and the resulting order is based on the
88 # 'subset' parameter passed down to them:
88 # 'subset' parameter passed down to them:
89 #
89 #
90 # m = revset.match(...)
90 # m = revset.match(...)
91 # m(repo, subset, order=defineorder)
91 # m(repo, subset, order=defineorder)
92 # ^^^^^^
92 # ^^^^^^
93 # For most revsets, 'define' means using the order this subset provides
93 # For most revsets, 'define' means using the order this subset provides
94 #
94 #
95 # There are a few revsets that always redefine the order if 'define' is
95 # There are a few revsets that always redefine the order if 'define' is
96 # specified: 'sort(X)', 'reverse(X)', 'x:y'.
96 # specified: 'sort(X)', 'reverse(X)', 'x:y'.
97 anyorder = b'any' # don't care the order, could be even random-shuffled
97 anyorder = b'any' # don't care the order, could be even random-shuffled
98 defineorder = b'define' # ALWAYS redefine, or ALWAYS follow the current order
98 defineorder = b'define' # ALWAYS redefine, or ALWAYS follow the current order
99 followorder = b'follow' # MUST follow the current order
99 followorder = b'follow' # MUST follow the current order
100
100
101 # helpers
101 # helpers
102
102
103
103
104 def getset(repo, subset, x, order=defineorder):
104 def getset(repo, subset, x, order=defineorder):
105 if not x:
105 if not x:
106 raise error.ParseError(_(b"missing argument"))
106 raise error.ParseError(_(b"missing argument"))
107 return methods[x[0]](repo, subset, *x[1:], order=order)
107 return methods[x[0]](repo, subset, *x[1:], order=order)
108
108
109
109
110 def _getrevsource(repo, r):
110 def _getrevsource(repo, r):
111 extra = repo[r].extra()
111 extra = repo[r].extra()
112 for label in (b'source', b'transplant_source', b'rebase_source'):
112 for label in (b'source', b'transplant_source', b'rebase_source'):
113 if label in extra:
113 if label in extra:
114 try:
114 try:
115 return repo[extra[label]].rev()
115 return repo[extra[label]].rev()
116 except error.RepoLookupError:
116 except error.RepoLookupError:
117 pass
117 pass
118 return None
118 return None
119
119
120
120
121 def _sortedb(xs):
121 def _sortedb(xs):
122 return sorted(pycompat.rapply(pycompat.maybebytestr, xs))
122 return sorted(pycompat.rapply(pycompat.maybebytestr, xs))
123
123
124
124
125 # operator methods
125 # operator methods
126
126
127
127
128 def stringset(repo, subset, x, order):
128 def stringset(repo, subset, x, order):
129 if not x:
129 if not x:
130 raise error.ParseError(_(b"empty string is not a valid revision"))
130 raise error.ParseError(_(b"empty string is not a valid revision"))
131 x = scmutil.intrev(scmutil.revsymbol(repo, x))
131 x = scmutil.intrev(scmutil.revsymbol(repo, x))
132 if x in subset or x in _virtualrevs and isinstance(subset, fullreposet):
132 if x in subset or x in _virtualrevs and isinstance(subset, fullreposet):
133 return baseset([x])
133 return baseset([x])
134 return baseset()
134 return baseset()
135
135
136
136
137 def rawsmartset(repo, subset, x, order):
137 def rawsmartset(repo, subset, x, order):
138 """argument is already a smartset, use that directly"""
138 """argument is already a smartset, use that directly"""
139 if order == followorder:
139 if order == followorder:
140 return subset & x
140 return subset & x
141 else:
141 else:
142 return x & subset
142 return x & subset
143
143
144
144
145 def rangeset(repo, subset, x, y, order):
145 def rangeset(repo, subset, x, y, order):
146 m = getset(repo, fullreposet(repo), x)
146 m = getset(repo, fullreposet(repo), x)
147 n = getset(repo, fullreposet(repo), y)
147 n = getset(repo, fullreposet(repo), y)
148
148
149 if not m or not n:
149 if not m or not n:
150 return baseset()
150 return baseset()
151 return _makerangeset(repo, subset, m.first(), n.last(), order)
151 return _makerangeset(repo, subset, m.first(), n.last(), order)
152
152
153
153
154 def rangeall(repo, subset, x, order):
154 def rangeall(repo, subset, x, order):
155 assert x is None
155 assert x is None
156 return _makerangeset(repo, subset, 0, repo.changelog.tiprev(), order)
156 return _makerangeset(repo, subset, 0, repo.changelog.tiprev(), order)
157
157
158
158
159 def rangepre(repo, subset, y, order):
159 def rangepre(repo, subset, y, order):
160 # ':y' can't be rewritten to '0:y' since '0' may be hidden
160 # ':y' can't be rewritten to '0:y' since '0' may be hidden
161 n = getset(repo, fullreposet(repo), y)
161 n = getset(repo, fullreposet(repo), y)
162 if not n:
162 if not n:
163 return baseset()
163 return baseset()
164 return _makerangeset(repo, subset, 0, n.last(), order)
164 return _makerangeset(repo, subset, 0, n.last(), order)
165
165
166
166
167 def rangepost(repo, subset, x, order):
167 def rangepost(repo, subset, x, order):
168 m = getset(repo, fullreposet(repo), x)
168 m = getset(repo, fullreposet(repo), x)
169 if not m:
169 if not m:
170 return baseset()
170 return baseset()
171 return _makerangeset(
171 return _makerangeset(
172 repo, subset, m.first(), repo.changelog.tiprev(), order
172 repo, subset, m.first(), repo.changelog.tiprev(), order
173 )
173 )
174
174
175
175
176 def _makerangeset(repo, subset, m, n, order):
176 def _makerangeset(repo, subset, m, n, order):
177 if m == n:
177 if m == n:
178 r = baseset([m])
178 r = baseset([m])
179 elif n == node.wdirrev:
179 elif n == node.wdirrev:
180 r = spanset(repo, m, len(repo)) + baseset([n])
180 r = spanset(repo, m, len(repo)) + baseset([n])
181 elif m == node.wdirrev:
181 elif m == node.wdirrev:
182 r = baseset([m]) + spanset(repo, repo.changelog.tiprev(), n - 1)
182 r = baseset([m]) + spanset(repo, repo.changelog.tiprev(), n - 1)
183 elif m < n:
183 elif m < n:
184 r = spanset(repo, m, n + 1)
184 r = spanset(repo, m, n + 1)
185 else:
185 else:
186 r = spanset(repo, m, n - 1)
186 r = spanset(repo, m, n - 1)
187
187
188 if order == defineorder:
188 if order == defineorder:
189 return r & subset
189 return r & subset
190 else:
190 else:
191 # carrying the sorting over when possible would be more efficient
191 # carrying the sorting over when possible would be more efficient
192 return subset & r
192 return subset & r
193
193
194
194
195 def dagrange(repo, subset, x, y, order):
195 def dagrange(repo, subset, x, y, order):
196 r = fullreposet(repo)
196 r = fullreposet(repo)
197 xs = dagop.reachableroots(
197 xs = dagop.reachableroots(
198 repo, getset(repo, r, x), getset(repo, r, y), includepath=True
198 repo, getset(repo, r, x), getset(repo, r, y), includepath=True
199 )
199 )
200 return subset & xs
200 return subset & xs
201
201
202
202
203 def andset(repo, subset, x, y, order):
203 def andset(repo, subset, x, y, order):
204 if order == anyorder:
204 if order == anyorder:
205 yorder = anyorder
205 yorder = anyorder
206 else:
206 else:
207 yorder = followorder
207 yorder = followorder
208 return getset(repo, getset(repo, subset, x, order), y, yorder)
208 return getset(repo, getset(repo, subset, x, order), y, yorder)
209
209
210
210
211 def andsmallyset(repo, subset, x, y, order):
211 def andsmallyset(repo, subset, x, y, order):
212 # 'andsmally(x, y)' is equivalent to 'and(x, y)', but faster when y is small
212 # 'andsmally(x, y)' is equivalent to 'and(x, y)', but faster when y is small
213 if order == anyorder:
213 if order == anyorder:
214 yorder = anyorder
214 yorder = anyorder
215 else:
215 else:
216 yorder = followorder
216 yorder = followorder
217 return getset(repo, getset(repo, subset, y, yorder), x, order)
217 return getset(repo, getset(repo, subset, y, yorder), x, order)
218
218
219
219
220 def differenceset(repo, subset, x, y, order):
220 def differenceset(repo, subset, x, y, order):
221 return getset(repo, subset, x, order) - getset(repo, subset, y, anyorder)
221 return getset(repo, subset, x, order) - getset(repo, subset, y, anyorder)
222
222
223
223
224 def _orsetlist(repo, subset, xs, order):
224 def _orsetlist(repo, subset, xs, order):
225 assert xs
225 assert xs
226 if len(xs) == 1:
226 if len(xs) == 1:
227 return getset(repo, subset, xs[0], order)
227 return getset(repo, subset, xs[0], order)
228 p = len(xs) // 2
228 p = len(xs) // 2
229 a = _orsetlist(repo, subset, xs[:p], order)
229 a = _orsetlist(repo, subset, xs[:p], order)
230 b = _orsetlist(repo, subset, xs[p:], order)
230 b = _orsetlist(repo, subset, xs[p:], order)
231 return a + b
231 return a + b
232
232
233
233
234 def orset(repo, subset, x, order):
234 def orset(repo, subset, x, order):
235 xs = getlist(x)
235 xs = getlist(x)
236 if not xs:
236 if not xs:
237 return baseset()
237 return baseset()
238 if order == followorder:
238 if order == followorder:
239 # slow path to take the subset order
239 # slow path to take the subset order
240 return subset & _orsetlist(repo, fullreposet(repo), xs, anyorder)
240 return subset & _orsetlist(repo, fullreposet(repo), xs, anyorder)
241 else:
241 else:
242 return _orsetlist(repo, subset, xs, order)
242 return _orsetlist(repo, subset, xs, order)
243
243
244
244
245 def notset(repo, subset, x, order):
245 def notset(repo, subset, x, order):
246 return subset - getset(repo, subset, x, anyorder)
246 return subset - getset(repo, subset, x, anyorder)
247
247
248
248
249 def relationset(repo, subset, x, y, order):
249 def relationset(repo, subset, x, y, order):
250 raise error.ParseError(_(b"can't use a relation in this context"))
250 raise error.ParseError(_(b"can't use a relation in this context"))
251
251
252
252
253 def _splitrange(a, b):
253 def _splitrange(a, b):
254 """Split range with bounds a and b into two ranges at 0 and return two
254 """Split range with bounds a and b into two ranges at 0 and return two
255 tuples of numbers for use as startdepth and stopdepth arguments of
255 tuples of numbers for use as startdepth and stopdepth arguments of
256 revancestors and revdescendants.
256 revancestors and revdescendants.
257
257
258 >>> _splitrange(-10, -5) # [-10:-5]
258 >>> _splitrange(-10, -5) # [-10:-5]
259 ((5, 11), (None, None))
259 ((5, 11), (None, None))
260 >>> _splitrange(5, 10) # [5:10]
260 >>> _splitrange(5, 10) # [5:10]
261 ((None, None), (5, 11))
261 ((None, None), (5, 11))
262 >>> _splitrange(-10, 10) # [-10:10]
262 >>> _splitrange(-10, 10) # [-10:10]
263 ((0, 11), (0, 11))
263 ((0, 11), (0, 11))
264 >>> _splitrange(-10, 0) # [-10:0]
264 >>> _splitrange(-10, 0) # [-10:0]
265 ((0, 11), (None, None))
265 ((0, 11), (None, None))
266 >>> _splitrange(0, 10) # [0:10]
266 >>> _splitrange(0, 10) # [0:10]
267 ((None, None), (0, 11))
267 ((None, None), (0, 11))
268 >>> _splitrange(0, 0) # [0:0]
268 >>> _splitrange(0, 0) # [0:0]
269 ((0, 1), (None, None))
269 ((0, 1), (None, None))
270 >>> _splitrange(1, -1) # [1:-1]
270 >>> _splitrange(1, -1) # [1:-1]
271 ((None, None), (None, None))
271 ((None, None), (None, None))
272 """
272 """
273 ancdepths = (None, None)
273 ancdepths = (None, None)
274 descdepths = (None, None)
274 descdepths = (None, None)
275 if a == b == 0:
275 if a == b == 0:
276 ancdepths = (0, 1)
276 ancdepths = (0, 1)
277 if a < 0:
277 if a < 0:
278 ancdepths = (-min(b, 0), -a + 1)
278 ancdepths = (-min(b, 0), -a + 1)
279 if b > 0:
279 if b > 0:
280 descdepths = (max(a, 0), b + 1)
280 descdepths = (max(a, 0), b + 1)
281 return ancdepths, descdepths
281 return ancdepths, descdepths
282
282
283
283
284 def generationsrel(repo, subset, x, rel, z, order):
284 def generationsrel(repo, subset, x, rel, z, order):
285 # TODO: rewrite tests, and drop startdepth argument from ancestors() and
285 # TODO: rewrite tests, and drop startdepth argument from ancestors() and
286 # descendants() predicates
286 # descendants() predicates
287 a, b = getintrange(
287 a, b = getintrange(
288 z,
288 z,
289 _(b'relation subscript must be an integer or a range'),
289 _(b'relation subscript must be an integer or a range'),
290 _(b'relation subscript bounds must be integers'),
290 _(b'relation subscript bounds must be integers'),
291 deffirst=-(dagop.maxlogdepth - 1),
291 deffirst=-(dagop.maxlogdepth - 1),
292 deflast=+(dagop.maxlogdepth - 1),
292 deflast=+(dagop.maxlogdepth - 1),
293 )
293 )
294 (ancstart, ancstop), (descstart, descstop) = _splitrange(a, b)
294 (ancstart, ancstop), (descstart, descstop) = _splitrange(a, b)
295
295
296 if ancstart is None and descstart is None:
296 if ancstart is None and descstart is None:
297 return baseset()
297 return baseset()
298
298
299 revs = getset(repo, fullreposet(repo), x)
299 revs = getset(repo, fullreposet(repo), x)
300 if not revs:
300 if not revs:
301 return baseset()
301 return baseset()
302
302
303 if ancstart is not None and descstart is not None:
303 if ancstart is not None and descstart is not None:
304 s = dagop.revancestors(repo, revs, False, ancstart, ancstop)
304 s = dagop.revancestors(repo, revs, False, ancstart, ancstop)
305 s += dagop.revdescendants(repo, revs, False, descstart, descstop)
305 s += dagop.revdescendants(repo, revs, False, descstart, descstop)
306 elif ancstart is not None:
306 elif ancstart is not None:
307 s = dagop.revancestors(repo, revs, False, ancstart, ancstop)
307 s = dagop.revancestors(repo, revs, False, ancstart, ancstop)
308 elif descstart is not None:
308 elif descstart is not None:
309 s = dagop.revdescendants(repo, revs, False, descstart, descstop)
309 s = dagop.revdescendants(repo, revs, False, descstart, descstop)
310
310
311 return subset & s
311 return subset & s
312
312
313
313
314 def relsubscriptset(repo, subset, x, y, z, order):
314 def relsubscriptset(repo, subset, x, y, z, order):
315 # this is pretty basic implementation of 'x#y[z]' operator, still
315 # this is pretty basic implementation of 'x#y[z]' operator, still
316 # experimental so undocumented. see the wiki for further ideas.
316 # experimental so undocumented. see the wiki for further ideas.
317 # https://www.mercurial-scm.org/wiki/RevsetOperatorPlan
317 # https://www.mercurial-scm.org/wiki/RevsetOperatorPlan
318 rel = getsymbol(y)
318 rel = getsymbol(y)
319 if rel in subscriptrelations:
319 if rel in subscriptrelations:
320 return subscriptrelations[rel](repo, subset, x, rel, z, order)
320 return subscriptrelations[rel](repo, subset, x, rel, z, order)
321
321
322 relnames = [r for r in subscriptrelations.keys() if len(r) > 1]
322 relnames = [r for r in subscriptrelations.keys() if len(r) > 1]
323 raise error.UnknownIdentifier(rel, relnames)
323 raise error.UnknownIdentifier(rel, relnames)
324
324
325
325
326 def subscriptset(repo, subset, x, y, order):
326 def subscriptset(repo, subset, x, y, order):
327 raise error.ParseError(_(b"can't use a subscript in this context"))
327 raise error.ParseError(_(b"can't use a subscript in this context"))
328
328
329
329
330 def listset(repo, subset, *xs, **opts):
330 def listset(repo, subset, *xs, **opts):
331 raise error.ParseError(
331 raise error.ParseError(
332 _(b"can't use a list in this context"),
332 _(b"can't use a list in this context"),
333 hint=_(b'see \'hg help "revsets.x or y"\''),
333 hint=_(b'see \'hg help "revsets.x or y"\''),
334 )
334 )
335
335
336
336
337 def keyvaluepair(repo, subset, k, v, order):
337 def keyvaluepair(repo, subset, k, v, order):
338 raise error.ParseError(_(b"can't use a key-value pair in this context"))
338 raise error.ParseError(_(b"can't use a key-value pair in this context"))
339
339
340
340
341 def func(repo, subset, a, b, order):
341 def func(repo, subset, a, b, order):
342 f = getsymbol(a)
342 f = getsymbol(a)
343 if f in symbols:
343 if f in symbols:
344 func = symbols[f]
344 func = symbols[f]
345 if getattr(func, '_takeorder', False):
345 if getattr(func, '_takeorder', False):
346 return func(repo, subset, b, order)
346 return func(repo, subset, b, order)
347 return func(repo, subset, b)
347 return func(repo, subset, b)
348
348
349 keep = lambda fn: getattr(fn, '__doc__', None) is not None
349 keep = lambda fn: getattr(fn, '__doc__', None) is not None
350
350
351 syms = [s for (s, fn) in symbols.items() if keep(fn)]
351 syms = [s for (s, fn) in symbols.items() if keep(fn)]
352 raise error.UnknownIdentifier(f, syms)
352 raise error.UnknownIdentifier(f, syms)
353
353
354
354
355 # functions
355 # functions
356
356
357 # symbols are callables like:
357 # symbols are callables like:
358 # fn(repo, subset, x)
358 # fn(repo, subset, x)
359 # with:
359 # with:
360 # repo - current repository instance
360 # repo - current repository instance
361 # subset - of revisions to be examined
361 # subset - of revisions to be examined
362 # x - argument in tree form
362 # x - argument in tree form
363 symbols = revsetlang.symbols
363 symbols = revsetlang.symbols
364
364
365 # symbols which can't be used for a DoS attack for any given input
365 # symbols which can't be used for a DoS attack for any given input
366 # (e.g. those which accept regexes as plain strings shouldn't be included)
366 # (e.g. those which accept regexes as plain strings shouldn't be included)
367 # functions that just return a lot of changesets (like all) don't count here
367 # functions that just return a lot of changesets (like all) don't count here
368 safesymbols = set()
368 safesymbols = set()
369
369
370 predicate = registrar.revsetpredicate()
370 predicate = registrar.revsetpredicate()
371
371
372
372
373 @predicate(b'_destupdate')
373 @predicate(b'_destupdate')
374 def _destupdate(repo, subset, x):
374 def _destupdate(repo, subset, x):
375 # experimental revset for update destination
375 # experimental revset for update destination
376 args = getargsdict(x, b'limit', b'clean')
376 args = getargsdict(x, b'limit', b'clean')
377 return subset & baseset(
377 return subset & baseset(
378 [destutil.destupdate(repo, **pycompat.strkwargs(args))[0]]
378 [destutil.destupdate(repo, **pycompat.strkwargs(args))[0]]
379 )
379 )
380
380
381
381
382 @predicate(b'_destmerge')
382 @predicate(b'_destmerge')
383 def _destmerge(repo, subset, x):
383 def _destmerge(repo, subset, x):
384 # experimental revset for merge destination
384 # experimental revset for merge destination
385 sourceset = None
385 sourceset = None
386 if x is not None:
386 if x is not None:
387 sourceset = getset(repo, fullreposet(repo), x)
387 sourceset = getset(repo, fullreposet(repo), x)
388 return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
388 return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
389
389
390
390
391 @predicate(b'adds(pattern)', safe=True, weight=30)
391 @predicate(b'adds(pattern)', safe=True, weight=30)
392 def adds(repo, subset, x):
392 def adds(repo, subset, x):
393 """Changesets that add a file matching pattern.
393 """Changesets that add a file matching pattern.
394
394
395 The pattern without explicit kind like ``glob:`` is expected to be
395 The pattern without explicit kind like ``glob:`` is expected to be
396 relative to the current directory and match against a file or a
396 relative to the current directory and match against a file or a
397 directory.
397 directory.
398 """
398 """
399 # i18n: "adds" is a keyword
399 # i18n: "adds" is a keyword
400 pat = getstring(x, _(b"adds requires a pattern"))
400 pat = getstring(x, _(b"adds requires a pattern"))
401 return checkstatus(repo, subset, pat, 1)
401 return checkstatus(repo, subset, pat, 1)
402
402
403
403
404 @predicate(b'ancestor(*changeset)', safe=True, weight=0.5)
404 @predicate(b'ancestor(*changeset)', safe=True, weight=0.5)
405 def ancestor(repo, subset, x):
405 def ancestor(repo, subset, x):
406 """A greatest common ancestor of the changesets.
406 """A greatest common ancestor of the changesets.
407
407
408 Accepts 0 or more changesets.
408 Accepts 0 or more changesets.
409 Will return empty list when passed no args.
409 Will return empty list when passed no args.
410 Greatest common ancestor of a single changeset is that changeset.
410 Greatest common ancestor of a single changeset is that changeset.
411 """
411 """
412 reviter = iter(orset(repo, fullreposet(repo), x, order=anyorder))
412 reviter = iter(orset(repo, fullreposet(repo), x, order=anyorder))
413 try:
413 try:
414 anc = repo[next(reviter)]
414 anc = repo[next(reviter)]
415 except StopIteration:
415 except StopIteration:
416 return baseset()
416 return baseset()
417 for r in reviter:
417 for r in reviter:
418 anc = anc.ancestor(repo[r])
418 anc = anc.ancestor(repo[r])
419
419
420 r = scmutil.intrev(anc)
420 r = scmutil.intrev(anc)
421 if r in subset:
421 if r in subset:
422 return baseset([r])
422 return baseset([r])
423 return baseset()
423 return baseset()
424
424
425
425
426 def _ancestors(
426 def _ancestors(
427 repo, subset, x, followfirst=False, startdepth=None, stopdepth=None
427 repo, subset, x, followfirst=False, startdepth=None, stopdepth=None
428 ):
428 ):
429 heads = getset(repo, fullreposet(repo), x)
429 heads = getset(repo, fullreposet(repo), x)
430 if not heads:
430 if not heads:
431 return baseset()
431 return baseset()
432 s = dagop.revancestors(repo, heads, followfirst, startdepth, stopdepth)
432 s = dagop.revancestors(repo, heads, followfirst, startdepth, stopdepth)
433 return subset & s
433 return subset & s
434
434
435
435
436 @predicate(b'ancestors(set[, depth])', safe=True)
436 @predicate(b'ancestors(set[, depth])', safe=True)
437 def ancestors(repo, subset, x):
437 def ancestors(repo, subset, x):
438 """Changesets that are ancestors of changesets in set, including the
438 """Changesets that are ancestors of changesets in set, including the
439 given changesets themselves.
439 given changesets themselves.
440
440
441 If depth is specified, the result only includes changesets up to
441 If depth is specified, the result only includes changesets up to
442 the specified generation.
442 the specified generation.
443 """
443 """
444 # startdepth is for internal use only until we can decide the UI
444 # startdepth is for internal use only until we can decide the UI
445 args = getargsdict(x, b'ancestors', b'set depth startdepth')
445 args = getargsdict(x, b'ancestors', b'set depth startdepth')
446 if b'set' not in args:
446 if b'set' not in args:
447 # i18n: "ancestors" is a keyword
447 # i18n: "ancestors" is a keyword
448 raise error.ParseError(_(b'ancestors takes at least 1 argument'))
448 raise error.ParseError(_(b'ancestors takes at least 1 argument'))
449 startdepth = stopdepth = None
449 startdepth = stopdepth = None
450 if b'startdepth' in args:
450 if b'startdepth' in args:
451 n = getinteger(
451 n = getinteger(
452 args[b'startdepth'], b"ancestors expects an integer startdepth"
452 args[b'startdepth'], b"ancestors expects an integer startdepth"
453 )
453 )
454 if n < 0:
454 if n < 0:
455 raise error.ParseError(b"negative startdepth")
455 raise error.ParseError(b"negative startdepth")
456 startdepth = n
456 startdepth = n
457 if b'depth' in args:
457 if b'depth' in args:
458 # i18n: "ancestors" is a keyword
458 # i18n: "ancestors" is a keyword
459 n = getinteger(args[b'depth'], _(b"ancestors expects an integer depth"))
459 n = getinteger(args[b'depth'], _(b"ancestors expects an integer depth"))
460 if n < 0:
460 if n < 0:
461 raise error.ParseError(_(b"negative depth"))
461 raise error.ParseError(_(b"negative depth"))
462 stopdepth = n + 1
462 stopdepth = n + 1
463 return _ancestors(
463 return _ancestors(
464 repo, subset, args[b'set'], startdepth=startdepth, stopdepth=stopdepth
464 repo, subset, args[b'set'], startdepth=startdepth, stopdepth=stopdepth
465 )
465 )
466
466
467
467
468 @predicate(b'_firstancestors', safe=True)
468 @predicate(b'_firstancestors', safe=True)
469 def _firstancestors(repo, subset, x):
469 def _firstancestors(repo, subset, x):
470 # ``_firstancestors(set)``
470 # ``_firstancestors(set)``
471 # Like ``ancestors(set)`` but follows only the first parents.
471 # Like ``ancestors(set)`` but follows only the first parents.
472 return _ancestors(repo, subset, x, followfirst=True)
472 return _ancestors(repo, subset, x, followfirst=True)
473
473
474
474
475 def _childrenspec(repo, subset, x, n, order):
475 def _childrenspec(repo, subset, x, n, order):
476 """Changesets that are the Nth child of a changeset
476 """Changesets that are the Nth child of a changeset
477 in set.
477 in set.
478 """
478 """
479 cs = set()
479 cs = set()
480 for r in getset(repo, fullreposet(repo), x):
480 for r in getset(repo, fullreposet(repo), x):
481 for i in range(n):
481 for i in range(n):
482 c = repo[r].children()
482 c = repo[r].children()
483 if len(c) == 0:
483 if len(c) == 0:
484 break
484 break
485 if len(c) > 1:
485 if len(c) > 1:
486 raise error.RepoLookupError(
486 raise error.RepoLookupError(
487 _(b"revision in set has more than one child")
487 _(b"revision in set has more than one child")
488 )
488 )
489 r = c[0].rev()
489 r = c[0].rev()
490 else:
490 else:
491 cs.add(r)
491 cs.add(r)
492 return subset & cs
492 return subset & cs
493
493
494
494
495 def ancestorspec(repo, subset, x, n, order):
495 def ancestorspec(repo, subset, x, n, order):
496 """``set~n``
496 """``set~n``
497 Changesets that are the Nth ancestor (first parents only) of a changeset
497 Changesets that are the Nth ancestor (first parents only) of a changeset
498 in set.
498 in set.
499 """
499 """
500 n = getinteger(n, _(b"~ expects a number"))
500 n = getinteger(n, _(b"~ expects a number"))
501 if n < 0:
501 if n < 0:
502 # children lookup
502 # children lookup
503 return _childrenspec(repo, subset, x, -n, order)
503 return _childrenspec(repo, subset, x, -n, order)
504 ps = set()
504 ps = set()
505 cl = repo.changelog
505 cl = repo.changelog
506 for r in getset(repo, fullreposet(repo), x):
506 for r in getset(repo, fullreposet(repo), x):
507 for i in range(n):
507 for i in range(n):
508 try:
508 try:
509 r = cl.parentrevs(r)[0]
509 r = cl.parentrevs(r)[0]
510 except error.WdirUnsupported:
510 except error.WdirUnsupported:
511 r = repo[r].p1().rev()
511 r = repo[r].p1().rev()
512 ps.add(r)
512 ps.add(r)
513 return subset & ps
513 return subset & ps
514
514
515
515
516 @predicate(b'author(string)', safe=True, weight=10)
516 @predicate(b'author(string)', safe=True, weight=10)
517 def author(repo, subset, x):
517 def author(repo, subset, x):
518 """Alias for ``user(string)``.
518 """Alias for ``user(string)``.
519 """
519 """
520 # i18n: "author" is a keyword
520 # i18n: "author" is a keyword
521 n = getstring(x, _(b"author requires a string"))
521 n = getstring(x, _(b"author requires a string"))
522 kind, pattern, matcher = _substringmatcher(n, casesensitive=False)
522 kind, pattern, matcher = _substringmatcher(n, casesensitive=False)
523 return subset.filter(
523 return subset.filter(
524 lambda x: matcher(repo[x].user()), condrepr=(b'<user %r>', n)
524 lambda x: matcher(repo[x].user()), condrepr=(b'<user %r>', n)
525 )
525 )
526
526
527
527
528 @predicate(b'bisect(string)', safe=True)
528 @predicate(b'bisect(string)', safe=True)
529 def bisect(repo, subset, x):
529 def bisect(repo, subset, x):
530 """Changesets marked in the specified bisect status:
530 """Changesets marked in the specified bisect status:
531
531
532 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
532 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
533 - ``goods``, ``bads`` : csets topologically good/bad
533 - ``goods``, ``bads`` : csets topologically good/bad
534 - ``range`` : csets taking part in the bisection
534 - ``range`` : csets taking part in the bisection
535 - ``pruned`` : csets that are goods, bads or skipped
535 - ``pruned`` : csets that are goods, bads or skipped
536 - ``untested`` : csets whose fate is yet unknown
536 - ``untested`` : csets whose fate is yet unknown
537 - ``ignored`` : csets ignored due to DAG topology
537 - ``ignored`` : csets ignored due to DAG topology
538 - ``current`` : the cset currently being bisected
538 - ``current`` : the cset currently being bisected
539 """
539 """
540 # i18n: "bisect" is a keyword
540 # i18n: "bisect" is a keyword
541 status = getstring(x, _(b"bisect requires a string")).lower()
541 status = getstring(x, _(b"bisect requires a string")).lower()
542 state = set(hbisect.get(repo, status))
542 state = set(hbisect.get(repo, status))
543 return subset & state
543 return subset & state
544
544
545
545
546 # Backward-compatibility
546 # Backward-compatibility
547 # - no help entry so that we do not advertise it any more
547 # - no help entry so that we do not advertise it any more
548 @predicate(b'bisected', safe=True)
548 @predicate(b'bisected', safe=True)
549 def bisected(repo, subset, x):
549 def bisected(repo, subset, x):
550 return bisect(repo, subset, x)
550 return bisect(repo, subset, x)
551
551
552
552
553 @predicate(b'bookmark([name])', safe=True)
553 @predicate(b'bookmark([name])', safe=True)
554 def bookmark(repo, subset, x):
554 def bookmark(repo, subset, x):
555 """The named bookmark or all bookmarks.
555 """The named bookmark or all bookmarks.
556
556
557 Pattern matching is supported for `name`. See :hg:`help revisions.patterns`.
557 Pattern matching is supported for `name`. See :hg:`help revisions.patterns`.
558 """
558 """
559 # i18n: "bookmark" is a keyword
559 # i18n: "bookmark" is a keyword
560 args = getargs(x, 0, 1, _(b'bookmark takes one or no arguments'))
560 args = getargs(x, 0, 1, _(b'bookmark takes one or no arguments'))
561 if args:
561 if args:
562 bm = getstring(
562 bm = getstring(
563 args[0],
563 args[0],
564 # i18n: "bookmark" is a keyword
564 # i18n: "bookmark" is a keyword
565 _(b'the argument to bookmark must be a string'),
565 _(b'the argument to bookmark must be a string'),
566 )
566 )
567 kind, pattern, matcher = stringutil.stringmatcher(bm)
567 kind, pattern, matcher = stringutil.stringmatcher(bm)
568 bms = set()
568 bms = set()
569 if kind == b'literal':
569 if kind == b'literal':
570 if bm == pattern:
570 if bm == pattern:
571 pattern = repo._bookmarks.expandname(pattern)
571 pattern = repo._bookmarks.expandname(pattern)
572 bmrev = repo._bookmarks.get(pattern, None)
572 bmrev = repo._bookmarks.get(pattern, None)
573 if not bmrev:
573 if not bmrev:
574 raise error.RepoLookupError(
574 raise error.RepoLookupError(
575 _(b"bookmark '%s' does not exist") % pattern
575 _(b"bookmark '%s' does not exist") % pattern
576 )
576 )
577 bms.add(repo[bmrev].rev())
577 bms.add(repo[bmrev].rev())
578 else:
578 else:
579 matchrevs = set()
579 matchrevs = set()
580 for name, bmrev in pycompat.iteritems(repo._bookmarks):
580 for name, bmrev in pycompat.iteritems(repo._bookmarks):
581 if matcher(name):
581 if matcher(name):
582 matchrevs.add(bmrev)
582 matchrevs.add(bmrev)
583 for bmrev in matchrevs:
583 for bmrev in matchrevs:
584 bms.add(repo[bmrev].rev())
584 bms.add(repo[bmrev].rev())
585 else:
585 else:
586 bms = {repo[r].rev() for r in repo._bookmarks.values()}
586 bms = {repo[r].rev() for r in repo._bookmarks.values()}
587 bms -= {node.nullrev}
587 bms -= {node.nullrev}
588 return subset & bms
588 return subset & bms
589
589
590
590
591 @predicate(b'branch(string or set)', safe=True, weight=10)
591 @predicate(b'branch(string or set)', safe=True, weight=10)
592 def branch(repo, subset, x):
592 def branch(repo, subset, x):
593 """
593 """
594 All changesets belonging to the given branch or the branches of the given
594 All changesets belonging to the given branch or the branches of the given
595 changesets.
595 changesets.
596
596
597 Pattern matching is supported for `string`. See
597 Pattern matching is supported for `string`. See
598 :hg:`help revisions.patterns`.
598 :hg:`help revisions.patterns`.
599 """
599 """
600 getbi = repo.revbranchcache().branchinfo
600 getbi = repo.revbranchcache().branchinfo
601
601
602 def getbranch(r):
602 def getbranch(r):
603 try:
603 try:
604 return getbi(r)[0]
604 return getbi(r)[0]
605 except error.WdirUnsupported:
605 except error.WdirUnsupported:
606 return repo[r].branch()
606 return repo[r].branch()
607
607
608 try:
608 try:
609 b = getstring(x, b'')
609 b = getstring(x, b'')
610 except error.ParseError:
610 except error.ParseError:
611 # not a string, but another revspec, e.g. tip()
611 # not a string, but another revspec, e.g. tip()
612 pass
612 pass
613 else:
613 else:
614 kind, pattern, matcher = stringutil.stringmatcher(b)
614 kind, pattern, matcher = stringutil.stringmatcher(b)
615 if kind == b'literal':
615 if kind == b'literal':
616 # note: falls through to the revspec case if no branch with
616 # note: falls through to the revspec case if no branch with
617 # this name exists and pattern kind is not specified explicitly
617 # this name exists and pattern kind is not specified explicitly
618 if repo.branchmap().hasbranch(pattern):
618 if repo.branchmap().hasbranch(pattern):
619 return subset.filter(
619 return subset.filter(
620 lambda r: matcher(getbranch(r)),
620 lambda r: matcher(getbranch(r)),
621 condrepr=(b'<branch %r>', b),
621 condrepr=(b'<branch %r>', b),
622 )
622 )
623 if b.startswith(b'literal:'):
623 if b.startswith(b'literal:'):
624 raise error.RepoLookupError(
624 raise error.RepoLookupError(
625 _(b"branch '%s' does not exist") % pattern
625 _(b"branch '%s' does not exist") % pattern
626 )
626 )
627 else:
627 else:
628 return subset.filter(
628 return subset.filter(
629 lambda r: matcher(getbranch(r)), condrepr=(b'<branch %r>', b)
629 lambda r: matcher(getbranch(r)), condrepr=(b'<branch %r>', b)
630 )
630 )
631
631
632 s = getset(repo, fullreposet(repo), x)
632 s = getset(repo, fullreposet(repo), x)
633 b = set()
633 b = set()
634 for r in s:
634 for r in s:
635 b.add(getbranch(r))
635 b.add(getbranch(r))
636 c = s.__contains__
636 c = s.__contains__
637 return subset.filter(
637 return subset.filter(
638 lambda r: c(r) or getbranch(r) in b,
638 lambda r: c(r) or getbranch(r) in b,
639 condrepr=lambda: b'<branch %r>' % _sortedb(b),
639 condrepr=lambda: b'<branch %r>' % _sortedb(b),
640 )
640 )
641
641
642
642
643 @predicate(b'phasedivergent()', safe=True)
643 @predicate(b'phasedivergent()', safe=True)
644 def phasedivergent(repo, subset, x):
644 def phasedivergent(repo, subset, x):
645 """Mutable changesets marked as successors of public changesets.
645 """Mutable changesets marked as successors of public changesets.
646
646
647 Only non-public and non-obsolete changesets can be `phasedivergent`.
647 Only non-public and non-obsolete changesets can be `phasedivergent`.
648 (EXPERIMENTAL)
648 (EXPERIMENTAL)
649 """
649 """
650 # i18n: "phasedivergent" is a keyword
650 # i18n: "phasedivergent" is a keyword
651 getargs(x, 0, 0, _(b"phasedivergent takes no arguments"))
651 getargs(x, 0, 0, _(b"phasedivergent takes no arguments"))
652 phasedivergent = obsmod.getrevs(repo, b'phasedivergent')
652 phasedivergent = obsmod.getrevs(repo, b'phasedivergent')
653 return subset & phasedivergent
653 return subset & phasedivergent
654
654
655
655
656 @predicate(b'bundle()', safe=True)
656 @predicate(b'bundle()', safe=True)
657 def bundle(repo, subset, x):
657 def bundle(repo, subset, x):
658 """Changesets in the bundle.
658 """Changesets in the bundle.
659
659
660 Bundle must be specified by the -R option."""
660 Bundle must be specified by the -R option."""
661
661
662 try:
662 try:
663 bundlerevs = repo.changelog.bundlerevs
663 bundlerevs = repo.changelog.bundlerevs
664 except AttributeError:
664 except AttributeError:
665 raise error.Abort(_(b"no bundle provided - specify with -R"))
665 raise error.Abort(_(b"no bundle provided - specify with -R"))
666 return subset & bundlerevs
666 return subset & bundlerevs
667
667
668
668
669 def checkstatus(repo, subset, pat, field):
669 def checkstatus(repo, subset, pat, field):
670 """Helper for status-related revsets (adds, removes, modifies).
670 """Helper for status-related revsets (adds, removes, modifies).
671 The field parameter says which kind is desired:
671 The field parameter says which kind is desired:
672 0: modified
672 0: modified
673 1: added
673 1: added
674 2: removed
674 2: removed
675 """
675 """
676 label = {0: 'modified', 1: 'added', 2: 'removed'}[field]
676 label = {0: 'modified', 1: 'added', 2: 'removed'}[field]
677 hasset = matchmod.patkind(pat) == b'set'
677 hasset = matchmod.patkind(pat) == b'set'
678
678
679 mcache = [None]
679 mcache = [None]
680
680
681 def matches(x):
681 def matches(x):
682 c = repo[x]
682 c = repo[x]
683 if not mcache[0] or hasset:
683 if not mcache[0] or hasset:
684 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
684 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
685 m = mcache[0]
685 m = mcache[0]
686 fname = None
686 fname = None
687
687
688 assert m is not None # help pytype
688 assert m is not None # help pytype
689 if not m.anypats() and len(m.files()) == 1:
689 if not m.anypats() and len(m.files()) == 1:
690 fname = m.files()[0]
690 fname = m.files()[0]
691 if fname is not None:
691 if fname is not None:
692 if fname not in c.files():
692 if fname not in c.files():
693 return False
693 return False
694 else:
694 else:
695 if not any(m(f) for f in c.files()):
695 if not any(m(f) for f in c.files()):
696 return False
696 return False
697 files = getattr(repo.status(c.p1().node(), c.node()), label)
697 files = getattr(repo.status(c.p1().node(), c.node()), label)
698 if fname is not None:
698 if fname is not None:
699 if fname in files:
699 if fname in files:
700 return True
700 return True
701 else:
701 else:
702 if any(m(f) for f in files):
702 if any(m(f) for f in files):
703 return True
703 return True
704
704
705 return subset.filter(matches, condrepr=(b'<status[%r] %r>', field, pat))
705 return subset.filter(matches, condrepr=(b'<status[%r] %r>', field, pat))
706
706
707
707
708 def _children(repo, subset, parentset):
708 def _children(repo, subset, parentset):
709 if not parentset:
709 if not parentset:
710 return baseset()
710 return baseset()
711 cs = set()
711 cs = set()
712 pr = repo.changelog.parentrevs
712 pr = repo.changelog.parentrevs
713 minrev = parentset.min()
713 minrev = parentset.min()
714 nullrev = node.nullrev
714 nullrev = node.nullrev
715 for r in subset:
715 for r in subset:
716 if r <= minrev:
716 if r <= minrev:
717 continue
717 continue
718 p1, p2 = pr(r)
718 p1, p2 = pr(r)
719 if p1 in parentset:
719 if p1 in parentset:
720 cs.add(r)
720 cs.add(r)
721 if p2 != nullrev and p2 in parentset:
721 if p2 != nullrev and p2 in parentset:
722 cs.add(r)
722 cs.add(r)
723 return baseset(cs)
723 return baseset(cs)
724
724
725
725
726 @predicate(b'children(set)', safe=True)
726 @predicate(b'children(set)', safe=True)
727 def children(repo, subset, x):
727 def children(repo, subset, x):
728 """Child changesets of changesets in set.
728 """Child changesets of changesets in set.
729 """
729 """
730 s = getset(repo, fullreposet(repo), x)
730 s = getset(repo, fullreposet(repo), x)
731 cs = _children(repo, subset, s)
731 cs = _children(repo, subset, s)
732 return subset & cs
732 return subset & cs
733
733
734
734
735 @predicate(b'closed()', safe=True, weight=10)
735 @predicate(b'closed()', safe=True, weight=10)
736 def closed(repo, subset, x):
736 def closed(repo, subset, x):
737 """Changeset is closed.
737 """Changeset is closed.
738 """
738 """
739 # i18n: "closed" is a keyword
739 # i18n: "closed" is a keyword
740 getargs(x, 0, 0, _(b"closed takes no arguments"))
740 getargs(x, 0, 0, _(b"closed takes no arguments"))
741 return subset.filter(
741 return subset.filter(
742 lambda r: repo[r].closesbranch(), condrepr=b'<branch closed>'
742 lambda r: repo[r].closesbranch(), condrepr=b'<branch closed>'
743 )
743 )
744
744
745
745
746 # for internal use
746 # for internal use
747 @predicate(b'_commonancestorheads(set)', safe=True)
747 @predicate(b'_commonancestorheads(set)', safe=True)
748 def _commonancestorheads(repo, subset, x):
748 def _commonancestorheads(repo, subset, x):
749 # This is an internal method is for quickly calculating "heads(::x and
749 # This is an internal method is for quickly calculating "heads(::x and
750 # ::y)"
750 # ::y)"
751
751
752 # These greatest common ancestors are the same ones that the consensus bid
752 # These greatest common ancestors are the same ones that the consensus bid
753 # merge will find.
753 # merge will find.
754 startrevs = getset(repo, fullreposet(repo), x, order=anyorder)
754 startrevs = getset(repo, fullreposet(repo), x, order=anyorder)
755
755
756 ancs = repo.changelog._commonancestorsheads(*list(startrevs))
756 ancs = repo.changelog._commonancestorsheads(*list(startrevs))
757 return subset & baseset(ancs)
757 return subset & baseset(ancs)
758
758
759
759
760 @predicate(b'commonancestors(set)', safe=True)
760 @predicate(b'commonancestors(set)', safe=True)
761 def commonancestors(repo, subset, x):
761 def commonancestors(repo, subset, x):
762 """Changesets that are ancestors of every changeset in set.
762 """Changesets that are ancestors of every changeset in set.
763 """
763 """
764 startrevs = getset(repo, fullreposet(repo), x, order=anyorder)
764 startrevs = getset(repo, fullreposet(repo), x, order=anyorder)
765 if not startrevs:
765 if not startrevs:
766 return baseset()
766 return baseset()
767 for r in startrevs:
767 for r in startrevs:
768 subset &= dagop.revancestors(repo, baseset([r]))
768 subset &= dagop.revancestors(repo, baseset([r]))
769 return subset
769 return subset
770
770
771
771
772 @predicate(b'conflictlocal()', safe=True)
772 @predicate(b'conflictlocal()', safe=True)
773 def conflictlocal(repo, subset, x):
773 def conflictlocal(repo, subset, x):
774 """The local side of the merge, if currently in an unresolved merge.
774 """The local side of the merge, if currently in an unresolved merge.
775
775
776 "merge" here includes merge conflicts from e.g. 'hg rebase' or 'hg graft'.
776 "merge" here includes merge conflicts from e.g. 'hg rebase' or 'hg graft'.
777 """
777 """
778 getargs(x, 0, 0, _(b"conflictlocal takes no arguments"))
778 getargs(x, 0, 0, _(b"conflictlocal takes no arguments"))
779 from . import merge
779 from . import merge
780
780
781 mergestate = merge.mergestate.read(repo)
781 mergestate = merge.mergestate.read(repo)
782 if mergestate.active() and repo.changelog.hasnode(mergestate.local):
782 if mergestate.active() and repo.changelog.hasnode(mergestate.local):
783 return subset & {repo.changelog.rev(mergestate.local)}
783 return subset & {repo.changelog.rev(mergestate.local)}
784
784
785 return baseset()
785 return baseset()
786
786
787
787
788 @predicate(b'conflictother()', safe=True)
788 @predicate(b'conflictother()', safe=True)
789 def conflictother(repo, subset, x):
789 def conflictother(repo, subset, x):
790 """The other side of the merge, if currently in an unresolved merge.
790 """The other side of the merge, if currently in an unresolved merge.
791
791
792 "merge" here includes merge conflicts from e.g. 'hg rebase' or 'hg graft'.
792 "merge" here includes merge conflicts from e.g. 'hg rebase' or 'hg graft'.
793 """
793 """
794 getargs(x, 0, 0, _(b"conflictother takes no arguments"))
794 getargs(x, 0, 0, _(b"conflictother takes no arguments"))
795 from . import merge
795 from . import merge
796
796
797 mergestate = merge.mergestate.read(repo)
797 mergestate = merge.mergestate.read(repo)
798 if mergestate.active() and repo.changelog.hasnode(mergestate.other):
798 if mergestate.active() and repo.changelog.hasnode(mergestate.other):
799 return subset & {repo.changelog.rev(mergestate.other)}
799 return subset & {repo.changelog.rev(mergestate.other)}
800
800
801 return baseset()
801 return baseset()
802
802
803
803
804 @predicate(b'contains(pattern)', weight=100)
804 @predicate(b'contains(pattern)', weight=100)
805 def contains(repo, subset, x):
805 def contains(repo, subset, x):
806 """The revision's manifest contains a file matching pattern (but might not
806 """The revision's manifest contains a file matching pattern (but might not
807 modify it). See :hg:`help patterns` for information about file patterns.
807 modify it). See :hg:`help patterns` for information about file patterns.
808
808
809 The pattern without explicit kind like ``glob:`` is expected to be
809 The pattern without explicit kind like ``glob:`` is expected to be
810 relative to the current directory and match against a file exactly
810 relative to the current directory and match against a file exactly
811 for efficiency.
811 for efficiency.
812 """
812 """
813 # i18n: "contains" is a keyword
813 # i18n: "contains" is a keyword
814 pat = getstring(x, _(b"contains requires a pattern"))
814 pat = getstring(x, _(b"contains requires a pattern"))
815
815
816 def matches(x):
816 def matches(x):
817 if not matchmod.patkind(pat):
817 if not matchmod.patkind(pat):
818 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
818 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
819 if pats in repo[x]:
819 if pats in repo[x]:
820 return True
820 return True
821 else:
821 else:
822 c = repo[x]
822 c = repo[x]
823 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
823 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
824 for f in c.manifest():
824 for f in c.manifest():
825 if m(f):
825 if m(f):
826 return True
826 return True
827 return False
827 return False
828
828
829 return subset.filter(matches, condrepr=(b'<contains %r>', pat))
829 return subset.filter(matches, condrepr=(b'<contains %r>', pat))
830
830
831
831
832 @predicate(b'converted([id])', safe=True)
832 @predicate(b'converted([id])', safe=True)
833 def converted(repo, subset, x):
833 def converted(repo, subset, x):
834 """Changesets converted from the given identifier in the old repository if
834 """Changesets converted from the given identifier in the old repository if
835 present, or all converted changesets if no identifier is specified.
835 present, or all converted changesets if no identifier is specified.
836 """
836 """
837
837
838 # There is exactly no chance of resolving the revision, so do a simple
838 # There is exactly no chance of resolving the revision, so do a simple
839 # string compare and hope for the best
839 # string compare and hope for the best
840
840
841 rev = None
841 rev = None
842 # i18n: "converted" is a keyword
842 # i18n: "converted" is a keyword
843 l = getargs(x, 0, 1, _(b'converted takes one or no arguments'))
843 l = getargs(x, 0, 1, _(b'converted takes one or no arguments'))
844 if l:
844 if l:
845 # i18n: "converted" is a keyword
845 # i18n: "converted" is a keyword
846 rev = getstring(l[0], _(b'converted requires a revision'))
846 rev = getstring(l[0], _(b'converted requires a revision'))
847
847
848 def _matchvalue(r):
848 def _matchvalue(r):
849 source = repo[r].extra().get(b'convert_revision', None)
849 source = repo[r].extra().get(b'convert_revision', None)
850 return source is not None and (rev is None or source.startswith(rev))
850 return source is not None and (rev is None or source.startswith(rev))
851
851
852 return subset.filter(
852 return subset.filter(
853 lambda r: _matchvalue(r), condrepr=(b'<converted %r>', rev)
853 lambda r: _matchvalue(r), condrepr=(b'<converted %r>', rev)
854 )
854 )
855
855
856
856
857 @predicate(b'date(interval)', safe=True, weight=10)
857 @predicate(b'date(interval)', safe=True, weight=10)
858 def date(repo, subset, x):
858 def date(repo, subset, x):
859 """Changesets within the interval, see :hg:`help dates`.
859 """Changesets within the interval, see :hg:`help dates`.
860 """
860 """
861 # i18n: "date" is a keyword
861 # i18n: "date" is a keyword
862 ds = getstring(x, _(b"date requires a string"))
862 ds = getstring(x, _(b"date requires a string"))
863 dm = dateutil.matchdate(ds)
863 dm = dateutil.matchdate(ds)
864 return subset.filter(
864 return subset.filter(
865 lambda x: dm(repo[x].date()[0]), condrepr=(b'<date %r>', ds)
865 lambda x: dm(repo[x].date()[0]), condrepr=(b'<date %r>', ds)
866 )
866 )
867
867
868
868
869 @predicate(b'desc(string)', safe=True, weight=10)
869 @predicate(b'desc(string)', safe=True, weight=10)
870 def desc(repo, subset, x):
870 def desc(repo, subset, x):
871 """Search commit message for string. The match is case-insensitive.
871 """Search commit message for string. The match is case-insensitive.
872
872
873 Pattern matching is supported for `string`. See
873 Pattern matching is supported for `string`. See
874 :hg:`help revisions.patterns`.
874 :hg:`help revisions.patterns`.
875 """
875 """
876 # i18n: "desc" is a keyword
876 # i18n: "desc" is a keyword
877 ds = getstring(x, _(b"desc requires a string"))
877 ds = getstring(x, _(b"desc requires a string"))
878
878
879 kind, pattern, matcher = _substringmatcher(ds, casesensitive=False)
879 kind, pattern, matcher = _substringmatcher(ds, casesensitive=False)
880
880
881 return subset.filter(
881 return subset.filter(
882 lambda r: matcher(repo[r].description()), condrepr=(b'<desc %r>', ds)
882 lambda r: matcher(repo[r].description()), condrepr=(b'<desc %r>', ds)
883 )
883 )
884
884
885
885
886 def _descendants(
886 def _descendants(
887 repo, subset, x, followfirst=False, startdepth=None, stopdepth=None
887 repo, subset, x, followfirst=False, startdepth=None, stopdepth=None
888 ):
888 ):
889 roots = getset(repo, fullreposet(repo), x)
889 roots = getset(repo, fullreposet(repo), x)
890 if not roots:
890 if not roots:
891 return baseset()
891 return baseset()
892 s = dagop.revdescendants(repo, roots, followfirst, startdepth, stopdepth)
892 s = dagop.revdescendants(repo, roots, followfirst, startdepth, stopdepth)
893 return subset & s
893 return subset & s
894
894
895
895
896 @predicate(b'descendants(set[, depth])', safe=True)
896 @predicate(b'descendants(set[, depth])', safe=True)
897 def descendants(repo, subset, x):
897 def descendants(repo, subset, x):
898 """Changesets which are descendants of changesets in set, including the
898 """Changesets which are descendants of changesets in set, including the
899 given changesets themselves.
899 given changesets themselves.
900
900
901 If depth is specified, the result only includes changesets up to
901 If depth is specified, the result only includes changesets up to
902 the specified generation.
902 the specified generation.
903 """
903 """
904 # startdepth is for internal use only until we can decide the UI
904 # startdepth is for internal use only until we can decide the UI
905 args = getargsdict(x, b'descendants', b'set depth startdepth')
905 args = getargsdict(x, b'descendants', b'set depth startdepth')
906 if b'set' not in args:
906 if b'set' not in args:
907 # i18n: "descendants" is a keyword
907 # i18n: "descendants" is a keyword
908 raise error.ParseError(_(b'descendants takes at least 1 argument'))
908 raise error.ParseError(_(b'descendants takes at least 1 argument'))
909 startdepth = stopdepth = None
909 startdepth = stopdepth = None
910 if b'startdepth' in args:
910 if b'startdepth' in args:
911 n = getinteger(
911 n = getinteger(
912 args[b'startdepth'], b"descendants expects an integer startdepth"
912 args[b'startdepth'], b"descendants expects an integer startdepth"
913 )
913 )
914 if n < 0:
914 if n < 0:
915 raise error.ParseError(b"negative startdepth")
915 raise error.ParseError(b"negative startdepth")
916 startdepth = n
916 startdepth = n
917 if b'depth' in args:
917 if b'depth' in args:
918 # i18n: "descendants" is a keyword
918 # i18n: "descendants" is a keyword
919 n = getinteger(
919 n = getinteger(
920 args[b'depth'], _(b"descendants expects an integer depth")
920 args[b'depth'], _(b"descendants expects an integer depth")
921 )
921 )
922 if n < 0:
922 if n < 0:
923 raise error.ParseError(_(b"negative depth"))
923 raise error.ParseError(_(b"negative depth"))
924 stopdepth = n + 1
924 stopdepth = n + 1
925 return _descendants(
925 return _descendants(
926 repo, subset, args[b'set'], startdepth=startdepth, stopdepth=stopdepth
926 repo, subset, args[b'set'], startdepth=startdepth, stopdepth=stopdepth
927 )
927 )
928
928
929
929
930 @predicate(b'_firstdescendants', safe=True)
930 @predicate(b'_firstdescendants', safe=True)
931 def _firstdescendants(repo, subset, x):
931 def _firstdescendants(repo, subset, x):
932 # ``_firstdescendants(set)``
932 # ``_firstdescendants(set)``
933 # Like ``descendants(set)`` but follows only the first parents.
933 # Like ``descendants(set)`` but follows only the first parents.
934 return _descendants(repo, subset, x, followfirst=True)
934 return _descendants(repo, subset, x, followfirst=True)
935
935
936
936
937 @predicate(b'destination([set])', safe=True, weight=10)
937 @predicate(b'destination([set])', safe=True, weight=10)
938 def destination(repo, subset, x):
938 def destination(repo, subset, x):
939 """Changesets that were created by a graft, transplant or rebase operation,
939 """Changesets that were created by a graft, transplant or rebase operation,
940 with the given revisions specified as the source. Omitting the optional set
940 with the given revisions specified as the source. Omitting the optional set
941 is the same as passing all().
941 is the same as passing all().
942 """
942 """
943 if x is not None:
943 if x is not None:
944 sources = getset(repo, fullreposet(repo), x)
944 sources = getset(repo, fullreposet(repo), x)
945 else:
945 else:
946 sources = fullreposet(repo)
946 sources = fullreposet(repo)
947
947
948 dests = set()
948 dests = set()
949
949
950 # subset contains all of the possible destinations that can be returned, so
950 # subset contains all of the possible destinations that can be returned, so
951 # iterate over them and see if their source(s) were provided in the arg set.
951 # iterate over them and see if their source(s) were provided in the arg set.
952 # Even if the immediate src of r is not in the arg set, src's source (or
952 # Even if the immediate src of r is not in the arg set, src's source (or
953 # further back) may be. Scanning back further than the immediate src allows
953 # further back) may be. Scanning back further than the immediate src allows
954 # transitive transplants and rebases to yield the same results as transitive
954 # transitive transplants and rebases to yield the same results as transitive
955 # grafts.
955 # grafts.
956 for r in subset:
956 for r in subset:
957 src = _getrevsource(repo, r)
957 src = _getrevsource(repo, r)
958 lineage = None
958 lineage = None
959
959
960 while src is not None:
960 while src is not None:
961 if lineage is None:
961 if lineage is None:
962 lineage = list()
962 lineage = list()
963
963
964 lineage.append(r)
964 lineage.append(r)
965
965
966 # The visited lineage is a match if the current source is in the arg
966 # The visited lineage is a match if the current source is in the arg
967 # set. Since every candidate dest is visited by way of iterating
967 # set. Since every candidate dest is visited by way of iterating
968 # subset, any dests further back in the lineage will be tested by a
968 # subset, any dests further back in the lineage will be tested by a
969 # different iteration over subset. Likewise, if the src was already
969 # different iteration over subset. Likewise, if the src was already
970 # selected, the current lineage can be selected without going back
970 # selected, the current lineage can be selected without going back
971 # further.
971 # further.
972 if src in sources or src in dests:
972 if src in sources or src in dests:
973 dests.update(lineage)
973 dests.update(lineage)
974 break
974 break
975
975
976 r = src
976 r = src
977 src = _getrevsource(repo, r)
977 src = _getrevsource(repo, r)
978
978
979 return subset.filter(
979 return subset.filter(
980 dests.__contains__,
980 dests.__contains__,
981 condrepr=lambda: b'<destination %r>' % _sortedb(dests),
981 condrepr=lambda: b'<destination %r>' % _sortedb(dests),
982 )
982 )
983
983
984
984
985 @predicate(b'contentdivergent()', safe=True)
985 @predicate(b'contentdivergent()', safe=True)
986 def contentdivergent(repo, subset, x):
986 def contentdivergent(repo, subset, x):
987 """
987 """
988 Final successors of changesets with an alternative set of final
988 Final successors of changesets with an alternative set of final
989 successors. (EXPERIMENTAL)
989 successors. (EXPERIMENTAL)
990 """
990 """
991 # i18n: "contentdivergent" is a keyword
991 # i18n: "contentdivergent" is a keyword
992 getargs(x, 0, 0, _(b"contentdivergent takes no arguments"))
992 getargs(x, 0, 0, _(b"contentdivergent takes no arguments"))
993 contentdivergent = obsmod.getrevs(repo, b'contentdivergent')
993 contentdivergent = obsmod.getrevs(repo, b'contentdivergent')
994 return subset & contentdivergent
994 return subset & contentdivergent
995
995
996
996
997 @predicate(b'expectsize(set[, size])', safe=True, takeorder=True)
997 @predicate(b'expectsize(set[, size])', safe=True, takeorder=True)
998 def expectsize(repo, subset, x, order):
998 def expectsize(repo, subset, x, order):
999 """Return the given revset if size matches the revset size.
999 """Return the given revset if size matches the revset size.
1000 Abort if the revset doesn't expect given size.
1000 Abort if the revset doesn't expect given size.
1001 size can either be an integer range or an integer.
1001 size can either be an integer range or an integer.
1002
1002
1003 For example, ``expectsize(0:1, 3:5)`` will abort as revset size is 2 and
1003 For example, ``expectsize(0:1, 3:5)`` will abort as revset size is 2 and
1004 2 is not between 3 and 5 inclusive."""
1004 2 is not between 3 and 5 inclusive."""
1005
1005
1006 args = getargsdict(x, b'expectsize', b'set size')
1006 args = getargsdict(x, b'expectsize', b'set size')
1007 minsize = 0
1007 minsize = 0
1008 maxsize = len(repo) + 1
1008 maxsize = len(repo) + 1
1009 err = b''
1009 err = b''
1010 if b'size' not in args or b'set' not in args:
1010 if b'size' not in args or b'set' not in args:
1011 raise error.ParseError(_(b'invalid set of arguments'))
1011 raise error.ParseError(_(b'invalid set of arguments'))
1012 minsize, maxsize = getintrange(
1012 minsize, maxsize = getintrange(
1013 args[b'size'],
1013 args[b'size'],
1014 _(b'expectsize requires a size range or a positive integer'),
1014 _(b'expectsize requires a size range or a positive integer'),
1015 _(b'size range bounds must be integers'),
1015 _(b'size range bounds must be integers'),
1016 minsize,
1016 minsize,
1017 maxsize,
1017 maxsize,
1018 )
1018 )
1019 if minsize < 0 or maxsize < 0:
1019 if minsize < 0 or maxsize < 0:
1020 raise error.ParseError(_(b'negative size'))
1020 raise error.ParseError(_(b'negative size'))
1021 rev = getset(repo, fullreposet(repo), args[b'set'], order=order)
1021 rev = getset(repo, fullreposet(repo), args[b'set'], order=order)
1022 if minsize != maxsize and (len(rev) < minsize or len(rev) > maxsize):
1022 if minsize != maxsize and (len(rev) < minsize or len(rev) > maxsize):
1023 err = _(b'revset size mismatch. expected between %d and %d, got %d') % (
1023 err = _(b'revset size mismatch. expected between %d and %d, got %d') % (
1024 minsize,
1024 minsize,
1025 maxsize,
1025 maxsize,
1026 len(rev),
1026 len(rev),
1027 )
1027 )
1028 elif minsize == maxsize and len(rev) != minsize:
1028 elif minsize == maxsize and len(rev) != minsize:
1029 err = _(b'revset size mismatch. expected %d, got %d') % (
1029 err = _(b'revset size mismatch. expected %d, got %d') % (
1030 minsize,
1030 minsize,
1031 len(rev),
1031 len(rev),
1032 )
1032 )
1033 if err:
1033 if err:
1034 raise error.RepoLookupError(err)
1034 raise error.RepoLookupError(err)
1035 if order == followorder:
1035 if order == followorder:
1036 return subset & rev
1036 return subset & rev
1037 else:
1037 else:
1038 return rev & subset
1038 return rev & subset
1039
1039
1040
1040
1041 @predicate(b'extdata(source)', safe=False, weight=100)
1041 @predicate(b'extdata(source)', safe=False, weight=100)
1042 def extdata(repo, subset, x):
1042 def extdata(repo, subset, x):
1043 """Changesets in the specified extdata source. (EXPERIMENTAL)"""
1043 """Changesets in the specified extdata source. (EXPERIMENTAL)"""
1044 # i18n: "extdata" is a keyword
1044 # i18n: "extdata" is a keyword
1045 args = getargsdict(x, b'extdata', b'source')
1045 args = getargsdict(x, b'extdata', b'source')
1046 source = getstring(
1046 source = getstring(
1047 args.get(b'source'),
1047 args.get(b'source'),
1048 # i18n: "extdata" is a keyword
1048 # i18n: "extdata" is a keyword
1049 _(b'extdata takes at least 1 string argument'),
1049 _(b'extdata takes at least 1 string argument'),
1050 )
1050 )
1051 data = scmutil.extdatasource(repo, source)
1051 data = scmutil.extdatasource(repo, source)
1052 return subset & baseset(data)
1052 return subset & baseset(data)
1053
1053
1054
1054
1055 @predicate(b'extinct()', safe=True)
1055 @predicate(b'extinct()', safe=True)
1056 def extinct(repo, subset, x):
1056 def extinct(repo, subset, x):
1057 """Obsolete changesets with obsolete descendants only.
1057 """Obsolete changesets with obsolete descendants only.
1058 """
1058 """
1059 # i18n: "extinct" is a keyword
1059 # i18n: "extinct" is a keyword
1060 getargs(x, 0, 0, _(b"extinct takes no arguments"))
1060 getargs(x, 0, 0, _(b"extinct takes no arguments"))
1061 extincts = obsmod.getrevs(repo, b'extinct')
1061 extincts = obsmod.getrevs(repo, b'extinct')
1062 return subset & extincts
1062 return subset & extincts
1063
1063
1064
1064
1065 @predicate(b'extra(label, [value])', safe=True)
1065 @predicate(b'extra(label, [value])', safe=True)
1066 def extra(repo, subset, x):
1066 def extra(repo, subset, x):
1067 """Changesets with the given label in the extra metadata, with the given
1067 """Changesets with the given label in the extra metadata, with the given
1068 optional value.
1068 optional value.
1069
1069
1070 Pattern matching is supported for `value`. See
1070 Pattern matching is supported for `value`. See
1071 :hg:`help revisions.patterns`.
1071 :hg:`help revisions.patterns`.
1072 """
1072 """
1073 args = getargsdict(x, b'extra', b'label value')
1073 args = getargsdict(x, b'extra', b'label value')
1074 if b'label' not in args:
1074 if b'label' not in args:
1075 # i18n: "extra" is a keyword
1075 # i18n: "extra" is a keyword
1076 raise error.ParseError(_(b'extra takes at least 1 argument'))
1076 raise error.ParseError(_(b'extra takes at least 1 argument'))
1077 # i18n: "extra" is a keyword
1077 # i18n: "extra" is a keyword
1078 label = getstring(
1078 label = getstring(
1079 args[b'label'], _(b'first argument to extra must be a string')
1079 args[b'label'], _(b'first argument to extra must be a string')
1080 )
1080 )
1081 value = None
1081 value = None
1082
1082
1083 if b'value' in args:
1083 if b'value' in args:
1084 # i18n: "extra" is a keyword
1084 # i18n: "extra" is a keyword
1085 value = getstring(
1085 value = getstring(
1086 args[b'value'], _(b'second argument to extra must be a string')
1086 args[b'value'], _(b'second argument to extra must be a string')
1087 )
1087 )
1088 kind, value, matcher = stringutil.stringmatcher(value)
1088 kind, value, matcher = stringutil.stringmatcher(value)
1089
1089
1090 def _matchvalue(r):
1090 def _matchvalue(r):
1091 extra = repo[r].extra()
1091 extra = repo[r].extra()
1092 return label in extra and (value is None or matcher(extra[label]))
1092 return label in extra and (value is None or matcher(extra[label]))
1093
1093
1094 return subset.filter(
1094 return subset.filter(
1095 lambda r: _matchvalue(r), condrepr=(b'<extra[%r] %r>', label, value)
1095 lambda r: _matchvalue(r), condrepr=(b'<extra[%r] %r>', label, value)
1096 )
1096 )
1097
1097
1098
1098
1099 @predicate(b'filelog(pattern)', safe=True)
1099 @predicate(b'filelog(pattern)', safe=True)
1100 def filelog(repo, subset, x):
1100 def filelog(repo, subset, x):
1101 """Changesets connected to the specified filelog.
1101 """Changesets connected to the specified filelog.
1102
1102
1103 For performance reasons, visits only revisions mentioned in the file-level
1103 For performance reasons, visits only revisions mentioned in the file-level
1104 filelog, rather than filtering through all changesets (much faster, but
1104 filelog, rather than filtering through all changesets (much faster, but
1105 doesn't include deletes or duplicate changes). For a slower, more accurate
1105 doesn't include deletes or duplicate changes). For a slower, more accurate
1106 result, use ``file()``.
1106 result, use ``file()``.
1107
1107
1108 The pattern without explicit kind like ``glob:`` is expected to be
1108 The pattern without explicit kind like ``glob:`` is expected to be
1109 relative to the current directory and match against a file exactly
1109 relative to the current directory and match against a file exactly
1110 for efficiency.
1110 for efficiency.
1111 """
1111 """
1112
1112
1113 # i18n: "filelog" is a keyword
1113 # i18n: "filelog" is a keyword
1114 pat = getstring(x, _(b"filelog requires a pattern"))
1114 pat = getstring(x, _(b"filelog requires a pattern"))
1115 s = set()
1115 s = set()
1116 cl = repo.changelog
1116 cl = repo.changelog
1117
1117
1118 if not matchmod.patkind(pat):
1118 if not matchmod.patkind(pat):
1119 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
1119 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
1120 files = [f]
1120 files = [f]
1121 else:
1121 else:
1122 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
1122 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
1123 files = (f for f in repo[None] if m(f))
1123 files = (f for f in repo[None] if m(f))
1124
1124
1125 for f in files:
1125 for f in files:
1126 fl = repo.file(f)
1126 fl = repo.file(f)
1127 known = {}
1127 known = {}
1128 scanpos = 0
1128 scanpos = 0
1129 for fr in list(fl):
1129 for fr in list(fl):
1130 fn = fl.node(fr)
1130 fn = fl.node(fr)
1131 if fn in known:
1131 if fn in known:
1132 s.add(known[fn])
1132 s.add(known[fn])
1133 continue
1133 continue
1134
1134
1135 lr = fl.linkrev(fr)
1135 lr = fl.linkrev(fr)
1136 if lr in cl:
1136 if lr in cl:
1137 s.add(lr)
1137 s.add(lr)
1138 elif scanpos is not None:
1138 elif scanpos is not None:
1139 # lowest matching changeset is filtered, scan further
1139 # lowest matching changeset is filtered, scan further
1140 # ahead in changelog
1140 # ahead in changelog
1141 start = max(lr, scanpos) + 1
1141 start = max(lr, scanpos) + 1
1142 scanpos = None
1142 scanpos = None
1143 for r in cl.revs(start):
1143 for r in cl.revs(start):
1144 # minimize parsing of non-matching entries
1144 # minimize parsing of non-matching entries
1145 if f in cl.revision(r) and f in cl.readfiles(r):
1145 if f in cl.revision(r) and f in cl.readfiles(r):
1146 try:
1146 try:
1147 # try to use manifest delta fastpath
1147 # try to use manifest delta fastpath
1148 n = repo[r].filenode(f)
1148 n = repo[r].filenode(f)
1149 if n not in known:
1149 if n not in known:
1150 if n == fn:
1150 if n == fn:
1151 s.add(r)
1151 s.add(r)
1152 scanpos = r
1152 scanpos = r
1153 break
1153 break
1154 else:
1154 else:
1155 known[n] = r
1155 known[n] = r
1156 except error.ManifestLookupError:
1156 except error.ManifestLookupError:
1157 # deletion in changelog
1157 # deletion in changelog
1158 continue
1158 continue
1159
1159
1160 return subset & s
1160 return subset & s
1161
1161
1162
1162
1163 @predicate(b'first(set, [n])', safe=True, takeorder=True, weight=0)
1163 @predicate(b'first(set, [n])', safe=True, takeorder=True, weight=0)
1164 def first(repo, subset, x, order):
1164 def first(repo, subset, x, order):
1165 """An alias for limit().
1165 """An alias for limit().
1166 """
1166 """
1167 return limit(repo, subset, x, order)
1167 return limit(repo, subset, x, order)
1168
1168
1169
1169
1170 def _follow(repo, subset, x, name, followfirst=False):
1170 def _follow(repo, subset, x, name, followfirst=False):
1171 args = getargsdict(x, name, b'file startrev')
1171 args = getargsdict(x, name, b'file startrev')
1172 revs = None
1172 revs = None
1173 if b'startrev' in args:
1173 if b'startrev' in args:
1174 revs = getset(repo, fullreposet(repo), args[b'startrev'])
1174 revs = getset(repo, fullreposet(repo), args[b'startrev'])
1175 if b'file' in args:
1175 if b'file' in args:
1176 x = getstring(args[b'file'], _(b"%s expected a pattern") % name)
1176 x = getstring(args[b'file'], _(b"%s expected a pattern") % name)
1177 if revs is None:
1177 if revs is None:
1178 revs = [None]
1178 revs = [None]
1179 fctxs = []
1179 fctxs = []
1180 for r in revs:
1180 for r in revs:
1181 ctx = mctx = repo[r]
1181 ctx = mctx = repo[r]
1182 if r is None:
1182 if r is None:
1183 ctx = repo[b'.']
1183 ctx = repo[b'.']
1184 m = matchmod.match(
1184 m = matchmod.match(
1185 repo.root, repo.getcwd(), [x], ctx=mctx, default=b'path'
1185 repo.root, repo.getcwd(), [x], ctx=mctx, default=b'path'
1186 )
1186 )
1187 fctxs.extend(ctx[f].introfilectx() for f in ctx.manifest().walk(m))
1187 fctxs.extend(ctx[f].introfilectx() for f in ctx.manifest().walk(m))
1188 s = dagop.filerevancestors(fctxs, followfirst)
1188 s = dagop.filerevancestors(fctxs, followfirst)
1189 else:
1189 else:
1190 if revs is None:
1190 if revs is None:
1191 revs = baseset([repo[b'.'].rev()])
1191 revs = baseset([repo[b'.'].rev()])
1192 s = dagop.revancestors(repo, revs, followfirst)
1192 s = dagop.revancestors(repo, revs, followfirst)
1193
1193
1194 return subset & s
1194 return subset & s
1195
1195
1196
1196
1197 @predicate(b'follow([file[, startrev]])', safe=True)
1197 @predicate(b'follow([file[, startrev]])', safe=True)
1198 def follow(repo, subset, x):
1198 def follow(repo, subset, x):
1199 """
1199 """
1200 An alias for ``::.`` (ancestors of the working directory's first parent).
1200 An alias for ``::.`` (ancestors of the working directory's first parent).
1201 If file pattern is specified, the histories of files matching given
1201 If file pattern is specified, the histories of files matching given
1202 pattern in the revision given by startrev are followed, including copies.
1202 pattern in the revision given by startrev are followed, including copies.
1203 """
1203 """
1204 return _follow(repo, subset, x, b'follow')
1204 return _follow(repo, subset, x, b'follow')
1205
1205
1206
1206
1207 @predicate(b'_followfirst', safe=True)
1207 @predicate(b'_followfirst', safe=True)
1208 def _followfirst(repo, subset, x):
1208 def _followfirst(repo, subset, x):
1209 # ``followfirst([file[, startrev]])``
1209 # ``followfirst([file[, startrev]])``
1210 # Like ``follow([file[, startrev]])`` but follows only the first parent
1210 # Like ``follow([file[, startrev]])`` but follows only the first parent
1211 # of every revisions or files revisions.
1211 # of every revisions or files revisions.
1212 return _follow(repo, subset, x, b'_followfirst', followfirst=True)
1212 return _follow(repo, subset, x, b'_followfirst', followfirst=True)
1213
1213
1214
1214
1215 @predicate(
1215 @predicate(
1216 b'followlines(file, fromline:toline[, startrev=., descend=False])',
1216 b'followlines(file, fromline:toline[, startrev=., descend=False])',
1217 safe=True,
1217 safe=True,
1218 )
1218 )
1219 def followlines(repo, subset, x):
1219 def followlines(repo, subset, x):
1220 """Changesets modifying `file` in line range ('fromline', 'toline').
1220 """Changesets modifying `file` in line range ('fromline', 'toline').
1221
1221
1222 Line range corresponds to 'file' content at 'startrev' and should hence be
1222 Line range corresponds to 'file' content at 'startrev' and should hence be
1223 consistent with file size. If startrev is not specified, working directory's
1223 consistent with file size. If startrev is not specified, working directory's
1224 parent is used.
1224 parent is used.
1225
1225
1226 By default, ancestors of 'startrev' are returned. If 'descend' is True,
1226 By default, ancestors of 'startrev' are returned. If 'descend' is True,
1227 descendants of 'startrev' are returned though renames are (currently) not
1227 descendants of 'startrev' are returned though renames are (currently) not
1228 followed in this direction.
1228 followed in this direction.
1229 """
1229 """
1230 args = getargsdict(x, b'followlines', b'file *lines startrev descend')
1230 args = getargsdict(x, b'followlines', b'file *lines startrev descend')
1231 if len(args[b'lines']) != 1:
1231 if len(args[b'lines']) != 1:
1232 raise error.ParseError(_(b"followlines requires a line range"))
1232 raise error.ParseError(_(b"followlines requires a line range"))
1233
1233
1234 rev = b'.'
1234 rev = b'.'
1235 if b'startrev' in args:
1235 if b'startrev' in args:
1236 revs = getset(repo, fullreposet(repo), args[b'startrev'])
1236 revs = getset(repo, fullreposet(repo), args[b'startrev'])
1237 if len(revs) != 1:
1237 if len(revs) != 1:
1238 raise error.ParseError(
1238 raise error.ParseError(
1239 # i18n: "followlines" is a keyword
1239 # i18n: "followlines" is a keyword
1240 _(b"followlines expects exactly one revision")
1240 _(b"followlines expects exactly one revision")
1241 )
1241 )
1242 rev = revs.last()
1242 rev = revs.last()
1243
1243
1244 pat = getstring(args[b'file'], _(b"followlines requires a pattern"))
1244 pat = getstring(args[b'file'], _(b"followlines requires a pattern"))
1245 # i18n: "followlines" is a keyword
1245 # i18n: "followlines" is a keyword
1246 msg = _(b"followlines expects exactly one file")
1246 msg = _(b"followlines expects exactly one file")
1247 fname = scmutil.parsefollowlinespattern(repo, rev, pat, msg)
1247 fname = scmutil.parsefollowlinespattern(repo, rev, pat, msg)
1248 fromline, toline = util.processlinerange(
1248 fromline, toline = util.processlinerange(
1249 *getintrange(
1249 *getintrange(
1250 args[b'lines'][0],
1250 args[b'lines'][0],
1251 # i18n: "followlines" is a keyword
1251 # i18n: "followlines" is a keyword
1252 _(b"followlines expects a line number or a range"),
1252 _(b"followlines expects a line number or a range"),
1253 _(b"line range bounds must be integers"),
1253 _(b"line range bounds must be integers"),
1254 )
1254 )
1255 )
1255 )
1256
1256
1257 fctx = repo[rev].filectx(fname)
1257 fctx = repo[rev].filectx(fname)
1258 descend = False
1258 descend = False
1259 if b'descend' in args:
1259 if b'descend' in args:
1260 descend = getboolean(
1260 descend = getboolean(
1261 args[b'descend'],
1261 args[b'descend'],
1262 # i18n: "descend" is a keyword
1262 # i18n: "descend" is a keyword
1263 _(b"descend argument must be a boolean"),
1263 _(b"descend argument must be a boolean"),
1264 )
1264 )
1265 if descend:
1265 if descend:
1266 rs = generatorset(
1266 rs = generatorset(
1267 (
1267 (
1268 c.rev()
1268 c.rev()
1269 for c, _linerange in dagop.blockdescendants(
1269 for c, _linerange in dagop.blockdescendants(
1270 fctx, fromline, toline
1270 fctx, fromline, toline
1271 )
1271 )
1272 ),
1272 ),
1273 iterasc=True,
1273 iterasc=True,
1274 )
1274 )
1275 else:
1275 else:
1276 rs = generatorset(
1276 rs = generatorset(
1277 (
1277 (
1278 c.rev()
1278 c.rev()
1279 for c, _linerange in dagop.blockancestors(
1279 for c, _linerange in dagop.blockancestors(
1280 fctx, fromline, toline
1280 fctx, fromline, toline
1281 )
1281 )
1282 ),
1282 ),
1283 iterasc=False,
1283 iterasc=False,
1284 )
1284 )
1285 return subset & rs
1285 return subset & rs
1286
1286
1287
1287
1288 @predicate(b'all()', safe=True)
1288 @predicate(b'all()', safe=True)
1289 def getall(repo, subset, x):
1289 def getall(repo, subset, x):
1290 """All changesets, the same as ``0:tip``.
1290 """All changesets, the same as ``0:tip``.
1291 """
1291 """
1292 # i18n: "all" is a keyword
1292 # i18n: "all" is a keyword
1293 getargs(x, 0, 0, _(b"all takes no arguments"))
1293 getargs(x, 0, 0, _(b"all takes no arguments"))
1294 return subset & spanset(repo) # drop "null" if any
1294 return subset & spanset(repo) # drop "null" if any
1295
1295
1296
1296
1297 @predicate(b'grep(regex)', weight=10)
1297 @predicate(b'grep(regex)', weight=10)
1298 def grep(repo, subset, x):
1298 def grep(repo, subset, x):
1299 """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1299 """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1300 to ensure special escape characters are handled correctly. Unlike
1300 to ensure special escape characters are handled correctly. Unlike
1301 ``keyword(string)``, the match is case-sensitive.
1301 ``keyword(string)``, the match is case-sensitive.
1302 """
1302 """
1303 try:
1303 try:
1304 # i18n: "grep" is a keyword
1304 # i18n: "grep" is a keyword
1305 gr = re.compile(getstring(x, _(b"grep requires a string")))
1305 gr = re.compile(getstring(x, _(b"grep requires a string")))
1306 except re.error as e:
1306 except re.error as e:
1307 raise error.ParseError(
1307 raise error.ParseError(
1308 _(b'invalid match pattern: %s') % stringutil.forcebytestr(e)
1308 _(b'invalid match pattern: %s') % stringutil.forcebytestr(e)
1309 )
1309 )
1310
1310
1311 def matches(x):
1311 def matches(x):
1312 c = repo[x]
1312 c = repo[x]
1313 for e in c.files() + [c.user(), c.description()]:
1313 for e in c.files() + [c.user(), c.description()]:
1314 if gr.search(e):
1314 if gr.search(e):
1315 return True
1315 return True
1316 return False
1316 return False
1317
1317
1318 return subset.filter(matches, condrepr=(b'<grep %r>', gr.pattern))
1318 return subset.filter(matches, condrepr=(b'<grep %r>', gr.pattern))
1319
1319
1320
1320
1321 @predicate(b'_matchfiles', safe=True)
1321 @predicate(b'_matchfiles', safe=True)
1322 def _matchfiles(repo, subset, x):
1322 def _matchfiles(repo, subset, x):
1323 # _matchfiles takes a revset list of prefixed arguments:
1323 # _matchfiles takes a revset list of prefixed arguments:
1324 #
1324 #
1325 # [p:foo, i:bar, x:baz]
1325 # [p:foo, i:bar, x:baz]
1326 #
1326 #
1327 # builds a match object from them and filters subset. Allowed
1327 # builds a match object from them and filters subset. Allowed
1328 # prefixes are 'p:' for regular patterns, 'i:' for include
1328 # prefixes are 'p:' for regular patterns, 'i:' for include
1329 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1329 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1330 # a revision identifier, or the empty string to reference the
1330 # a revision identifier, or the empty string to reference the
1331 # working directory, from which the match object is
1331 # working directory, from which the match object is
1332 # initialized. Use 'd:' to set the default matching mode, default
1332 # initialized. Use 'd:' to set the default matching mode, default
1333 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1333 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1334
1334
1335 l = getargs(x, 1, -1, b"_matchfiles requires at least one argument")
1335 l = getargs(x, 1, -1, b"_matchfiles requires at least one argument")
1336 pats, inc, exc = [], [], []
1336 pats, inc, exc = [], [], []
1337 rev, default = None, None
1337 rev, default = None, None
1338 for arg in l:
1338 for arg in l:
1339 s = getstring(arg, b"_matchfiles requires string arguments")
1339 s = getstring(arg, b"_matchfiles requires string arguments")
1340 prefix, value = s[:2], s[2:]
1340 prefix, value = s[:2], s[2:]
1341 if prefix == b'p:':
1341 if prefix == b'p:':
1342 pats.append(value)
1342 pats.append(value)
1343 elif prefix == b'i:':
1343 elif prefix == b'i:':
1344 inc.append(value)
1344 inc.append(value)
1345 elif prefix == b'x:':
1345 elif prefix == b'x:':
1346 exc.append(value)
1346 exc.append(value)
1347 elif prefix == b'r:':
1347 elif prefix == b'r:':
1348 if rev is not None:
1348 if rev is not None:
1349 raise error.ParseError(
1349 raise error.ParseError(
1350 b'_matchfiles expected at most one revision'
1350 b'_matchfiles expected at most one revision'
1351 )
1351 )
1352 if value == b'': # empty means working directory
1352 if value == b'': # empty means working directory
1353 rev = node.wdirrev
1353 rev = node.wdirrev
1354 else:
1354 else:
1355 rev = value
1355 rev = value
1356 elif prefix == b'd:':
1356 elif prefix == b'd:':
1357 if default is not None:
1357 if default is not None:
1358 raise error.ParseError(
1358 raise error.ParseError(
1359 b'_matchfiles expected at most one default mode'
1359 b'_matchfiles expected at most one default mode'
1360 )
1360 )
1361 default = value
1361 default = value
1362 else:
1362 else:
1363 raise error.ParseError(b'invalid _matchfiles prefix: %s' % prefix)
1363 raise error.ParseError(b'invalid _matchfiles prefix: %s' % prefix)
1364 if not default:
1364 if not default:
1365 default = b'glob'
1365 default = b'glob'
1366 hasset = any(matchmod.patkind(p) == b'set' for p in pats + inc + exc)
1366 hasset = any(matchmod.patkind(p) == b'set' for p in pats + inc + exc)
1367
1367
1368 mcache = [None]
1368 mcache = [None]
1369
1369
1370 # This directly read the changelog data as creating changectx for all
1370 # This directly read the changelog data as creating changectx for all
1371 # revisions is quite expensive.
1371 # revisions is quite expensive.
1372 getfiles = repo.changelog.readfiles
1372 getfiles = repo.changelog.readfiles
1373 wdirrev = node.wdirrev
1373 wdirrev = node.wdirrev
1374
1374
1375 def matches(x):
1375 def matches(x):
1376 if x == wdirrev:
1376 if x == wdirrev:
1377 files = repo[x].files()
1377 files = repo[x].files()
1378 else:
1378 else:
1379 files = getfiles(x)
1379 files = getfiles(x)
1380
1380
1381 if not mcache[0] or (hasset and rev is None):
1381 if not mcache[0] or (hasset and rev is None):
1382 r = x if rev is None else rev
1382 r = x if rev is None else rev
1383 mcache[0] = matchmod.match(
1383 mcache[0] = matchmod.match(
1384 repo.root,
1384 repo.root,
1385 repo.getcwd(),
1385 repo.getcwd(),
1386 pats,
1386 pats,
1387 include=inc,
1387 include=inc,
1388 exclude=exc,
1388 exclude=exc,
1389 ctx=repo[r],
1389 ctx=repo[r],
1390 default=default,
1390 default=default,
1391 )
1391 )
1392 m = mcache[0]
1392 m = mcache[0]
1393
1393
1394 for f in files:
1394 for f in files:
1395 if m(f):
1395 if m(f):
1396 return True
1396 return True
1397 return False
1397 return False
1398
1398
1399 return subset.filter(
1399 return subset.filter(
1400 matches,
1400 matches,
1401 condrepr=(
1401 condrepr=(
1402 b'<matchfiles patterns=%r, include=%r '
1402 b'<matchfiles patterns=%r, include=%r '
1403 b'exclude=%r, default=%r, rev=%r>',
1403 b'exclude=%r, default=%r, rev=%r>',
1404 pats,
1404 pats,
1405 inc,
1405 inc,
1406 exc,
1406 exc,
1407 default,
1407 default,
1408 rev,
1408 rev,
1409 ),
1409 ),
1410 )
1410 )
1411
1411
1412
1412
1413 @predicate(b'file(pattern)', safe=True, weight=10)
1413 @predicate(b'file(pattern)', safe=True, weight=10)
1414 def hasfile(repo, subset, x):
1414 def hasfile(repo, subset, x):
1415 """Changesets affecting files matched by pattern.
1415 """Changesets affecting files matched by pattern.
1416
1416
1417 For a faster but less accurate result, consider using ``filelog()``
1417 For a faster but less accurate result, consider using ``filelog()``
1418 instead.
1418 instead.
1419
1419
1420 This predicate uses ``glob:`` as the default kind of pattern.
1420 This predicate uses ``glob:`` as the default kind of pattern.
1421 """
1421 """
1422 # i18n: "file" is a keyword
1422 # i18n: "file" is a keyword
1423 pat = getstring(x, _(b"file requires a pattern"))
1423 pat = getstring(x, _(b"file requires a pattern"))
1424 return _matchfiles(repo, subset, (b'string', b'p:' + pat))
1424 return _matchfiles(repo, subset, (b'string', b'p:' + pat))
1425
1425
1426
1426
1427 @predicate(b'head()', safe=True)
1427 @predicate(b'head()', safe=True)
1428 def head(repo, subset, x):
1428 def head(repo, subset, x):
1429 """Changeset is a named branch head.
1429 """Changeset is a named branch head.
1430 """
1430 """
1431 # i18n: "head" is a keyword
1431 # i18n: "head" is a keyword
1432 getargs(x, 0, 0, _(b"head takes no arguments"))
1432 getargs(x, 0, 0, _(b"head takes no arguments"))
1433 hs = set()
1433 hs = set()
1434 cl = repo.changelog
1434 cl = repo.changelog
1435 for ls in repo.branchmap().iterheads():
1435 for ls in repo.branchmap().iterheads():
1436 hs.update(cl.rev(h) for h in ls)
1436 hs.update(cl.rev(h) for h in ls)
1437 return subset & baseset(hs)
1437 return subset & baseset(hs)
1438
1438
1439
1439
1440 @predicate(b'heads(set)', safe=True, takeorder=True)
1440 @predicate(b'heads(set)', safe=True, takeorder=True)
1441 def heads(repo, subset, x, order):
1441 def heads(repo, subset, x, order):
1442 """Members of set with no children in set.
1442 """Members of set with no children in set.
1443 """
1443 """
1444 # argument set should never define order
1444 # argument set should never define order
1445 if order == defineorder:
1445 if order == defineorder:
1446 order = followorder
1446 order = followorder
1447 inputset = getset(repo, fullreposet(repo), x, order=order)
1447 inputset = getset(repo, fullreposet(repo), x, order=order)
1448 wdirparents = None
1448 wdirparents = None
1449 if node.wdirrev in inputset:
1449 if node.wdirrev in inputset:
1450 # a bit slower, but not common so good enough for now
1450 # a bit slower, but not common so good enough for now
1451 wdirparents = [p.rev() for p in repo[None].parents()]
1451 wdirparents = [p.rev() for p in repo[None].parents()]
1452 inputset = set(inputset)
1452 inputset = set(inputset)
1453 inputset.discard(node.wdirrev)
1453 inputset.discard(node.wdirrev)
1454 heads = repo.changelog.headrevs(inputset)
1454 heads = repo.changelog.headrevs(inputset)
1455 if wdirparents is not None:
1455 if wdirparents is not None:
1456 heads.difference_update(wdirparents)
1456 heads.difference_update(wdirparents)
1457 heads.add(node.wdirrev)
1457 heads.add(node.wdirrev)
1458 heads = baseset(heads)
1458 heads = baseset(heads)
1459 return subset & heads
1459 return subset & heads
1460
1460
1461
1461
1462 @predicate(b'hidden()', safe=True)
1462 @predicate(b'hidden()', safe=True)
1463 def hidden(repo, subset, x):
1463 def hidden(repo, subset, x):
1464 """Hidden changesets.
1464 """Hidden changesets.
1465 """
1465 """
1466 # i18n: "hidden" is a keyword
1466 # i18n: "hidden" is a keyword
1467 getargs(x, 0, 0, _(b"hidden takes no arguments"))
1467 getargs(x, 0, 0, _(b"hidden takes no arguments"))
1468 hiddenrevs = repoview.filterrevs(repo, b'visible')
1468 hiddenrevs = repoview.filterrevs(repo, b'visible')
1469 return subset & hiddenrevs
1469 return subset & hiddenrevs
1470
1470
1471
1471
1472 @predicate(b'keyword(string)', safe=True, weight=10)
1472 @predicate(b'keyword(string)', safe=True, weight=10)
1473 def keyword(repo, subset, x):
1473 def keyword(repo, subset, x):
1474 """Search commit message, user name, and names of changed files for
1474 """Search commit message, user name, and names of changed files for
1475 string. The match is case-insensitive.
1475 string. The match is case-insensitive.
1476
1476
1477 For a regular expression or case sensitive search of these fields, use
1477 For a regular expression or case sensitive search of these fields, use
1478 ``grep(regex)``.
1478 ``grep(regex)``.
1479 """
1479 """
1480 # i18n: "keyword" is a keyword
1480 # i18n: "keyword" is a keyword
1481 kw = encoding.lower(getstring(x, _(b"keyword requires a string")))
1481 kw = encoding.lower(getstring(x, _(b"keyword requires a string")))
1482
1482
1483 def matches(r):
1483 def matches(r):
1484 c = repo[r]
1484 c = repo[r]
1485 return any(
1485 return any(
1486 kw in encoding.lower(t)
1486 kw in encoding.lower(t)
1487 for t in c.files() + [c.user(), c.description()]
1487 for t in c.files() + [c.user(), c.description()]
1488 )
1488 )
1489
1489
1490 return subset.filter(matches, condrepr=(b'<keyword %r>', kw))
1490 return subset.filter(matches, condrepr=(b'<keyword %r>', kw))
1491
1491
1492
1492
1493 @predicate(b'limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0)
1493 @predicate(b'limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0)
1494 def limit(repo, subset, x, order):
1494 def limit(repo, subset, x, order):
1495 """First n members of set, defaulting to 1, starting from offset.
1495 """First n members of set, defaulting to 1, starting from offset.
1496 """
1496 """
1497 args = getargsdict(x, b'limit', b'set n offset')
1497 args = getargsdict(x, b'limit', b'set n offset')
1498 if b'set' not in args:
1498 if b'set' not in args:
1499 # i18n: "limit" is a keyword
1499 # i18n: "limit" is a keyword
1500 raise error.ParseError(_(b"limit requires one to three arguments"))
1500 raise error.ParseError(_(b"limit requires one to three arguments"))
1501 # i18n: "limit" is a keyword
1501 # i18n: "limit" is a keyword
1502 lim = getinteger(args.get(b'n'), _(b"limit expects a number"), default=1)
1502 lim = getinteger(args.get(b'n'), _(b"limit expects a number"), default=1)
1503 if lim < 0:
1503 if lim < 0:
1504 raise error.ParseError(_(b"negative number to select"))
1504 raise error.ParseError(_(b"negative number to select"))
1505 # i18n: "limit" is a keyword
1505 # i18n: "limit" is a keyword
1506 ofs = getinteger(
1506 ofs = getinteger(
1507 args.get(b'offset'), _(b"limit expects a number"), default=0
1507 args.get(b'offset'), _(b"limit expects a number"), default=0
1508 )
1508 )
1509 if ofs < 0:
1509 if ofs < 0:
1510 raise error.ParseError(_(b"negative offset"))
1510 raise error.ParseError(_(b"negative offset"))
1511 os = getset(repo, fullreposet(repo), args[b'set'])
1511 os = getset(repo, fullreposet(repo), args[b'set'])
1512 ls = os.slice(ofs, ofs + lim)
1512 ls = os.slice(ofs, ofs + lim)
1513 if order == followorder and lim > 1:
1513 if order == followorder and lim > 1:
1514 return subset & ls
1514 return subset & ls
1515 return ls & subset
1515 return ls & subset
1516
1516
1517
1517
1518 @predicate(b'last(set, [n])', safe=True, takeorder=True)
1518 @predicate(b'last(set, [n])', safe=True, takeorder=True)
1519 def last(repo, subset, x, order):
1519 def last(repo, subset, x, order):
1520 """Last n members of set, defaulting to 1.
1520 """Last n members of set, defaulting to 1.
1521 """
1521 """
1522 # i18n: "last" is a keyword
1522 # i18n: "last" is a keyword
1523 l = getargs(x, 1, 2, _(b"last requires one or two arguments"))
1523 l = getargs(x, 1, 2, _(b"last requires one or two arguments"))
1524 lim = 1
1524 lim = 1
1525 if len(l) == 2:
1525 if len(l) == 2:
1526 # i18n: "last" is a keyword
1526 # i18n: "last" is a keyword
1527 lim = getinteger(l[1], _(b"last expects a number"))
1527 lim = getinteger(l[1], _(b"last expects a number"))
1528 if lim < 0:
1528 if lim < 0:
1529 raise error.ParseError(_(b"negative number to select"))
1529 raise error.ParseError(_(b"negative number to select"))
1530 os = getset(repo, fullreposet(repo), l[0])
1530 os = getset(repo, fullreposet(repo), l[0])
1531 os.reverse()
1531 os.reverse()
1532 ls = os.slice(0, lim)
1532 ls = os.slice(0, lim)
1533 if order == followorder and lim > 1:
1533 if order == followorder and lim > 1:
1534 return subset & ls
1534 return subset & ls
1535 ls.reverse()
1535 ls.reverse()
1536 return ls & subset
1536 return ls & subset
1537
1537
1538
1538
1539 @predicate(b'max(set)', safe=True)
1539 @predicate(b'max(set)', safe=True)
1540 def maxrev(repo, subset, x):
1540 def maxrev(repo, subset, x):
1541 """Changeset with highest revision number in set.
1541 """Changeset with highest revision number in set.
1542 """
1542 """
1543 os = getset(repo, fullreposet(repo), x)
1543 os = getset(repo, fullreposet(repo), x)
1544 try:
1544 try:
1545 m = os.max()
1545 m = os.max()
1546 if m in subset:
1546 if m in subset:
1547 return baseset([m], datarepr=(b'<max %r, %r>', subset, os))
1547 return baseset([m], datarepr=(b'<max %r, %r>', subset, os))
1548 except ValueError:
1548 except ValueError:
1549 # os.max() throws a ValueError when the collection is empty.
1549 # os.max() throws a ValueError when the collection is empty.
1550 # Same as python's max().
1550 # Same as python's max().
1551 pass
1551 pass
1552 return baseset(datarepr=(b'<max %r, %r>', subset, os))
1552 return baseset(datarepr=(b'<max %r, %r>', subset, os))
1553
1553
1554
1554
1555 @predicate(b'merge()', safe=True)
1555 @predicate(b'merge()', safe=True)
1556 def merge(repo, subset, x):
1556 def merge(repo, subset, x):
1557 """Changeset is a merge changeset.
1557 """Changeset is a merge changeset.
1558 """
1558 """
1559 # i18n: "merge" is a keyword
1559 # i18n: "merge" is a keyword
1560 getargs(x, 0, 0, _(b"merge takes no arguments"))
1560 getargs(x, 0, 0, _(b"merge takes no arguments"))
1561 cl = repo.changelog
1561 cl = repo.changelog
1562 nullrev = node.nullrev
1562 nullrev = node.nullrev
1563
1563
1564 def ismerge(r):
1564 def ismerge(r):
1565 try:
1565 try:
1566 return cl.parentrevs(r)[1] != nullrev
1566 return cl.parentrevs(r)[1] != nullrev
1567 except error.WdirUnsupported:
1567 except error.WdirUnsupported:
1568 return bool(repo[r].p2())
1568 return bool(repo[r].p2())
1569
1569
1570 return subset.filter(ismerge, condrepr=b'<merge>')
1570 return subset.filter(ismerge, condrepr=b'<merge>')
1571
1571
1572
1572
1573 @predicate(b'branchpoint()', safe=True)
1573 @predicate(b'branchpoint()', safe=True)
1574 def branchpoint(repo, subset, x):
1574 def branchpoint(repo, subset, x):
1575 """Changesets with more than one child.
1575 """Changesets with more than one child.
1576 """
1576 """
1577 # i18n: "branchpoint" is a keyword
1577 # i18n: "branchpoint" is a keyword
1578 getargs(x, 0, 0, _(b"branchpoint takes no arguments"))
1578 getargs(x, 0, 0, _(b"branchpoint takes no arguments"))
1579 cl = repo.changelog
1579 cl = repo.changelog
1580 if not subset:
1580 if not subset:
1581 return baseset()
1581 return baseset()
1582 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1582 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1583 # (and if it is not, it should.)
1583 # (and if it is not, it should.)
1584 baserev = min(subset)
1584 baserev = min(subset)
1585 parentscount = [0] * (len(repo) - baserev)
1585 parentscount = [0] * (len(repo) - baserev)
1586 for r in cl.revs(start=baserev + 1):
1586 for r in cl.revs(start=baserev + 1):
1587 for p in cl.parentrevs(r):
1587 for p in cl.parentrevs(r):
1588 if p >= baserev:
1588 if p >= baserev:
1589 parentscount[p - baserev] += 1
1589 parentscount[p - baserev] += 1
1590 return subset.filter(
1590 return subset.filter(
1591 lambda r: parentscount[r - baserev] > 1, condrepr=b'<branchpoint>'
1591 lambda r: parentscount[r - baserev] > 1, condrepr=b'<branchpoint>'
1592 )
1592 )
1593
1593
1594
1594
1595 @predicate(b'min(set)', safe=True)
1595 @predicate(b'min(set)', safe=True)
1596 def minrev(repo, subset, x):
1596 def minrev(repo, subset, x):
1597 """Changeset with lowest revision number in set.
1597 """Changeset with lowest revision number in set.
1598 """
1598 """
1599 os = getset(repo, fullreposet(repo), x)
1599 os = getset(repo, fullreposet(repo), x)
1600 try:
1600 try:
1601 m = os.min()
1601 m = os.min()
1602 if m in subset:
1602 if m in subset:
1603 return baseset([m], datarepr=(b'<min %r, %r>', subset, os))
1603 return baseset([m], datarepr=(b'<min %r, %r>', subset, os))
1604 except ValueError:
1604 except ValueError:
1605 # os.min() throws a ValueError when the collection is empty.
1605 # os.min() throws a ValueError when the collection is empty.
1606 # Same as python's min().
1606 # Same as python's min().
1607 pass
1607 pass
1608 return baseset(datarepr=(b'<min %r, %r>', subset, os))
1608 return baseset(datarepr=(b'<min %r, %r>', subset, os))
1609
1609
1610
1610
1611 @predicate(b'modifies(pattern)', safe=True, weight=30)
1611 @predicate(b'modifies(pattern)', safe=True, weight=30)
1612 def modifies(repo, subset, x):
1612 def modifies(repo, subset, x):
1613 """Changesets modifying files matched by pattern.
1613 """Changesets modifying files matched by pattern.
1614
1614
1615 The pattern without explicit kind like ``glob:`` is expected to be
1615 The pattern without explicit kind like ``glob:`` is expected to be
1616 relative to the current directory and match against a file or a
1616 relative to the current directory and match against a file or a
1617 directory.
1617 directory.
1618 """
1618 """
1619 # i18n: "modifies" is a keyword
1619 # i18n: "modifies" is a keyword
1620 pat = getstring(x, _(b"modifies requires a pattern"))
1620 pat = getstring(x, _(b"modifies requires a pattern"))
1621 return checkstatus(repo, subset, pat, 0)
1621 return checkstatus(repo, subset, pat, 0)
1622
1622
1623
1623
1624 @predicate(b'named(namespace)')
1624 @predicate(b'named(namespace)')
1625 def named(repo, subset, x):
1625 def named(repo, subset, x):
1626 """The changesets in a given namespace.
1626 """The changesets in a given namespace.
1627
1627
1628 Pattern matching is supported for `namespace`. See
1628 Pattern matching is supported for `namespace`. See
1629 :hg:`help revisions.patterns`.
1629 :hg:`help revisions.patterns`.
1630 """
1630 """
1631 # i18n: "named" is a keyword
1631 # i18n: "named" is a keyword
1632 args = getargs(x, 1, 1, _(b'named requires a namespace argument'))
1632 args = getargs(x, 1, 1, _(b'named requires a namespace argument'))
1633
1633
1634 ns = getstring(
1634 ns = getstring(
1635 args[0],
1635 args[0],
1636 # i18n: "named" is a keyword
1636 # i18n: "named" is a keyword
1637 _(b'the argument to named must be a string'),
1637 _(b'the argument to named must be a string'),
1638 )
1638 )
1639 kind, pattern, matcher = stringutil.stringmatcher(ns)
1639 kind, pattern, matcher = stringutil.stringmatcher(ns)
1640 namespaces = set()
1640 namespaces = set()
1641 if kind == b'literal':
1641 if kind == b'literal':
1642 if pattern not in repo.names:
1642 if pattern not in repo.names:
1643 raise error.RepoLookupError(
1643 raise error.RepoLookupError(
1644 _(b"namespace '%s' does not exist") % ns
1644 _(b"namespace '%s' does not exist") % ns
1645 )
1645 )
1646 namespaces.add(repo.names[pattern])
1646 namespaces.add(repo.names[pattern])
1647 else:
1647 else:
1648 for name, ns in pycompat.iteritems(repo.names):
1648 for name, ns in pycompat.iteritems(repo.names):
1649 if matcher(name):
1649 if matcher(name):
1650 namespaces.add(ns)
1650 namespaces.add(ns)
1651
1651
1652 names = set()
1652 names = set()
1653 for ns in namespaces:
1653 for ns in namespaces:
1654 for name in ns.listnames(repo):
1654 for name in ns.listnames(repo):
1655 if name not in ns.deprecated:
1655 if name not in ns.deprecated:
1656 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1656 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1657
1657
1658 names -= {node.nullrev}
1658 names -= {node.nullrev}
1659 return subset & names
1659 return subset & names
1660
1660
1661
1661
1662 @predicate(b'id(string)', safe=True)
1662 @predicate(b'id(string)', safe=True)
1663 def node_(repo, subset, x):
1663 def node_(repo, subset, x):
1664 """Revision non-ambiguously specified by the given hex string prefix.
1664 """Revision non-ambiguously specified by the given hex string prefix.
1665 """
1665 """
1666 # i18n: "id" is a keyword
1666 # i18n: "id" is a keyword
1667 l = getargs(x, 1, 1, _(b"id requires one argument"))
1667 l = getargs(x, 1, 1, _(b"id requires one argument"))
1668 # i18n: "id" is a keyword
1668 # i18n: "id" is a keyword
1669 n = getstring(l[0], _(b"id requires a string"))
1669 n = getstring(l[0], _(b"id requires a string"))
1670 if len(n) == 40:
1670 if len(n) == 40:
1671 try:
1671 try:
1672 rn = repo.changelog.rev(node.bin(n))
1672 rn = repo.changelog.rev(node.bin(n))
1673 except error.WdirUnsupported:
1673 except error.WdirUnsupported:
1674 rn = node.wdirrev
1674 rn = node.wdirrev
1675 except (LookupError, TypeError):
1675 except (LookupError, TypeError):
1676 rn = None
1676 rn = None
1677 else:
1677 else:
1678 rn = None
1678 rn = None
1679 try:
1679 try:
1680 pm = scmutil.resolvehexnodeidprefix(repo, n)
1680 pm = scmutil.resolvehexnodeidprefix(repo, n)
1681 if pm is not None:
1681 if pm is not None:
1682 rn = repo.changelog.rev(pm)
1682 rn = repo.changelog.rev(pm)
1683 except LookupError:
1683 except LookupError:
1684 pass
1684 pass
1685 except error.WdirUnsupported:
1685 except error.WdirUnsupported:
1686 rn = node.wdirrev
1686 rn = node.wdirrev
1687
1687
1688 if rn is None:
1688 if rn is None:
1689 return baseset()
1689 return baseset()
1690 result = baseset([rn])
1690 result = baseset([rn])
1691 return result & subset
1691 return result & subset
1692
1692
1693
1693
1694 @predicate(b'none()', safe=True)
1694 @predicate(b'none()', safe=True)
1695 def none(repo, subset, x):
1695 def none(repo, subset, x):
1696 """No changesets.
1696 """No changesets.
1697 """
1697 """
1698 # i18n: "none" is a keyword
1698 # i18n: "none" is a keyword
1699 getargs(x, 0, 0, _(b"none takes no arguments"))
1699 getargs(x, 0, 0, _(b"none takes no arguments"))
1700 return baseset()
1700 return baseset()
1701
1701
1702
1702
1703 @predicate(b'obsolete()', safe=True)
1703 @predicate(b'obsolete()', safe=True)
1704 def obsolete(repo, subset, x):
1704 def obsolete(repo, subset, x):
1705 """Mutable changeset with a newer version."""
1705 """Mutable changeset with a newer version."""
1706 # i18n: "obsolete" is a keyword
1706 # i18n: "obsolete" is a keyword
1707 getargs(x, 0, 0, _(b"obsolete takes no arguments"))
1707 getargs(x, 0, 0, _(b"obsolete takes no arguments"))
1708 obsoletes = obsmod.getrevs(repo, b'obsolete')
1708 obsoletes = obsmod.getrevs(repo, b'obsolete')
1709 return subset & obsoletes
1709 return subset & obsoletes
1710
1710
1711
1711
1712 @predicate(b'only(set, [set])', safe=True)
1712 @predicate(b'only(set, [set])', safe=True)
1713 def only(repo, subset, x):
1713 def only(repo, subset, x):
1714 """Changesets that are ancestors of the first set that are not ancestors
1714 """Changesets that are ancestors of the first set that are not ancestors
1715 of any other head in the repo. If a second set is specified, the result
1715 of any other head in the repo. If a second set is specified, the result
1716 is ancestors of the first set that are not ancestors of the second set
1716 is ancestors of the first set that are not ancestors of the second set
1717 (i.e. ::<set1> - ::<set2>).
1717 (i.e. ::<set1> - ::<set2>).
1718 """
1718 """
1719 cl = repo.changelog
1719 cl = repo.changelog
1720 # i18n: "only" is a keyword
1720 # i18n: "only" is a keyword
1721 args = getargs(x, 1, 2, _(b'only takes one or two arguments'))
1721 args = getargs(x, 1, 2, _(b'only takes one or two arguments'))
1722 include = getset(repo, fullreposet(repo), args[0])
1722 include = getset(repo, fullreposet(repo), args[0])
1723 if len(args) == 1:
1723 if len(args) == 1:
1724 if not include:
1724 if not include:
1725 return baseset()
1725 return baseset()
1726
1726
1727 descendants = set(dagop.revdescendants(repo, include, False))
1727 descendants = set(dagop.revdescendants(repo, include, False))
1728 exclude = [
1728 exclude = [
1729 rev
1729 rev
1730 for rev in cl.headrevs()
1730 for rev in cl.headrevs()
1731 if not rev in descendants and not rev in include
1731 if not rev in descendants and not rev in include
1732 ]
1732 ]
1733 else:
1733 else:
1734 exclude = getset(repo, fullreposet(repo), args[1])
1734 exclude = getset(repo, fullreposet(repo), args[1])
1735
1735
1736 results = set(cl.findmissingrevs(common=exclude, heads=include))
1736 results = set(cl.findmissingrevs(common=exclude, heads=include))
1737 # XXX we should turn this into a baseset instead of a set, smartset may do
1737 # XXX we should turn this into a baseset instead of a set, smartset may do
1738 # some optimizations from the fact this is a baseset.
1738 # some optimizations from the fact this is a baseset.
1739 return subset & results
1739 return subset & results
1740
1740
1741
1741
1742 @predicate(b'origin([set])', safe=True)
1742 @predicate(b'origin([set])', safe=True)
1743 def origin(repo, subset, x):
1743 def origin(repo, subset, x):
1744 """
1744 """
1745 Changesets that were specified as a source for the grafts, transplants or
1745 Changesets that were specified as a source for the grafts, transplants or
1746 rebases that created the given revisions. Omitting the optional set is the
1746 rebases that created the given revisions. Omitting the optional set is the
1747 same as passing all(). If a changeset created by these operations is itself
1747 same as passing all(). If a changeset created by these operations is itself
1748 specified as a source for one of these operations, only the source changeset
1748 specified as a source for one of these operations, only the source changeset
1749 for the first operation is selected.
1749 for the first operation is selected.
1750 """
1750 """
1751 if x is not None:
1751 if x is not None:
1752 dests = getset(repo, fullreposet(repo), x)
1752 dests = getset(repo, fullreposet(repo), x)
1753 else:
1753 else:
1754 dests = fullreposet(repo)
1754 dests = fullreposet(repo)
1755
1755
1756 def _firstsrc(rev):
1756 def _firstsrc(rev):
1757 src = _getrevsource(repo, rev)
1757 src = _getrevsource(repo, rev)
1758 if src is None:
1758 if src is None:
1759 return None
1759 return None
1760
1760
1761 while True:
1761 while True:
1762 prev = _getrevsource(repo, src)
1762 prev = _getrevsource(repo, src)
1763
1763
1764 if prev is None:
1764 if prev is None:
1765 return src
1765 return src
1766 src = prev
1766 src = prev
1767
1767
1768 o = {_firstsrc(r) for r in dests}
1768 o = {_firstsrc(r) for r in dests}
1769 o -= {None}
1769 o -= {None}
1770 # XXX we should turn this into a baseset instead of a set, smartset may do
1770 # XXX we should turn this into a baseset instead of a set, smartset may do
1771 # some optimizations from the fact this is a baseset.
1771 # some optimizations from the fact this is a baseset.
1772 return subset & o
1772 return subset & o
1773
1773
1774
1774
1775 @predicate(b'outgoing([path])', safe=False, weight=10)
1775 @predicate(b'outgoing([path])', safe=False, weight=10)
1776 def outgoing(repo, subset, x):
1776 def outgoing(repo, subset, x):
1777 """Changesets not found in the specified destination repository, or the
1777 """Changesets not found in the specified destination repository, or the
1778 default push location.
1778 default push location.
1779 """
1779 """
1780 # Avoid cycles.
1780 # Avoid cycles.
1781 from . import (
1781 from . import (
1782 discovery,
1782 discovery,
1783 hg,
1783 hg,
1784 )
1784 )
1785
1785
1786 # i18n: "outgoing" is a keyword
1786 # i18n: "outgoing" is a keyword
1787 l = getargs(x, 0, 1, _(b"outgoing takes one or no arguments"))
1787 l = getargs(x, 0, 1, _(b"outgoing takes one or no arguments"))
1788 # i18n: "outgoing" is a keyword
1788 # i18n: "outgoing" is a keyword
1789 dest = (
1789 dest = (
1790 l and getstring(l[0], _(b"outgoing requires a repository path")) or b''
1790 l and getstring(l[0], _(b"outgoing requires a repository path")) or b''
1791 )
1791 )
1792 if not dest:
1792 if not dest:
1793 # ui.paths.getpath() explicitly tests for None, not just a boolean
1793 # ui.paths.getpath() explicitly tests for None, not just a boolean
1794 dest = None
1794 dest = None
1795 path = repo.ui.paths.getpath(dest, default=(b'default-push', b'default'))
1795 path = repo.ui.paths.getpath(dest, default=(b'default-push', b'default'))
1796 if not path:
1796 if not path:
1797 raise error.Abort(
1797 raise error.Abort(
1798 _(b'default repository not configured!'),
1798 _(b'default repository not configured!'),
1799 hint=_(b"see 'hg help config.paths'"),
1799 hint=_(b"see 'hg help config.paths'"),
1800 )
1800 )
1801 dest = path.pushloc or path.loc
1801 dest = path.pushloc or path.loc
1802 branches = path.branch, []
1802 branches = path.branch, []
1803
1803
1804 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1804 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1805 if revs:
1805 if revs:
1806 revs = [repo.lookup(rev) for rev in revs]
1806 revs = [repo.lookup(rev) for rev in revs]
1807 other = hg.peer(repo, {}, dest)
1807 other = hg.peer(repo, {}, dest)
1808 repo.ui.pushbuffer()
1808 repo.ui.pushbuffer()
1809 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1809 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1810 repo.ui.popbuffer()
1810 repo.ui.popbuffer()
1811 cl = repo.changelog
1811 cl = repo.changelog
1812 o = {cl.rev(r) for r in outgoing.missing}
1812 o = {cl.rev(r) for r in outgoing.missing}
1813 return subset & o
1813 return subset & o
1814
1814
1815
1815
1816 @predicate(b'p1([set])', safe=True)
1816 @predicate(b'p1([set])', safe=True)
1817 def p1(repo, subset, x):
1817 def p1(repo, subset, x):
1818 """First parent of changesets in set, or the working directory.
1818 """First parent of changesets in set, or the working directory.
1819 """
1819 """
1820 if x is None:
1820 if x is None:
1821 p = repo[x].p1().rev()
1821 p = repo[x].p1().rev()
1822 if p >= 0:
1822 if p >= 0:
1823 return subset & baseset([p])
1823 return subset & baseset([p])
1824 return baseset()
1824 return baseset()
1825
1825
1826 ps = set()
1826 ps = set()
1827 cl = repo.changelog
1827 cl = repo.changelog
1828 for r in getset(repo, fullreposet(repo), x):
1828 for r in getset(repo, fullreposet(repo), x):
1829 try:
1829 try:
1830 ps.add(cl.parentrevs(r)[0])
1830 ps.add(cl.parentrevs(r)[0])
1831 except error.WdirUnsupported:
1831 except error.WdirUnsupported:
1832 ps.add(repo[r].p1().rev())
1832 ps.add(repo[r].p1().rev())
1833 ps -= {node.nullrev}
1833 ps -= {node.nullrev}
1834 # XXX we should turn this into a baseset instead of a set, smartset may do
1834 # XXX we should turn this into a baseset instead of a set, smartset may do
1835 # some optimizations from the fact this is a baseset.
1835 # some optimizations from the fact this is a baseset.
1836 return subset & ps
1836 return subset & ps
1837
1837
1838
1838
1839 @predicate(b'p2([set])', safe=True)
1839 @predicate(b'p2([set])', safe=True)
1840 def p2(repo, subset, x):
1840 def p2(repo, subset, x):
1841 """Second parent of changesets in set, or the working directory.
1841 """Second parent of changesets in set, or the working directory.
1842 """
1842 """
1843 if x is None:
1843 if x is None:
1844 ps = repo[x].parents()
1844 ps = repo[x].parents()
1845 try:
1845 try:
1846 p = ps[1].rev()
1846 p = ps[1].rev()
1847 if p >= 0:
1847 if p >= 0:
1848 return subset & baseset([p])
1848 return subset & baseset([p])
1849 return baseset()
1849 return baseset()
1850 except IndexError:
1850 except IndexError:
1851 return baseset()
1851 return baseset()
1852
1852
1853 ps = set()
1853 ps = set()
1854 cl = repo.changelog
1854 cl = repo.changelog
1855 for r in getset(repo, fullreposet(repo), x):
1855 for r in getset(repo, fullreposet(repo), x):
1856 try:
1856 try:
1857 ps.add(cl.parentrevs(r)[1])
1857 ps.add(cl.parentrevs(r)[1])
1858 except error.WdirUnsupported:
1858 except error.WdirUnsupported:
1859 parents = repo[r].parents()
1859 parents = repo[r].parents()
1860 if len(parents) == 2:
1860 if len(parents) == 2:
1861 ps.add(parents[1])
1861 ps.add(parents[1])
1862 ps -= {node.nullrev}
1862 ps -= {node.nullrev}
1863 # XXX we should turn this into a baseset instead of a set, smartset may do
1863 # XXX we should turn this into a baseset instead of a set, smartset may do
1864 # some optimizations from the fact this is a baseset.
1864 # some optimizations from the fact this is a baseset.
1865 return subset & ps
1865 return subset & ps
1866
1866
1867
1867
1868 def parentpost(repo, subset, x, order):
1868 def parentpost(repo, subset, x, order):
1869 return p1(repo, subset, x)
1869 return p1(repo, subset, x)
1870
1870
1871
1871
1872 @predicate(b'parents([set])', safe=True)
1872 @predicate(b'parents([set])', safe=True)
1873 def parents(repo, subset, x):
1873 def parents(repo, subset, x):
1874 """
1874 """
1875 The set of all parents for all changesets in set, or the working directory.
1875 The set of all parents for all changesets in set, or the working directory.
1876 """
1876 """
1877 if x is None:
1877 if x is None:
1878 ps = {p.rev() for p in repo[x].parents()}
1878 ps = {p.rev() for p in repo[x].parents()}
1879 else:
1879 else:
1880 ps = set()
1880 ps = set()
1881 cl = repo.changelog
1881 cl = repo.changelog
1882 up = ps.update
1882 up = ps.update
1883 parentrevs = cl.parentrevs
1883 parentrevs = cl.parentrevs
1884 for r in getset(repo, fullreposet(repo), x):
1884 for r in getset(repo, fullreposet(repo), x):
1885 try:
1885 try:
1886 up(parentrevs(r))
1886 up(parentrevs(r))
1887 except error.WdirUnsupported:
1887 except error.WdirUnsupported:
1888 up(p.rev() for p in repo[r].parents())
1888 up(p.rev() for p in repo[r].parents())
1889 ps -= {node.nullrev}
1889 ps -= {node.nullrev}
1890 return subset & ps
1890 return subset & ps
1891
1891
1892
1892
1893 def _phase(repo, subset, *targets):
1893 def _phase(repo, subset, *targets):
1894 """helper to select all rev in <targets> phases"""
1894 """helper to select all rev in <targets> phases"""
1895 return repo._phasecache.getrevset(repo, targets, subset)
1895 return repo._phasecache.getrevset(repo, targets, subset)
1896
1896
1897
1897
1898 @predicate(b'_phase(idx)', safe=True)
1898 @predicate(b'_phase(idx)', safe=True)
1899 def phase(repo, subset, x):
1899 def phase(repo, subset, x):
1900 l = getargs(x, 1, 1, b"_phase requires one argument")
1900 l = getargs(x, 1, 1, b"_phase requires one argument")
1901 target = getinteger(l[0], b"_phase expects a number")
1901 target = getinteger(l[0], b"_phase expects a number")
1902 return _phase(repo, subset, target)
1902 return _phase(repo, subset, target)
1903
1903
1904
1904
1905 @predicate(b'draft()', safe=True)
1905 @predicate(b'draft()', safe=True)
1906 def draft(repo, subset, x):
1906 def draft(repo, subset, x):
1907 """Changeset in draft phase."""
1907 """Changeset in draft phase."""
1908 # i18n: "draft" is a keyword
1908 # i18n: "draft" is a keyword
1909 getargs(x, 0, 0, _(b"draft takes no arguments"))
1909 getargs(x, 0, 0, _(b"draft takes no arguments"))
1910 target = phases.draft
1910 target = phases.draft
1911 return _phase(repo, subset, target)
1911 return _phase(repo, subset, target)
1912
1912
1913
1913
1914 @predicate(b'secret()', safe=True)
1914 @predicate(b'secret()', safe=True)
1915 def secret(repo, subset, x):
1915 def secret(repo, subset, x):
1916 """Changeset in secret phase."""
1916 """Changeset in secret phase."""
1917 # i18n: "secret" is a keyword
1917 # i18n: "secret" is a keyword
1918 getargs(x, 0, 0, _(b"secret takes no arguments"))
1918 getargs(x, 0, 0, _(b"secret takes no arguments"))
1919 target = phases.secret
1919 target = phases.secret
1920 return _phase(repo, subset, target)
1920 return _phase(repo, subset, target)
1921
1921
1922
1922
1923 @predicate(b'stack([revs])', safe=True)
1923 @predicate(b'stack([revs])', safe=True)
1924 def stack(repo, subset, x):
1924 def stack(repo, subset, x):
1925 """Experimental revset for the stack of changesets or working directory
1925 """Experimental revset for the stack of changesets or working directory
1926 parent. (EXPERIMENTAL)
1926 parent. (EXPERIMENTAL)
1927 """
1927 """
1928 if x is None:
1928 if x is None:
1929 stacks = stackmod.getstack(repo)
1929 stacks = stackmod.getstack(repo)
1930 else:
1930 else:
1931 stacks = smartset.baseset([])
1931 stacks = smartset.baseset([])
1932 for revision in getset(repo, fullreposet(repo), x):
1932 for revision in getset(repo, fullreposet(repo), x):
1933 currentstack = stackmod.getstack(repo, revision)
1933 currentstack = stackmod.getstack(repo, revision)
1934 stacks = stacks + currentstack
1934 stacks = stacks + currentstack
1935
1935
1936 return subset & stacks
1936 return subset & stacks
1937
1937
1938
1938
1939 def parentspec(repo, subset, x, n, order):
1939 def parentspec(repo, subset, x, n, order):
1940 """``set^0``
1940 """``set^0``
1941 The set.
1941 The set.
1942 ``set^1`` (or ``set^``), ``set^2``
1942 ``set^1`` (or ``set^``), ``set^2``
1943 First or second parent, respectively, of all changesets in set.
1943 First or second parent, respectively, of all changesets in set.
1944 """
1944 """
1945 try:
1945 try:
1946 n = int(n[1])
1946 n = int(n[1])
1947 if n not in (0, 1, 2):
1947 if n not in (0, 1, 2):
1948 raise ValueError
1948 raise ValueError
1949 except (TypeError, ValueError):
1949 except (TypeError, ValueError):
1950 raise error.ParseError(_(b"^ expects a number 0, 1, or 2"))
1950 raise error.ParseError(_(b"^ expects a number 0, 1, or 2"))
1951 ps = set()
1951 ps = set()
1952 cl = repo.changelog
1952 cl = repo.changelog
1953 for r in getset(repo, fullreposet(repo), x):
1953 for r in getset(repo, fullreposet(repo), x):
1954 if n == 0:
1954 if n == 0:
1955 ps.add(r)
1955 ps.add(r)
1956 elif n == 1:
1956 elif n == 1:
1957 try:
1957 try:
1958 ps.add(cl.parentrevs(r)[0])
1958 ps.add(cl.parentrevs(r)[0])
1959 except error.WdirUnsupported:
1959 except error.WdirUnsupported:
1960 ps.add(repo[r].p1().rev())
1960 ps.add(repo[r].p1().rev())
1961 else:
1961 else:
1962 try:
1962 try:
1963 parents = cl.parentrevs(r)
1963 parents = cl.parentrevs(r)
1964 if parents[1] != node.nullrev:
1964 if parents[1] != node.nullrev:
1965 ps.add(parents[1])
1965 ps.add(parents[1])
1966 except error.WdirUnsupported:
1966 except error.WdirUnsupported:
1967 parents = repo[r].parents()
1967 parents = repo[r].parents()
1968 if len(parents) == 2:
1968 if len(parents) == 2:
1969 ps.add(parents[1].rev())
1969 ps.add(parents[1].rev())
1970 return subset & ps
1970 return subset & ps
1971
1971
1972
1972
1973 @predicate(b'present(set)', safe=True, takeorder=True)
1973 @predicate(b'present(set)', safe=True, takeorder=True)
1974 def present(repo, subset, x, order):
1974 def present(repo, subset, x, order):
1975 """An empty set, if any revision in set isn't found; otherwise,
1975 """An empty set, if any revision in set isn't found; otherwise,
1976 all revisions in set.
1976 all revisions in set.
1977
1977
1978 If any of specified revisions is not present in the local repository,
1978 If any of specified revisions is not present in the local repository,
1979 the query is normally aborted. But this predicate allows the query
1979 the query is normally aborted. But this predicate allows the query
1980 to continue even in such cases.
1980 to continue even in such cases.
1981 """
1981 """
1982 try:
1982 try:
1983 return getset(repo, subset, x, order)
1983 return getset(repo, subset, x, order)
1984 except error.RepoLookupError:
1984 except error.RepoLookupError:
1985 return baseset()
1985 return baseset()
1986
1986
1987
1987
1988 # for internal use
1988 # for internal use
1989 @predicate(b'_notpublic', safe=True)
1989 @predicate(b'_notpublic', safe=True)
1990 def _notpublic(repo, subset, x):
1990 def _notpublic(repo, subset, x):
1991 getargs(x, 0, 0, b"_notpublic takes no arguments")
1991 getargs(x, 0, 0, b"_notpublic takes no arguments")
1992 return _phase(repo, subset, phases.draft, phases.secret)
1992 return _phase(repo, subset, phases.draft, phases.secret)
1993
1993
1994
1994
1995 # for internal use
1995 # for internal use
1996 @predicate(b'_phaseandancestors(phasename, set)', safe=True)
1996 @predicate(b'_phaseandancestors(phasename, set)', safe=True)
1997 def _phaseandancestors(repo, subset, x):
1997 def _phaseandancestors(repo, subset, x):
1998 # equivalent to (phasename() & ancestors(set)) but more efficient
1998 # equivalent to (phasename() & ancestors(set)) but more efficient
1999 # phasename could be one of 'draft', 'secret', or '_notpublic'
1999 # phasename could be one of 'draft', 'secret', or '_notpublic'
2000 args = getargs(x, 2, 2, b"_phaseandancestors requires two arguments")
2000 args = getargs(x, 2, 2, b"_phaseandancestors requires two arguments")
2001 phasename = getsymbol(args[0])
2001 phasename = getsymbol(args[0])
2002 s = getset(repo, fullreposet(repo), args[1])
2002 s = getset(repo, fullreposet(repo), args[1])
2003
2003
2004 draft = phases.draft
2004 draft = phases.draft
2005 secret = phases.secret
2005 secret = phases.secret
2006 phasenamemap = {
2006 phasenamemap = {
2007 b'_notpublic': draft,
2007 b'_notpublic': draft,
2008 b'draft': draft, # follow secret's ancestors
2008 b'draft': draft, # follow secret's ancestors
2009 b'secret': secret,
2009 b'secret': secret,
2010 }
2010 }
2011 if phasename not in phasenamemap:
2011 if phasename not in phasenamemap:
2012 raise error.ParseError(b'%r is not a valid phasename' % phasename)
2012 raise error.ParseError(b'%r is not a valid phasename' % phasename)
2013
2013
2014 minimalphase = phasenamemap[phasename]
2014 minimalphase = phasenamemap[phasename]
2015 getphase = repo._phasecache.phase
2015 getphase = repo._phasecache.phase
2016
2016
2017 def cutfunc(rev):
2017 def cutfunc(rev):
2018 return getphase(repo, rev) < minimalphase
2018 return getphase(repo, rev) < minimalphase
2019
2019
2020 revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
2020 revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
2021
2021
2022 if phasename == b'draft': # need to remove secret changesets
2022 if phasename == b'draft': # need to remove secret changesets
2023 revs = revs.filter(lambda r: getphase(repo, r) == draft)
2023 revs = revs.filter(lambda r: getphase(repo, r) == draft)
2024 return subset & revs
2024 return subset & revs
2025
2025
2026
2026
2027 @predicate(b'public()', safe=True)
2027 @predicate(b'public()', safe=True)
2028 def public(repo, subset, x):
2028 def public(repo, subset, x):
2029 """Changeset in public phase."""
2029 """Changeset in public phase."""
2030 # i18n: "public" is a keyword
2030 # i18n: "public" is a keyword
2031 getargs(x, 0, 0, _(b"public takes no arguments"))
2031 getargs(x, 0, 0, _(b"public takes no arguments"))
2032 return _phase(repo, subset, phases.public)
2032 return _phase(repo, subset, phases.public)
2033
2033
2034
2034
2035 @predicate(b'remote([id [,path]])', safe=False)
2035 @predicate(b'remote([id [,path]])', safe=False)
2036 def remote(repo, subset, x):
2036 def remote(repo, subset, x):
2037 """Local revision that corresponds to the given identifier in a
2037 """Local revision that corresponds to the given identifier in a
2038 remote repository, if present. Here, the '.' identifier is a
2038 remote repository, if present. Here, the '.' identifier is a
2039 synonym for the current local branch.
2039 synonym for the current local branch.
2040 """
2040 """
2041
2041
2042 from . import hg # avoid start-up nasties
2042 from . import hg # avoid start-up nasties
2043
2043
2044 # i18n: "remote" is a keyword
2044 # i18n: "remote" is a keyword
2045 l = getargs(x, 0, 2, _(b"remote takes zero, one, or two arguments"))
2045 l = getargs(x, 0, 2, _(b"remote takes zero, one, or two arguments"))
2046
2046
2047 q = b'.'
2047 q = b'.'
2048 if len(l) > 0:
2048 if len(l) > 0:
2049 # i18n: "remote" is a keyword
2049 # i18n: "remote" is a keyword
2050 q = getstring(l[0], _(b"remote requires a string id"))
2050 q = getstring(l[0], _(b"remote requires a string id"))
2051 if q == b'.':
2051 if q == b'.':
2052 q = repo[b'.'].branch()
2052 q = repo[b'.'].branch()
2053
2053
2054 dest = b''
2054 dest = b''
2055 if len(l) > 1:
2055 if len(l) > 1:
2056 # i18n: "remote" is a keyword
2056 # i18n: "remote" is a keyword
2057 dest = getstring(l[1], _(b"remote requires a repository path"))
2057 dest = getstring(l[1], _(b"remote requires a repository path"))
2058 dest = repo.ui.expandpath(dest or b'default')
2058 dest = repo.ui.expandpath(dest or b'default')
2059 dest, branches = hg.parseurl(dest)
2059 dest, branches = hg.parseurl(dest)
2060
2060
2061 other = hg.peer(repo, {}, dest)
2061 other = hg.peer(repo, {}, dest)
2062 n = other.lookup(q)
2062 n = other.lookup(q)
2063 if n in repo:
2063 if n in repo:
2064 r = repo[n].rev()
2064 r = repo[n].rev()
2065 if r in subset:
2065 if r in subset:
2066 return baseset([r])
2066 return baseset([r])
2067 return baseset()
2067 return baseset()
2068
2068
2069
2069
2070 @predicate(b'removes(pattern)', safe=True, weight=30)
2070 @predicate(b'removes(pattern)', safe=True, weight=30)
2071 def removes(repo, subset, x):
2071 def removes(repo, subset, x):
2072 """Changesets which remove files matching pattern.
2072 """Changesets which remove files matching pattern.
2073
2073
2074 The pattern without explicit kind like ``glob:`` is expected to be
2074 The pattern without explicit kind like ``glob:`` is expected to be
2075 relative to the current directory and match against a file or a
2075 relative to the current directory and match against a file or a
2076 directory.
2076 directory.
2077 """
2077 """
2078 # i18n: "removes" is a keyword
2078 # i18n: "removes" is a keyword
2079 pat = getstring(x, _(b"removes requires a pattern"))
2079 pat = getstring(x, _(b"removes requires a pattern"))
2080 return checkstatus(repo, subset, pat, 2)
2080 return checkstatus(repo, subset, pat, 2)
2081
2081
2082
2082
2083 @predicate(b'rev(number)', safe=True)
2083 @predicate(b'rev(number)', safe=True)
2084 def rev(repo, subset, x):
2084 def rev(repo, subset, x):
2085 """Revision with the given numeric identifier.
2085 """Revision with the given numeric identifier.
2086 """
2086 """
2087 # i18n: "rev" is a keyword
2087 # i18n: "rev" is a keyword
2088 l = getargs(x, 1, 1, _(b"rev requires one argument"))
2088 l = getargs(x, 1, 1, _(b"rev requires one argument"))
2089 try:
2089 try:
2090 # i18n: "rev" is a keyword
2090 # i18n: "rev" is a keyword
2091 l = int(getstring(l[0], _(b"rev requires a number")))
2091 l = int(getstring(l[0], _(b"rev requires a number")))
2092 except (TypeError, ValueError):
2092 except (TypeError, ValueError):
2093 # i18n: "rev" is a keyword
2093 # i18n: "rev" is a keyword
2094 raise error.ParseError(_(b"rev expects a number"))
2094 raise error.ParseError(_(b"rev expects a number"))
2095 if l not in repo.changelog and l not in _virtualrevs:
2095 if l not in repo.changelog and l not in _virtualrevs:
2096 return baseset()
2096 return baseset()
2097 return subset & baseset([l])
2097 return subset & baseset([l])
2098
2098
2099
2099
2100 @predicate(b'_rev(number)', safe=True)
2100 @predicate(b'_rev(number)', safe=True)
2101 def _rev(repo, subset, x):
2101 def _rev(repo, subset, x):
2102 # internal version of "rev(x)" that raise error if "x" is invalid
2102 # internal version of "rev(x)" that raise error if "x" is invalid
2103 # i18n: "rev" is a keyword
2103 # i18n: "rev" is a keyword
2104 l = getargs(x, 1, 1, _(b"rev requires one argument"))
2104 l = getargs(x, 1, 1, _(b"rev requires one argument"))
2105 try:
2105 try:
2106 # i18n: "rev" is a keyword
2106 # i18n: "rev" is a keyword
2107 l = int(getstring(l[0], _(b"rev requires a number")))
2107 l = int(getstring(l[0], _(b"rev requires a number")))
2108 except (TypeError, ValueError):
2108 except (TypeError, ValueError):
2109 # i18n: "rev" is a keyword
2109 # i18n: "rev" is a keyword
2110 raise error.ParseError(_(b"rev expects a number"))
2110 raise error.ParseError(_(b"rev expects a number"))
2111 repo.changelog.node(l) # check that the rev exists
2111 if l not in _virtualrevs:
2112 repo.changelog.node(l) # check that the rev exists
2112 return subset & baseset([l])
2113 return subset & baseset([l])
2113
2114
2114
2115
2115 @predicate(b'revset(set)', safe=True, takeorder=True)
2116 @predicate(b'revset(set)', safe=True, takeorder=True)
2116 def revsetpredicate(repo, subset, x, order):
2117 def revsetpredicate(repo, subset, x, order):
2117 """Strictly interpret the content as a revset.
2118 """Strictly interpret the content as a revset.
2118
2119
2119 The content of this special predicate will be strictly interpreted as a
2120 The content of this special predicate will be strictly interpreted as a
2120 revset. For example, ``revset(id(0))`` will be interpreted as "id(0)"
2121 revset. For example, ``revset(id(0))`` will be interpreted as "id(0)"
2121 without possible ambiguity with a "id(0)" bookmark or tag.
2122 without possible ambiguity with a "id(0)" bookmark or tag.
2122 """
2123 """
2123 return getset(repo, subset, x, order)
2124 return getset(repo, subset, x, order)
2124
2125
2125
2126
2126 @predicate(b'matching(revision [, field])', safe=True)
2127 @predicate(b'matching(revision [, field])', safe=True)
2127 def matching(repo, subset, x):
2128 def matching(repo, subset, x):
2128 """Changesets in which a given set of fields match the set of fields in the
2129 """Changesets in which a given set of fields match the set of fields in the
2129 selected revision or set.
2130 selected revision or set.
2130
2131
2131 To match more than one field pass the list of fields to match separated
2132 To match more than one field pass the list of fields to match separated
2132 by spaces (e.g. ``author description``).
2133 by spaces (e.g. ``author description``).
2133
2134
2134 Valid fields are most regular revision fields and some special fields.
2135 Valid fields are most regular revision fields and some special fields.
2135
2136
2136 Regular revision fields are ``description``, ``author``, ``branch``,
2137 Regular revision fields are ``description``, ``author``, ``branch``,
2137 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
2138 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
2138 and ``diff``.
2139 and ``diff``.
2139 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
2140 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
2140 contents of the revision. Two revisions matching their ``diff`` will
2141 contents of the revision. Two revisions matching their ``diff`` will
2141 also match their ``files``.
2142 also match their ``files``.
2142
2143
2143 Special fields are ``summary`` and ``metadata``:
2144 Special fields are ``summary`` and ``metadata``:
2144 ``summary`` matches the first line of the description.
2145 ``summary`` matches the first line of the description.
2145 ``metadata`` is equivalent to matching ``description user date``
2146 ``metadata`` is equivalent to matching ``description user date``
2146 (i.e. it matches the main metadata fields).
2147 (i.e. it matches the main metadata fields).
2147
2148
2148 ``metadata`` is the default field which is used when no fields are
2149 ``metadata`` is the default field which is used when no fields are
2149 specified. You can match more than one field at a time.
2150 specified. You can match more than one field at a time.
2150 """
2151 """
2151 # i18n: "matching" is a keyword
2152 # i18n: "matching" is a keyword
2152 l = getargs(x, 1, 2, _(b"matching takes 1 or 2 arguments"))
2153 l = getargs(x, 1, 2, _(b"matching takes 1 or 2 arguments"))
2153
2154
2154 revs = getset(repo, fullreposet(repo), l[0])
2155 revs = getset(repo, fullreposet(repo), l[0])
2155
2156
2156 fieldlist = [b'metadata']
2157 fieldlist = [b'metadata']
2157 if len(l) > 1:
2158 if len(l) > 1:
2158 fieldlist = getstring(
2159 fieldlist = getstring(
2159 l[1],
2160 l[1],
2160 # i18n: "matching" is a keyword
2161 # i18n: "matching" is a keyword
2161 _(b"matching requires a string as its second argument"),
2162 _(b"matching requires a string as its second argument"),
2162 ).split()
2163 ).split()
2163
2164
2164 # Make sure that there are no repeated fields,
2165 # Make sure that there are no repeated fields,
2165 # expand the 'special' 'metadata' field type
2166 # expand the 'special' 'metadata' field type
2166 # and check the 'files' whenever we check the 'diff'
2167 # and check the 'files' whenever we check the 'diff'
2167 fields = []
2168 fields = []
2168 for field in fieldlist:
2169 for field in fieldlist:
2169 if field == b'metadata':
2170 if field == b'metadata':
2170 fields += [b'user', b'description', b'date']
2171 fields += [b'user', b'description', b'date']
2171 elif field == b'diff':
2172 elif field == b'diff':
2172 # a revision matching the diff must also match the files
2173 # a revision matching the diff must also match the files
2173 # since matching the diff is very costly, make sure to
2174 # since matching the diff is very costly, make sure to
2174 # also match the files first
2175 # also match the files first
2175 fields += [b'files', b'diff']
2176 fields += [b'files', b'diff']
2176 else:
2177 else:
2177 if field == b'author':
2178 if field == b'author':
2178 field = b'user'
2179 field = b'user'
2179 fields.append(field)
2180 fields.append(field)
2180 fields = set(fields)
2181 fields = set(fields)
2181 if b'summary' in fields and b'description' in fields:
2182 if b'summary' in fields and b'description' in fields:
2182 # If a revision matches its description it also matches its summary
2183 # If a revision matches its description it also matches its summary
2183 fields.discard(b'summary')
2184 fields.discard(b'summary')
2184
2185
2185 # We may want to match more than one field
2186 # We may want to match more than one field
2186 # Not all fields take the same amount of time to be matched
2187 # Not all fields take the same amount of time to be matched
2187 # Sort the selected fields in order of increasing matching cost
2188 # Sort the selected fields in order of increasing matching cost
2188 fieldorder = [
2189 fieldorder = [
2189 b'phase',
2190 b'phase',
2190 b'parents',
2191 b'parents',
2191 b'user',
2192 b'user',
2192 b'date',
2193 b'date',
2193 b'branch',
2194 b'branch',
2194 b'summary',
2195 b'summary',
2195 b'files',
2196 b'files',
2196 b'description',
2197 b'description',
2197 b'substate',
2198 b'substate',
2198 b'diff',
2199 b'diff',
2199 ]
2200 ]
2200
2201
2201 def fieldkeyfunc(f):
2202 def fieldkeyfunc(f):
2202 try:
2203 try:
2203 return fieldorder.index(f)
2204 return fieldorder.index(f)
2204 except ValueError:
2205 except ValueError:
2205 # assume an unknown field is very costly
2206 # assume an unknown field is very costly
2206 return len(fieldorder)
2207 return len(fieldorder)
2207
2208
2208 fields = list(fields)
2209 fields = list(fields)
2209 fields.sort(key=fieldkeyfunc)
2210 fields.sort(key=fieldkeyfunc)
2210
2211
2211 # Each field will be matched with its own "getfield" function
2212 # Each field will be matched with its own "getfield" function
2212 # which will be added to the getfieldfuncs array of functions
2213 # which will be added to the getfieldfuncs array of functions
2213 getfieldfuncs = []
2214 getfieldfuncs = []
2214 _funcs = {
2215 _funcs = {
2215 b'user': lambda r: repo[r].user(),
2216 b'user': lambda r: repo[r].user(),
2216 b'branch': lambda r: repo[r].branch(),
2217 b'branch': lambda r: repo[r].branch(),
2217 b'date': lambda r: repo[r].date(),
2218 b'date': lambda r: repo[r].date(),
2218 b'description': lambda r: repo[r].description(),
2219 b'description': lambda r: repo[r].description(),
2219 b'files': lambda r: repo[r].files(),
2220 b'files': lambda r: repo[r].files(),
2220 b'parents': lambda r: repo[r].parents(),
2221 b'parents': lambda r: repo[r].parents(),
2221 b'phase': lambda r: repo[r].phase(),
2222 b'phase': lambda r: repo[r].phase(),
2222 b'substate': lambda r: repo[r].substate,
2223 b'substate': lambda r: repo[r].substate,
2223 b'summary': lambda r: repo[r].description().splitlines()[0],
2224 b'summary': lambda r: repo[r].description().splitlines()[0],
2224 b'diff': lambda r: list(
2225 b'diff': lambda r: list(
2225 repo[r].diff(opts=diffutil.diffallopts(repo.ui, {b'git': True}))
2226 repo[r].diff(opts=diffutil.diffallopts(repo.ui, {b'git': True}))
2226 ),
2227 ),
2227 }
2228 }
2228 for info in fields:
2229 for info in fields:
2229 getfield = _funcs.get(info, None)
2230 getfield = _funcs.get(info, None)
2230 if getfield is None:
2231 if getfield is None:
2231 raise error.ParseError(
2232 raise error.ParseError(
2232 # i18n: "matching" is a keyword
2233 # i18n: "matching" is a keyword
2233 _(b"unexpected field name passed to matching: %s")
2234 _(b"unexpected field name passed to matching: %s")
2234 % info
2235 % info
2235 )
2236 )
2236 getfieldfuncs.append(getfield)
2237 getfieldfuncs.append(getfield)
2237 # convert the getfield array of functions into a "getinfo" function
2238 # convert the getfield array of functions into a "getinfo" function
2238 # which returns an array of field values (or a single value if there
2239 # which returns an array of field values (or a single value if there
2239 # is only one field to match)
2240 # is only one field to match)
2240 getinfo = lambda r: [f(r) for f in getfieldfuncs]
2241 getinfo = lambda r: [f(r) for f in getfieldfuncs]
2241
2242
2242 def matches(x):
2243 def matches(x):
2243 for rev in revs:
2244 for rev in revs:
2244 target = getinfo(rev)
2245 target = getinfo(rev)
2245 match = True
2246 match = True
2246 for n, f in enumerate(getfieldfuncs):
2247 for n, f in enumerate(getfieldfuncs):
2247 if target[n] != f(x):
2248 if target[n] != f(x):
2248 match = False
2249 match = False
2249 if match:
2250 if match:
2250 return True
2251 return True
2251 return False
2252 return False
2252
2253
2253 return subset.filter(matches, condrepr=(b'<matching%r %r>', fields, revs))
2254 return subset.filter(matches, condrepr=(b'<matching%r %r>', fields, revs))
2254
2255
2255
2256
2256 @predicate(b'reverse(set)', safe=True, takeorder=True, weight=0)
2257 @predicate(b'reverse(set)', safe=True, takeorder=True, weight=0)
2257 def reverse(repo, subset, x, order):
2258 def reverse(repo, subset, x, order):
2258 """Reverse order of set.
2259 """Reverse order of set.
2259 """
2260 """
2260 l = getset(repo, subset, x, order)
2261 l = getset(repo, subset, x, order)
2261 if order == defineorder:
2262 if order == defineorder:
2262 l.reverse()
2263 l.reverse()
2263 return l
2264 return l
2264
2265
2265
2266
2266 @predicate(b'roots(set)', safe=True)
2267 @predicate(b'roots(set)', safe=True)
2267 def roots(repo, subset, x):
2268 def roots(repo, subset, x):
2268 """Changesets in set with no parent changeset in set.
2269 """Changesets in set with no parent changeset in set.
2269 """
2270 """
2270 s = getset(repo, fullreposet(repo), x)
2271 s = getset(repo, fullreposet(repo), x)
2271 parents = repo.changelog.parentrevs
2272 parents = repo.changelog.parentrevs
2272
2273
2273 def filter(r):
2274 def filter(r):
2274 for p in parents(r):
2275 for p in parents(r):
2275 if 0 <= p and p in s:
2276 if 0 <= p and p in s:
2276 return False
2277 return False
2277 return True
2278 return True
2278
2279
2279 return subset & s.filter(filter, condrepr=b'<roots>')
2280 return subset & s.filter(filter, condrepr=b'<roots>')
2280
2281
2281
2282
2282 _sortkeyfuncs = {
2283 _sortkeyfuncs = {
2283 b'rev': lambda c: c.rev(),
2284 b'rev': lambda c: c.rev(),
2284 b'branch': lambda c: c.branch(),
2285 b'branch': lambda c: c.branch(),
2285 b'desc': lambda c: c.description(),
2286 b'desc': lambda c: c.description(),
2286 b'user': lambda c: c.user(),
2287 b'user': lambda c: c.user(),
2287 b'author': lambda c: c.user(),
2288 b'author': lambda c: c.user(),
2288 b'date': lambda c: c.date()[0],
2289 b'date': lambda c: c.date()[0],
2289 }
2290 }
2290
2291
2291
2292
2292 def _getsortargs(x):
2293 def _getsortargs(x):
2293 """Parse sort options into (set, [(key, reverse)], opts)"""
2294 """Parse sort options into (set, [(key, reverse)], opts)"""
2294 args = getargsdict(x, b'sort', b'set keys topo.firstbranch')
2295 args = getargsdict(x, b'sort', b'set keys topo.firstbranch')
2295 if b'set' not in args:
2296 if b'set' not in args:
2296 # i18n: "sort" is a keyword
2297 # i18n: "sort" is a keyword
2297 raise error.ParseError(_(b'sort requires one or two arguments'))
2298 raise error.ParseError(_(b'sort requires one or two arguments'))
2298 keys = b"rev"
2299 keys = b"rev"
2299 if b'keys' in args:
2300 if b'keys' in args:
2300 # i18n: "sort" is a keyword
2301 # i18n: "sort" is a keyword
2301 keys = getstring(args[b'keys'], _(b"sort spec must be a string"))
2302 keys = getstring(args[b'keys'], _(b"sort spec must be a string"))
2302
2303
2303 keyflags = []
2304 keyflags = []
2304 for k in keys.split():
2305 for k in keys.split():
2305 fk = k
2306 fk = k
2306 reverse = k.startswith(b'-')
2307 reverse = k.startswith(b'-')
2307 if reverse:
2308 if reverse:
2308 k = k[1:]
2309 k = k[1:]
2309 if k not in _sortkeyfuncs and k != b'topo':
2310 if k not in _sortkeyfuncs and k != b'topo':
2310 raise error.ParseError(
2311 raise error.ParseError(
2311 _(b"unknown sort key %r") % pycompat.bytestr(fk)
2312 _(b"unknown sort key %r") % pycompat.bytestr(fk)
2312 )
2313 )
2313 keyflags.append((k, reverse))
2314 keyflags.append((k, reverse))
2314
2315
2315 if len(keyflags) > 1 and any(k == b'topo' for k, reverse in keyflags):
2316 if len(keyflags) > 1 and any(k == b'topo' for k, reverse in keyflags):
2316 # i18n: "topo" is a keyword
2317 # i18n: "topo" is a keyword
2317 raise error.ParseError(
2318 raise error.ParseError(
2318 _(b'topo sort order cannot be combined with other sort keys')
2319 _(b'topo sort order cannot be combined with other sort keys')
2319 )
2320 )
2320
2321
2321 opts = {}
2322 opts = {}
2322 if b'topo.firstbranch' in args:
2323 if b'topo.firstbranch' in args:
2323 if any(k == b'topo' for k, reverse in keyflags):
2324 if any(k == b'topo' for k, reverse in keyflags):
2324 opts[b'topo.firstbranch'] = args[b'topo.firstbranch']
2325 opts[b'topo.firstbranch'] = args[b'topo.firstbranch']
2325 else:
2326 else:
2326 # i18n: "topo" and "topo.firstbranch" are keywords
2327 # i18n: "topo" and "topo.firstbranch" are keywords
2327 raise error.ParseError(
2328 raise error.ParseError(
2328 _(
2329 _(
2329 b'topo.firstbranch can only be used '
2330 b'topo.firstbranch can only be used '
2330 b'when using the topo sort key'
2331 b'when using the topo sort key'
2331 )
2332 )
2332 )
2333 )
2333
2334
2334 return args[b'set'], keyflags, opts
2335 return args[b'set'], keyflags, opts
2335
2336
2336
2337
2337 @predicate(
2338 @predicate(
2338 b'sort(set[, [-]key... [, ...]])', safe=True, takeorder=True, weight=10
2339 b'sort(set[, [-]key... [, ...]])', safe=True, takeorder=True, weight=10
2339 )
2340 )
2340 def sort(repo, subset, x, order):
2341 def sort(repo, subset, x, order):
2341 """Sort set by keys. The default sort order is ascending, specify a key
2342 """Sort set by keys. The default sort order is ascending, specify a key
2342 as ``-key`` to sort in descending order.
2343 as ``-key`` to sort in descending order.
2343
2344
2344 The keys can be:
2345 The keys can be:
2345
2346
2346 - ``rev`` for the revision number,
2347 - ``rev`` for the revision number,
2347 - ``branch`` for the branch name,
2348 - ``branch`` for the branch name,
2348 - ``desc`` for the commit message (description),
2349 - ``desc`` for the commit message (description),
2349 - ``user`` for user name (``author`` can be used as an alias),
2350 - ``user`` for user name (``author`` can be used as an alias),
2350 - ``date`` for the commit date
2351 - ``date`` for the commit date
2351 - ``topo`` for a reverse topographical sort
2352 - ``topo`` for a reverse topographical sort
2352
2353
2353 The ``topo`` sort order cannot be combined with other sort keys. This sort
2354 The ``topo`` sort order cannot be combined with other sort keys. This sort
2354 takes one optional argument, ``topo.firstbranch``, which takes a revset that
2355 takes one optional argument, ``topo.firstbranch``, which takes a revset that
2355 specifies what topographical branches to prioritize in the sort.
2356 specifies what topographical branches to prioritize in the sort.
2356
2357
2357 """
2358 """
2358 s, keyflags, opts = _getsortargs(x)
2359 s, keyflags, opts = _getsortargs(x)
2359 revs = getset(repo, subset, s, order)
2360 revs = getset(repo, subset, s, order)
2360
2361
2361 if not keyflags or order != defineorder:
2362 if not keyflags or order != defineorder:
2362 return revs
2363 return revs
2363 if len(keyflags) == 1 and keyflags[0][0] == b"rev":
2364 if len(keyflags) == 1 and keyflags[0][0] == b"rev":
2364 revs.sort(reverse=keyflags[0][1])
2365 revs.sort(reverse=keyflags[0][1])
2365 return revs
2366 return revs
2366 elif keyflags[0][0] == b"topo":
2367 elif keyflags[0][0] == b"topo":
2367 firstbranch = ()
2368 firstbranch = ()
2368 if b'topo.firstbranch' in opts:
2369 if b'topo.firstbranch' in opts:
2369 firstbranch = getset(repo, subset, opts[b'topo.firstbranch'])
2370 firstbranch = getset(repo, subset, opts[b'topo.firstbranch'])
2370 revs = baseset(
2371 revs = baseset(
2371 dagop.toposort(revs, repo.changelog.parentrevs, firstbranch),
2372 dagop.toposort(revs, repo.changelog.parentrevs, firstbranch),
2372 istopo=True,
2373 istopo=True,
2373 )
2374 )
2374 if keyflags[0][1]:
2375 if keyflags[0][1]:
2375 revs.reverse()
2376 revs.reverse()
2376 return revs
2377 return revs
2377
2378
2378 # sort() is guaranteed to be stable
2379 # sort() is guaranteed to be stable
2379 ctxs = [repo[r] for r in revs]
2380 ctxs = [repo[r] for r in revs]
2380 for k, reverse in reversed(keyflags):
2381 for k, reverse in reversed(keyflags):
2381 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
2382 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
2382 return baseset([c.rev() for c in ctxs])
2383 return baseset([c.rev() for c in ctxs])
2383
2384
2384
2385
2385 @predicate(b'subrepo([pattern])')
2386 @predicate(b'subrepo([pattern])')
2386 def subrepo(repo, subset, x):
2387 def subrepo(repo, subset, x):
2387 """Changesets that add, modify or remove the given subrepo. If no subrepo
2388 """Changesets that add, modify or remove the given subrepo. If no subrepo
2388 pattern is named, any subrepo changes are returned.
2389 pattern is named, any subrepo changes are returned.
2389 """
2390 """
2390 # i18n: "subrepo" is a keyword
2391 # i18n: "subrepo" is a keyword
2391 args = getargs(x, 0, 1, _(b'subrepo takes at most one argument'))
2392 args = getargs(x, 0, 1, _(b'subrepo takes at most one argument'))
2392 pat = None
2393 pat = None
2393 if len(args) != 0:
2394 if len(args) != 0:
2394 pat = getstring(args[0], _(b"subrepo requires a pattern"))
2395 pat = getstring(args[0], _(b"subrepo requires a pattern"))
2395
2396
2396 m = matchmod.exact([b'.hgsubstate'])
2397 m = matchmod.exact([b'.hgsubstate'])
2397
2398
2398 def submatches(names):
2399 def submatches(names):
2399 k, p, m = stringutil.stringmatcher(pat)
2400 k, p, m = stringutil.stringmatcher(pat)
2400 for name in names:
2401 for name in names:
2401 if m(name):
2402 if m(name):
2402 yield name
2403 yield name
2403
2404
2404 def matches(x):
2405 def matches(x):
2405 c = repo[x]
2406 c = repo[x]
2406 s = repo.status(c.p1().node(), c.node(), match=m)
2407 s = repo.status(c.p1().node(), c.node(), match=m)
2407
2408
2408 if pat is None:
2409 if pat is None:
2409 return s.added or s.modified or s.removed
2410 return s.added or s.modified or s.removed
2410
2411
2411 if s.added:
2412 if s.added:
2412 return any(submatches(c.substate.keys()))
2413 return any(submatches(c.substate.keys()))
2413
2414
2414 if s.modified:
2415 if s.modified:
2415 subs = set(c.p1().substate.keys())
2416 subs = set(c.p1().substate.keys())
2416 subs.update(c.substate.keys())
2417 subs.update(c.substate.keys())
2417
2418
2418 for path in submatches(subs):
2419 for path in submatches(subs):
2419 if c.p1().substate.get(path) != c.substate.get(path):
2420 if c.p1().substate.get(path) != c.substate.get(path):
2420 return True
2421 return True
2421
2422
2422 if s.removed:
2423 if s.removed:
2423 return any(submatches(c.p1().substate.keys()))
2424 return any(submatches(c.p1().substate.keys()))
2424
2425
2425 return False
2426 return False
2426
2427
2427 return subset.filter(matches, condrepr=(b'<subrepo %r>', pat))
2428 return subset.filter(matches, condrepr=(b'<subrepo %r>', pat))
2428
2429
2429
2430
2430 def _mapbynodefunc(repo, s, f):
2431 def _mapbynodefunc(repo, s, f):
2431 """(repo, smartset, [node] -> [node]) -> smartset
2432 """(repo, smartset, [node] -> [node]) -> smartset
2432
2433
2433 Helper method to map a smartset to another smartset given a function only
2434 Helper method to map a smartset to another smartset given a function only
2434 talking about nodes. Handles converting between rev numbers and nodes, and
2435 talking about nodes. Handles converting between rev numbers and nodes, and
2435 filtering.
2436 filtering.
2436 """
2437 """
2437 cl = repo.unfiltered().changelog
2438 cl = repo.unfiltered().changelog
2438 torev = cl.index.get_rev
2439 torev = cl.index.get_rev
2439 tonode = cl.node
2440 tonode = cl.node
2440 result = {torev(n) for n in f(tonode(r) for r in s)}
2441 result = {torev(n) for n in f(tonode(r) for r in s)}
2441 result.discard(None)
2442 result.discard(None)
2442 return smartset.baseset(result - repo.changelog.filteredrevs)
2443 return smartset.baseset(result - repo.changelog.filteredrevs)
2443
2444
2444
2445
2445 @predicate(b'successors(set)', safe=True)
2446 @predicate(b'successors(set)', safe=True)
2446 def successors(repo, subset, x):
2447 def successors(repo, subset, x):
2447 """All successors for set, including the given set themselves"""
2448 """All successors for set, including the given set themselves"""
2448 s = getset(repo, fullreposet(repo), x)
2449 s = getset(repo, fullreposet(repo), x)
2449 f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
2450 f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
2450 d = _mapbynodefunc(repo, s, f)
2451 d = _mapbynodefunc(repo, s, f)
2451 return subset & d
2452 return subset & d
2452
2453
2453
2454
2454 def _substringmatcher(pattern, casesensitive=True):
2455 def _substringmatcher(pattern, casesensitive=True):
2455 kind, pattern, matcher = stringutil.stringmatcher(
2456 kind, pattern, matcher = stringutil.stringmatcher(
2456 pattern, casesensitive=casesensitive
2457 pattern, casesensitive=casesensitive
2457 )
2458 )
2458 if kind == b'literal':
2459 if kind == b'literal':
2459 if not casesensitive:
2460 if not casesensitive:
2460 pattern = encoding.lower(pattern)
2461 pattern = encoding.lower(pattern)
2461 matcher = lambda s: pattern in encoding.lower(s)
2462 matcher = lambda s: pattern in encoding.lower(s)
2462 else:
2463 else:
2463 matcher = lambda s: pattern in s
2464 matcher = lambda s: pattern in s
2464 return kind, pattern, matcher
2465 return kind, pattern, matcher
2465
2466
2466
2467
2467 @predicate(b'tag([name])', safe=True)
2468 @predicate(b'tag([name])', safe=True)
2468 def tag(repo, subset, x):
2469 def tag(repo, subset, x):
2469 """The specified tag by name, or all tagged revisions if no name is given.
2470 """The specified tag by name, or all tagged revisions if no name is given.
2470
2471
2471 Pattern matching is supported for `name`. See
2472 Pattern matching is supported for `name`. See
2472 :hg:`help revisions.patterns`.
2473 :hg:`help revisions.patterns`.
2473 """
2474 """
2474 # i18n: "tag" is a keyword
2475 # i18n: "tag" is a keyword
2475 args = getargs(x, 0, 1, _(b"tag takes one or no arguments"))
2476 args = getargs(x, 0, 1, _(b"tag takes one or no arguments"))
2476 cl = repo.changelog
2477 cl = repo.changelog
2477 if args:
2478 if args:
2478 pattern = getstring(
2479 pattern = getstring(
2479 args[0],
2480 args[0],
2480 # i18n: "tag" is a keyword
2481 # i18n: "tag" is a keyword
2481 _(b'the argument to tag must be a string'),
2482 _(b'the argument to tag must be a string'),
2482 )
2483 )
2483 kind, pattern, matcher = stringutil.stringmatcher(pattern)
2484 kind, pattern, matcher = stringutil.stringmatcher(pattern)
2484 if kind == b'literal':
2485 if kind == b'literal':
2485 # avoid resolving all tags
2486 # avoid resolving all tags
2486 tn = repo._tagscache.tags.get(pattern, None)
2487 tn = repo._tagscache.tags.get(pattern, None)
2487 if tn is None:
2488 if tn is None:
2488 raise error.RepoLookupError(
2489 raise error.RepoLookupError(
2489 _(b"tag '%s' does not exist") % pattern
2490 _(b"tag '%s' does not exist") % pattern
2490 )
2491 )
2491 s = {repo[tn].rev()}
2492 s = {repo[tn].rev()}
2492 else:
2493 else:
2493 s = {cl.rev(n) for t, n in repo.tagslist() if matcher(t)}
2494 s = {cl.rev(n) for t, n in repo.tagslist() if matcher(t)}
2494 else:
2495 else:
2495 s = {cl.rev(n) for t, n in repo.tagslist() if t != b'tip'}
2496 s = {cl.rev(n) for t, n in repo.tagslist() if t != b'tip'}
2496 return subset & s
2497 return subset & s
2497
2498
2498
2499
2499 @predicate(b'tagged', safe=True)
2500 @predicate(b'tagged', safe=True)
2500 def tagged(repo, subset, x):
2501 def tagged(repo, subset, x):
2501 return tag(repo, subset, x)
2502 return tag(repo, subset, x)
2502
2503
2503
2504
2504 @predicate(b'orphan()', safe=True)
2505 @predicate(b'orphan()', safe=True)
2505 def orphan(repo, subset, x):
2506 def orphan(repo, subset, x):
2506 """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)
2507 """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)
2507 """
2508 """
2508 # i18n: "orphan" is a keyword
2509 # i18n: "orphan" is a keyword
2509 getargs(x, 0, 0, _(b"orphan takes no arguments"))
2510 getargs(x, 0, 0, _(b"orphan takes no arguments"))
2510 orphan = obsmod.getrevs(repo, b'orphan')
2511 orphan = obsmod.getrevs(repo, b'orphan')
2511 return subset & orphan
2512 return subset & orphan
2512
2513
2513
2514
2514 @predicate(b'user(string)', safe=True, weight=10)
2515 @predicate(b'user(string)', safe=True, weight=10)
2515 def user(repo, subset, x):
2516 def user(repo, subset, x):
2516 """User name contains string. The match is case-insensitive.
2517 """User name contains string. The match is case-insensitive.
2517
2518
2518 Pattern matching is supported for `string`. See
2519 Pattern matching is supported for `string`. See
2519 :hg:`help revisions.patterns`.
2520 :hg:`help revisions.patterns`.
2520 """
2521 """
2521 return author(repo, subset, x)
2522 return author(repo, subset, x)
2522
2523
2523
2524
2524 @predicate(b'wdir()', safe=True, weight=0)
2525 @predicate(b'wdir()', safe=True, weight=0)
2525 def wdir(repo, subset, x):
2526 def wdir(repo, subset, x):
2526 """Working directory. (EXPERIMENTAL)"""
2527 """Working directory. (EXPERIMENTAL)"""
2527 # i18n: "wdir" is a keyword
2528 # i18n: "wdir" is a keyword
2528 getargs(x, 0, 0, _(b"wdir takes no arguments"))
2529 getargs(x, 0, 0, _(b"wdir takes no arguments"))
2529 if node.wdirrev in subset or isinstance(subset, fullreposet):
2530 if node.wdirrev in subset or isinstance(subset, fullreposet):
2530 return baseset([node.wdirrev])
2531 return baseset([node.wdirrev])
2531 return baseset()
2532 return baseset()
2532
2533
2533
2534
2534 def _orderedlist(repo, subset, x):
2535 def _orderedlist(repo, subset, x):
2535 s = getstring(x, b"internal error")
2536 s = getstring(x, b"internal error")
2536 if not s:
2537 if not s:
2537 return baseset()
2538 return baseset()
2538 # remove duplicates here. it's difficult for caller to deduplicate sets
2539 # remove duplicates here. it's difficult for caller to deduplicate sets
2539 # because different symbols can point to the same rev.
2540 # because different symbols can point to the same rev.
2540 cl = repo.changelog
2541 cl = repo.changelog
2541 ls = []
2542 ls = []
2542 seen = set()
2543 seen = set()
2543 for t in s.split(b'\0'):
2544 for t in s.split(b'\0'):
2544 try:
2545 try:
2545 # fast path for integer revision
2546 # fast path for integer revision
2546 r = int(t)
2547 r = int(t)
2547 if (b'%d' % r) != t or r not in cl:
2548 if (b'%d' % r) != t or r not in cl:
2548 raise ValueError
2549 raise ValueError
2549 revs = [r]
2550 revs = [r]
2550 except ValueError:
2551 except ValueError:
2551 revs = stringset(repo, subset, t, defineorder)
2552 revs = stringset(repo, subset, t, defineorder)
2552
2553
2553 for r in revs:
2554 for r in revs:
2554 if r in seen:
2555 if r in seen:
2555 continue
2556 continue
2556 if (
2557 if (
2557 r in subset
2558 r in subset
2558 or r in _virtualrevs
2559 or r in _virtualrevs
2559 and isinstance(subset, fullreposet)
2560 and isinstance(subset, fullreposet)
2560 ):
2561 ):
2561 ls.append(r)
2562 ls.append(r)
2562 seen.add(r)
2563 seen.add(r)
2563 return baseset(ls)
2564 return baseset(ls)
2564
2565
2565
2566
2566 # for internal use
2567 # for internal use
2567 @predicate(b'_list', safe=True, takeorder=True)
2568 @predicate(b'_list', safe=True, takeorder=True)
2568 def _list(repo, subset, x, order):
2569 def _list(repo, subset, x, order):
2569 if order == followorder:
2570 if order == followorder:
2570 # slow path to take the subset order
2571 # slow path to take the subset order
2571 return subset & _orderedlist(repo, fullreposet(repo), x)
2572 return subset & _orderedlist(repo, fullreposet(repo), x)
2572 else:
2573 else:
2573 return _orderedlist(repo, subset, x)
2574 return _orderedlist(repo, subset, x)
2574
2575
2575
2576
2576 def _orderedintlist(repo, subset, x):
2577 def _orderedintlist(repo, subset, x):
2577 s = getstring(x, b"internal error")
2578 s = getstring(x, b"internal error")
2578 if not s:
2579 if not s:
2579 return baseset()
2580 return baseset()
2580 ls = [int(r) for r in s.split(b'\0')]
2581 ls = [int(r) for r in s.split(b'\0')]
2581 s = subset
2582 s = subset
2582 return baseset([r for r in ls if r in s])
2583 return baseset([r for r in ls if r in s])
2583
2584
2584
2585
2585 # for internal use
2586 # for internal use
2586 @predicate(b'_intlist', safe=True, takeorder=True, weight=0)
2587 @predicate(b'_intlist', safe=True, takeorder=True, weight=0)
2587 def _intlist(repo, subset, x, order):
2588 def _intlist(repo, subset, x, order):
2588 if order == followorder:
2589 if order == followorder:
2589 # slow path to take the subset order
2590 # slow path to take the subset order
2590 return subset & _orderedintlist(repo, fullreposet(repo), x)
2591 return subset & _orderedintlist(repo, fullreposet(repo), x)
2591 else:
2592 else:
2592 return _orderedintlist(repo, subset, x)
2593 return _orderedintlist(repo, subset, x)
2593
2594
2594
2595
2595 def _orderedhexlist(repo, subset, x):
2596 def _orderedhexlist(repo, subset, x):
2596 s = getstring(x, b"internal error")
2597 s = getstring(x, b"internal error")
2597 if not s:
2598 if not s:
2598 return baseset()
2599 return baseset()
2599 cl = repo.changelog
2600 cl = repo.changelog
2600 ls = [cl.rev(node.bin(r)) for r in s.split(b'\0')]
2601 ls = [cl.rev(node.bin(r)) for r in s.split(b'\0')]
2601 s = subset
2602 s = subset
2602 return baseset([r for r in ls if r in s])
2603 return baseset([r for r in ls if r in s])
2603
2604
2604
2605
2605 # for internal use
2606 # for internal use
2606 @predicate(b'_hexlist', safe=True, takeorder=True)
2607 @predicate(b'_hexlist', safe=True, takeorder=True)
2607 def _hexlist(repo, subset, x, order):
2608 def _hexlist(repo, subset, x, order):
2608 if order == followorder:
2609 if order == followorder:
2609 # slow path to take the subset order
2610 # slow path to take the subset order
2610 return subset & _orderedhexlist(repo, fullreposet(repo), x)
2611 return subset & _orderedhexlist(repo, fullreposet(repo), x)
2611 else:
2612 else:
2612 return _orderedhexlist(repo, subset, x)
2613 return _orderedhexlist(repo, subset, x)
2613
2614
2614
2615
2615 methods = {
2616 methods = {
2616 b"range": rangeset,
2617 b"range": rangeset,
2617 b"rangeall": rangeall,
2618 b"rangeall": rangeall,
2618 b"rangepre": rangepre,
2619 b"rangepre": rangepre,
2619 b"rangepost": rangepost,
2620 b"rangepost": rangepost,
2620 b"dagrange": dagrange,
2621 b"dagrange": dagrange,
2621 b"string": stringset,
2622 b"string": stringset,
2622 b"symbol": stringset,
2623 b"symbol": stringset,
2623 b"and": andset,
2624 b"and": andset,
2624 b"andsmally": andsmallyset,
2625 b"andsmally": andsmallyset,
2625 b"or": orset,
2626 b"or": orset,
2626 b"not": notset,
2627 b"not": notset,
2627 b"difference": differenceset,
2628 b"difference": differenceset,
2628 b"relation": relationset,
2629 b"relation": relationset,
2629 b"relsubscript": relsubscriptset,
2630 b"relsubscript": relsubscriptset,
2630 b"subscript": subscriptset,
2631 b"subscript": subscriptset,
2631 b"list": listset,
2632 b"list": listset,
2632 b"keyvalue": keyvaluepair,
2633 b"keyvalue": keyvaluepair,
2633 b"func": func,
2634 b"func": func,
2634 b"ancestor": ancestorspec,
2635 b"ancestor": ancestorspec,
2635 b"parent": parentspec,
2636 b"parent": parentspec,
2636 b"parentpost": parentpost,
2637 b"parentpost": parentpost,
2637 b"smartset": rawsmartset,
2638 b"smartset": rawsmartset,
2638 }
2639 }
2639
2640
2640 subscriptrelations = {
2641 subscriptrelations = {
2641 b"g": generationsrel,
2642 b"g": generationsrel,
2642 b"generations": generationsrel,
2643 b"generations": generationsrel,
2643 }
2644 }
2644
2645
2645
2646
2646 def lookupfn(repo):
2647 def lookupfn(repo):
2647 return lambda symbol: scmutil.isrevsymbol(repo, symbol)
2648 return lambda symbol: scmutil.isrevsymbol(repo, symbol)
2648
2649
2649
2650
2650 def match(ui, spec, lookup=None):
2651 def match(ui, spec, lookup=None):
2651 """Create a matcher for a single revision spec"""
2652 """Create a matcher for a single revision spec"""
2652 return matchany(ui, [spec], lookup=lookup)
2653 return matchany(ui, [spec], lookup=lookup)
2653
2654
2654
2655
2655 def matchany(ui, specs, lookup=None, localalias=None):
2656 def matchany(ui, specs, lookup=None, localalias=None):
2656 """Create a matcher that will include any revisions matching one of the
2657 """Create a matcher that will include any revisions matching one of the
2657 given specs
2658 given specs
2658
2659
2659 If lookup function is not None, the parser will first attempt to handle
2660 If lookup function is not None, the parser will first attempt to handle
2660 old-style ranges, which may contain operator characters.
2661 old-style ranges, which may contain operator characters.
2661
2662
2662 If localalias is not None, it is a dict {name: definitionstring}. It takes
2663 If localalias is not None, it is a dict {name: definitionstring}. It takes
2663 precedence over [revsetalias] config section.
2664 precedence over [revsetalias] config section.
2664 """
2665 """
2665 if not specs:
2666 if not specs:
2666
2667
2667 def mfunc(repo, subset=None):
2668 def mfunc(repo, subset=None):
2668 return baseset()
2669 return baseset()
2669
2670
2670 return mfunc
2671 return mfunc
2671 if not all(specs):
2672 if not all(specs):
2672 raise error.ParseError(_(b"empty query"))
2673 raise error.ParseError(_(b"empty query"))
2673 if len(specs) == 1:
2674 if len(specs) == 1:
2674 tree = revsetlang.parse(specs[0], lookup)
2675 tree = revsetlang.parse(specs[0], lookup)
2675 else:
2676 else:
2676 tree = (
2677 tree = (
2677 b'or',
2678 b'or',
2678 (b'list',) + tuple(revsetlang.parse(s, lookup) for s in specs),
2679 (b'list',) + tuple(revsetlang.parse(s, lookup) for s in specs),
2679 )
2680 )
2680
2681
2681 aliases = []
2682 aliases = []
2682 warn = None
2683 warn = None
2683 if ui:
2684 if ui:
2684 aliases.extend(ui.configitems(b'revsetalias'))
2685 aliases.extend(ui.configitems(b'revsetalias'))
2685 warn = ui.warn
2686 warn = ui.warn
2686 if localalias:
2687 if localalias:
2687 aliases.extend(localalias.items())
2688 aliases.extend(localalias.items())
2688 if aliases:
2689 if aliases:
2689 tree = revsetlang.expandaliases(tree, aliases, warn=warn)
2690 tree = revsetlang.expandaliases(tree, aliases, warn=warn)
2690 tree = revsetlang.foldconcat(tree)
2691 tree = revsetlang.foldconcat(tree)
2691 tree = revsetlang.analyze(tree)
2692 tree = revsetlang.analyze(tree)
2692 tree = revsetlang.optimize(tree)
2693 tree = revsetlang.optimize(tree)
2693 return makematcher(tree)
2694 return makematcher(tree)
2694
2695
2695
2696
2696 def makematcher(tree):
2697 def makematcher(tree):
2697 """Create a matcher from an evaluatable tree"""
2698 """Create a matcher from an evaluatable tree"""
2698
2699
2699 def mfunc(repo, subset=None, order=None):
2700 def mfunc(repo, subset=None, order=None):
2700 if order is None:
2701 if order is None:
2701 if subset is None:
2702 if subset is None:
2702 order = defineorder # 'x'
2703 order = defineorder # 'x'
2703 else:
2704 else:
2704 order = followorder # 'subset & x'
2705 order = followorder # 'subset & x'
2705 if subset is None:
2706 if subset is None:
2706 subset = fullreposet(repo)
2707 subset = fullreposet(repo)
2707 return getset(repo, subset, tree, order)
2708 return getset(repo, subset, tree, order)
2708
2709
2709 return mfunc
2710 return mfunc
2710
2711
2711
2712
2712 def loadpredicate(ui, extname, registrarobj):
2713 def loadpredicate(ui, extname, registrarobj):
2713 """Load revset predicates from specified registrarobj
2714 """Load revset predicates from specified registrarobj
2714 """
2715 """
2715 for name, func in pycompat.iteritems(registrarobj._table):
2716 for name, func in pycompat.iteritems(registrarobj._table):
2716 symbols[name] = func
2717 symbols[name] = func
2717 if func._safe:
2718 if func._safe:
2718 safesymbols.add(name)
2719 safesymbols.add(name)
2719
2720
2720
2721
2721 # load built-in predicates explicitly to setup safesymbols
2722 # load built-in predicates explicitly to setup safesymbols
2722 loadpredicate(None, None, predicate)
2723 loadpredicate(None, None, predicate)
2723
2724
2724 # tell hggettext to extract docstrings from these functions:
2725 # tell hggettext to extract docstrings from these functions:
2725 i18nfunctions = symbols.values()
2726 i18nfunctions = symbols.values()
@@ -1,1653 +1,1660 b''
1 Test template filters and functions
1 Test template filters and functions
2 ===================================
2 ===================================
3
3
4 $ hg init a
4 $ hg init a
5 $ cd a
5 $ cd a
6 $ echo a > a
6 $ echo a > a
7 $ hg add a
7 $ hg add a
8 $ echo line 1 > b
8 $ echo line 1 > b
9 $ echo line 2 >> b
9 $ echo line 2 >> b
10 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
10 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
11
11
12 $ hg add b
12 $ hg add b
13 $ echo other 1 > c
13 $ echo other 1 > c
14 $ echo other 2 >> c
14 $ echo other 2 >> c
15 $ echo >> c
15 $ echo >> c
16 $ echo other 3 >> c
16 $ echo other 3 >> c
17 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
17 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
18
18
19 $ hg add c
19 $ hg add c
20 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
20 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
21 $ echo c >> c
21 $ echo c >> c
22 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
22 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
23
23
24 $ echo foo > .hg/branch
24 $ echo foo > .hg/branch
25 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
25 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
26
26
27 $ hg co -q 3
27 $ hg co -q 3
28 $ echo other 4 >> d
28 $ echo other 4 >> d
29 $ hg add d
29 $ hg add d
30 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
30 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
31
31
32 $ hg merge -q foo
32 $ hg merge -q foo
33 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
33 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
34
34
35 Second branch starting at nullrev:
35 Second branch starting at nullrev:
36
36
37 $ hg update null
37 $ hg update null
38 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
38 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
39 $ echo second > second
39 $ echo second > second
40 $ hg add second
40 $ hg add second
41 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
41 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
42 created new head
42 created new head
43
43
44 $ echo third > third
44 $ echo third > third
45 $ hg add third
45 $ hg add third
46 $ hg mv second fourth
46 $ hg mv second fourth
47 $ hg commit -m third -d "2020-01-01 10:01"
47 $ hg commit -m third -d "2020-01-01 10:01"
48
48
49 $ hg phase -r 5 --public
49 $ hg phase -r 5 --public
50 $ hg phase -r 7 --secret --force
50 $ hg phase -r 7 --secret --force
51
51
52 Filters work:
52 Filters work:
53
53
54 $ hg log --template '{author|domain}\n'
54 $ hg log --template '{author|domain}\n'
55
55
56 hostname
56 hostname
57
57
58
58
59
59
60
60
61 place
61 place
62 place
62 place
63 hostname
63 hostname
64
64
65 $ hg log --template '{author|person}\n'
65 $ hg log --template '{author|person}\n'
66 test
66 test
67 User Name
67 User Name
68 person
68 person
69 person
69 person
70 person
70 person
71 person
71 person
72 other
72 other
73 A. N. Other
73 A. N. Other
74 User Name
74 User Name
75
75
76 $ hg log --template '{author|user}\n'
76 $ hg log --template '{author|user}\n'
77 test
77 test
78 user
78 user
79 person
79 person
80 person
80 person
81 person
81 person
82 person
82 person
83 other
83 other
84 other
84 other
85 user
85 user
86
86
87 $ hg log --template '{date|date}\n'
87 $ hg log --template '{date|date}\n'
88 Wed Jan 01 10:01:00 2020 +0000
88 Wed Jan 01 10:01:00 2020 +0000
89 Mon Jan 12 13:46:40 1970 +0000
89 Mon Jan 12 13:46:40 1970 +0000
90 Sun Jan 18 08:40:01 1970 +0000
90 Sun Jan 18 08:40:01 1970 +0000
91 Sun Jan 18 08:40:00 1970 +0000
91 Sun Jan 18 08:40:00 1970 +0000
92 Sat Jan 17 04:53:20 1970 +0000
92 Sat Jan 17 04:53:20 1970 +0000
93 Fri Jan 16 01:06:40 1970 +0000
93 Fri Jan 16 01:06:40 1970 +0000
94 Wed Jan 14 21:20:00 1970 +0000
94 Wed Jan 14 21:20:00 1970 +0000
95 Tue Jan 13 17:33:20 1970 +0000
95 Tue Jan 13 17:33:20 1970 +0000
96 Mon Jan 12 13:46:40 1970 +0000
96 Mon Jan 12 13:46:40 1970 +0000
97
97
98 $ hg log --template '{date|isodate}\n'
98 $ hg log --template '{date|isodate}\n'
99 2020-01-01 10:01 +0000
99 2020-01-01 10:01 +0000
100 1970-01-12 13:46 +0000
100 1970-01-12 13:46 +0000
101 1970-01-18 08:40 +0000
101 1970-01-18 08:40 +0000
102 1970-01-18 08:40 +0000
102 1970-01-18 08:40 +0000
103 1970-01-17 04:53 +0000
103 1970-01-17 04:53 +0000
104 1970-01-16 01:06 +0000
104 1970-01-16 01:06 +0000
105 1970-01-14 21:20 +0000
105 1970-01-14 21:20 +0000
106 1970-01-13 17:33 +0000
106 1970-01-13 17:33 +0000
107 1970-01-12 13:46 +0000
107 1970-01-12 13:46 +0000
108
108
109 $ hg log --template '{date|isodatesec}\n'
109 $ hg log --template '{date|isodatesec}\n'
110 2020-01-01 10:01:00 +0000
110 2020-01-01 10:01:00 +0000
111 1970-01-12 13:46:40 +0000
111 1970-01-12 13:46:40 +0000
112 1970-01-18 08:40:01 +0000
112 1970-01-18 08:40:01 +0000
113 1970-01-18 08:40:00 +0000
113 1970-01-18 08:40:00 +0000
114 1970-01-17 04:53:20 +0000
114 1970-01-17 04:53:20 +0000
115 1970-01-16 01:06:40 +0000
115 1970-01-16 01:06:40 +0000
116 1970-01-14 21:20:00 +0000
116 1970-01-14 21:20:00 +0000
117 1970-01-13 17:33:20 +0000
117 1970-01-13 17:33:20 +0000
118 1970-01-12 13:46:40 +0000
118 1970-01-12 13:46:40 +0000
119
119
120 $ hg log --template '{date|rfc822date}\n'
120 $ hg log --template '{date|rfc822date}\n'
121 Wed, 01 Jan 2020 10:01:00 +0000
121 Wed, 01 Jan 2020 10:01:00 +0000
122 Mon, 12 Jan 1970 13:46:40 +0000
122 Mon, 12 Jan 1970 13:46:40 +0000
123 Sun, 18 Jan 1970 08:40:01 +0000
123 Sun, 18 Jan 1970 08:40:01 +0000
124 Sun, 18 Jan 1970 08:40:00 +0000
124 Sun, 18 Jan 1970 08:40:00 +0000
125 Sat, 17 Jan 1970 04:53:20 +0000
125 Sat, 17 Jan 1970 04:53:20 +0000
126 Fri, 16 Jan 1970 01:06:40 +0000
126 Fri, 16 Jan 1970 01:06:40 +0000
127 Wed, 14 Jan 1970 21:20:00 +0000
127 Wed, 14 Jan 1970 21:20:00 +0000
128 Tue, 13 Jan 1970 17:33:20 +0000
128 Tue, 13 Jan 1970 17:33:20 +0000
129 Mon, 12 Jan 1970 13:46:40 +0000
129 Mon, 12 Jan 1970 13:46:40 +0000
130
130
131 $ hg log --template '{desc|firstline}\n'
131 $ hg log --template '{desc|firstline}\n'
132 third
132 third
133 second
133 second
134 merge
134 merge
135 new head
135 new head
136 new branch
136 new branch
137 no user, no domain
137 no user, no domain
138 no person
138 no person
139 other 1
139 other 1
140 line 1
140 line 1
141
141
142 $ hg log --template '{node|short}\n'
142 $ hg log --template '{node|short}\n'
143 95c24699272e
143 95c24699272e
144 29114dbae42b
144 29114dbae42b
145 d41e714fe50d
145 d41e714fe50d
146 13207e5a10d9
146 13207e5a10d9
147 bbe44766e73d
147 bbe44766e73d
148 10e46f2dcbf4
148 10e46f2dcbf4
149 97054abb4ab8
149 97054abb4ab8
150 b608e9d1a3f0
150 b608e9d1a3f0
151 1e4e1b8f71e0
151 1e4e1b8f71e0
152
152
153 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
153 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
154 <changeset author="test"/>
154 <changeset author="test"/>
155 <changeset author="User Name &lt;user@hostname&gt;"/>
155 <changeset author="User Name &lt;user@hostname&gt;"/>
156 <changeset author="person"/>
156 <changeset author="person"/>
157 <changeset author="person"/>
157 <changeset author="person"/>
158 <changeset author="person"/>
158 <changeset author="person"/>
159 <changeset author="person"/>
159 <changeset author="person"/>
160 <changeset author="other@place"/>
160 <changeset author="other@place"/>
161 <changeset author="A. N. Other &lt;other@place&gt;"/>
161 <changeset author="A. N. Other &lt;other@place&gt;"/>
162 <changeset author="User Name &lt;user@hostname&gt;"/>
162 <changeset author="User Name &lt;user@hostname&gt;"/>
163
163
164 $ hg log --template '{rev}: {children}\n'
164 $ hg log --template '{rev}: {children}\n'
165 8:
165 8:
166 7: 8:95c24699272e
166 7: 8:95c24699272e
167 6:
167 6:
168 5: 6:d41e714fe50d
168 5: 6:d41e714fe50d
169 4: 6:d41e714fe50d
169 4: 6:d41e714fe50d
170 3: 4:bbe44766e73d 5:13207e5a10d9
170 3: 4:bbe44766e73d 5:13207e5a10d9
171 2: 3:10e46f2dcbf4
171 2: 3:10e46f2dcbf4
172 1: 2:97054abb4ab8
172 1: 2:97054abb4ab8
173 0: 1:b608e9d1a3f0
173 0: 1:b608e9d1a3f0
174
174
175 Formatnode filter works:
175 Formatnode filter works:
176
176
177 $ hg -q log -r 0 --template '{node|formatnode}\n'
177 $ hg -q log -r 0 --template '{node|formatnode}\n'
178 1e4e1b8f71e0
178 1e4e1b8f71e0
179
179
180 $ hg log -r 0 --template '{node|formatnode}\n'
180 $ hg log -r 0 --template '{node|formatnode}\n'
181 1e4e1b8f71e0
181 1e4e1b8f71e0
182
182
183 $ hg -v log -r 0 --template '{node|formatnode}\n'
183 $ hg -v log -r 0 --template '{node|formatnode}\n'
184 1e4e1b8f71e0
184 1e4e1b8f71e0
185
185
186 $ hg --debug log -r 0 --template '{node|formatnode}\n'
186 $ hg --debug log -r 0 --template '{node|formatnode}\n'
187 1e4e1b8f71e05681d422154f5421e385fec3454f
187 1e4e1b8f71e05681d422154f5421e385fec3454f
188
188
189 Age filter:
189 Age filter:
190
190
191 $ hg init unstable-hash
191 $ hg init unstable-hash
192 $ cd unstable-hash
192 $ cd unstable-hash
193 $ hg log --template '{date|age}\n' > /dev/null || exit 1
193 $ hg log --template '{date|age}\n' > /dev/null || exit 1
194
194
195 >>> from __future__ import absolute_import
195 >>> from __future__ import absolute_import
196 >>> import datetime
196 >>> import datetime
197 >>> fp = open('a', 'wb')
197 >>> fp = open('a', 'wb')
198 >>> n = datetime.datetime.now() + datetime.timedelta(366 * 7)
198 >>> n = datetime.datetime.now() + datetime.timedelta(366 * 7)
199 >>> fp.write(b'%d-%d-%d 00:00' % (n.year, n.month, n.day)) and None
199 >>> fp.write(b'%d-%d-%d 00:00' % (n.year, n.month, n.day)) and None
200 >>> fp.close()
200 >>> fp.close()
201 $ hg add a
201 $ hg add a
202 $ hg commit -m future -d "`cat a`"
202 $ hg commit -m future -d "`cat a`"
203
203
204 $ hg log -l1 --template '{date|age}\n'
204 $ hg log -l1 --template '{date|age}\n'
205 7 years from now
205 7 years from now
206
206
207 $ cd ..
207 $ cd ..
208 $ rm -rf unstable-hash
208 $ rm -rf unstable-hash
209
209
210 Filename filters:
210 Filename filters:
211
211
212 $ hg debugtemplate '{"foo/bar"|basename}|{"foo/"|basename}|{"foo"|basename}|\n'
212 $ hg debugtemplate '{"foo/bar"|basename}|{"foo/"|basename}|{"foo"|basename}|\n'
213 bar||foo|
213 bar||foo|
214 $ hg debugtemplate '{"foo/bar"|dirname}|{"foo/"|dirname}|{"foo"|dirname}|\n'
214 $ hg debugtemplate '{"foo/bar"|dirname}|{"foo/"|dirname}|{"foo"|dirname}|\n'
215 foo|foo||
215 foo|foo||
216 $ hg debugtemplate '{"foo/bar"|stripdir}|{"foo/"|stripdir}|{"foo"|stripdir}|\n'
216 $ hg debugtemplate '{"foo/bar"|stripdir}|{"foo/"|stripdir}|{"foo"|stripdir}|\n'
217 foo|foo|foo|
217 foo|foo|foo|
218
218
219 commondir() filter:
219 commondir() filter:
220
220
221 $ hg debugtemplate '{""|splitlines|commondir}\n'
221 $ hg debugtemplate '{""|splitlines|commondir}\n'
222
222
223 $ hg debugtemplate '{"foo/bar\nfoo/baz\nfoo/foobar\n"|splitlines|commondir}\n'
223 $ hg debugtemplate '{"foo/bar\nfoo/baz\nfoo/foobar\n"|splitlines|commondir}\n'
224 foo
224 foo
225 $ hg debugtemplate '{"foo/bar\nfoo/bar\n"|splitlines|commondir}\n'
225 $ hg debugtemplate '{"foo/bar\nfoo/bar\n"|splitlines|commondir}\n'
226 foo
226 foo
227 $ hg debugtemplate '{"/foo/bar\n/foo/bar\n"|splitlines|commondir}\n'
227 $ hg debugtemplate '{"/foo/bar\n/foo/bar\n"|splitlines|commondir}\n'
228 foo
228 foo
229 $ hg debugtemplate '{"/foo\n/foo\n"|splitlines|commondir}\n'
229 $ hg debugtemplate '{"/foo\n/foo\n"|splitlines|commondir}\n'
230
230
231 $ hg debugtemplate '{"foo/bar\nbar/baz"|splitlines|commondir}\n'
231 $ hg debugtemplate '{"foo/bar\nbar/baz"|splitlines|commondir}\n'
232
232
233 $ hg debugtemplate '{"foo/bar\nbar/baz\nbar/foo\n"|splitlines|commondir}\n'
233 $ hg debugtemplate '{"foo/bar\nbar/baz\nbar/foo\n"|splitlines|commondir}\n'
234
234
235 $ hg debugtemplate '{"foo/../bar\nfoo/bar"|splitlines|commondir}\n'
235 $ hg debugtemplate '{"foo/../bar\nfoo/bar"|splitlines|commondir}\n'
236 foo
236 foo
237 $ hg debugtemplate '{"foo\n/foo"|splitlines|commondir}\n'
237 $ hg debugtemplate '{"foo\n/foo"|splitlines|commondir}\n'
238
238
239
239
240 $ hg log -r null -T '{rev|commondir}'
240 $ hg log -r null -T '{rev|commondir}'
241 hg: parse error: argument is not a list of text
241 hg: parse error: argument is not a list of text
242 (template filter 'commondir' is not compatible with keyword 'rev')
242 (template filter 'commondir' is not compatible with keyword 'rev')
243 [255]
243 [255]
244
244
245 Add a dummy commit to make up for the instability of the above:
245 Add a dummy commit to make up for the instability of the above:
246
246
247 $ echo a > a
247 $ echo a > a
248 $ hg add a
248 $ hg add a
249 $ hg ci -m future
249 $ hg ci -m future
250
250
251 Count filter:
251 Count filter:
252
252
253 $ hg log -l1 --template '{node|count} {node|short|count}\n'
253 $ hg log -l1 --template '{node|count} {node|short|count}\n'
254 40 12
254 40 12
255
255
256 $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
256 $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
257 0 1 4
257 0 1 4
258
258
259 $ hg log -G --template '{rev}: children: {children|count}, \
259 $ hg log -G --template '{rev}: children: {children|count}, \
260 > tags: {tags|count}, file_adds: {file_adds|count}, \
260 > tags: {tags|count}, file_adds: {file_adds|count}, \
261 > ancestors: {revset("ancestors(%s)", rev)|count}'
261 > ancestors: {revset("ancestors(%s)", rev)|count}'
262 @ 9: children: 0, tags: 1, file_adds: 1, ancestors: 3
262 @ 9: children: 0, tags: 1, file_adds: 1, ancestors: 3
263 |
263 |
264 o 8: children: 1, tags: 0, file_adds: 2, ancestors: 2
264 o 8: children: 1, tags: 0, file_adds: 2, ancestors: 2
265 |
265 |
266 o 7: children: 1, tags: 0, file_adds: 1, ancestors: 1
266 o 7: children: 1, tags: 0, file_adds: 1, ancestors: 1
267
267
268 o 6: children: 0, tags: 0, file_adds: 0, ancestors: 7
268 o 6: children: 0, tags: 0, file_adds: 0, ancestors: 7
269 |\
269 |\
270 | o 5: children: 1, tags: 0, file_adds: 1, ancestors: 5
270 | o 5: children: 1, tags: 0, file_adds: 1, ancestors: 5
271 | |
271 | |
272 o | 4: children: 1, tags: 0, file_adds: 0, ancestors: 5
272 o | 4: children: 1, tags: 0, file_adds: 0, ancestors: 5
273 |/
273 |/
274 o 3: children: 2, tags: 0, file_adds: 0, ancestors: 4
274 o 3: children: 2, tags: 0, file_adds: 0, ancestors: 4
275 |
275 |
276 o 2: children: 1, tags: 0, file_adds: 1, ancestors: 3
276 o 2: children: 1, tags: 0, file_adds: 1, ancestors: 3
277 |
277 |
278 o 1: children: 1, tags: 0, file_adds: 1, ancestors: 2
278 o 1: children: 1, tags: 0, file_adds: 1, ancestors: 2
279 |
279 |
280 o 0: children: 1, tags: 0, file_adds: 1, ancestors: 1
280 o 0: children: 1, tags: 0, file_adds: 1, ancestors: 1
281
281
282
282
283 $ hg log -l1 -T '{termwidth|count}\n'
283 $ hg log -l1 -T '{termwidth|count}\n'
284 hg: parse error: not countable
284 hg: parse error: not countable
285 (template filter 'count' is not compatible with keyword 'termwidth')
285 (template filter 'count' is not compatible with keyword 'termwidth')
286 [255]
286 [255]
287
287
288 Upper/lower filters:
288 Upper/lower filters:
289
289
290 $ hg log -r0 --template '{branch|upper}\n'
290 $ hg log -r0 --template '{branch|upper}\n'
291 DEFAULT
291 DEFAULT
292 $ hg log -r0 --template '{author|lower}\n'
292 $ hg log -r0 --template '{author|lower}\n'
293 user name <user@hostname>
293 user name <user@hostname>
294 $ hg log -r0 --template '{date|upper}\n'
294 $ hg log -r0 --template '{date|upper}\n'
295 1000000.00
295 1000000.00
296
296
297 Add a commit that does all possible modifications at once
297 Add a commit that does all possible modifications at once
298
298
299 $ echo modify >> third
299 $ echo modify >> third
300 $ touch b
300 $ touch b
301 $ hg add b
301 $ hg add b
302 $ hg mv fourth fifth
302 $ hg mv fourth fifth
303 $ hg rm a
303 $ hg rm a
304 $ hg ci -m "Modify, add, remove, rename"
304 $ hg ci -m "Modify, add, remove, rename"
305
305
306 Pass generator object created by template function to filter
306 Pass generator object created by template function to filter
307
307
308 $ hg log -l 1 --template '{if(author, author)|user}\n'
308 $ hg log -l 1 --template '{if(author, author)|user}\n'
309 test
309 test
310
310
311 Test diff function:
311 Test diff function:
312
312
313 $ hg diff -c 8
313 $ hg diff -c 8
314 diff -r 29114dbae42b -r 95c24699272e fourth
314 diff -r 29114dbae42b -r 95c24699272e fourth
315 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
315 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
316 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
316 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
317 @@ -0,0 +1,1 @@
317 @@ -0,0 +1,1 @@
318 +second
318 +second
319 diff -r 29114dbae42b -r 95c24699272e second
319 diff -r 29114dbae42b -r 95c24699272e second
320 --- a/second Mon Jan 12 13:46:40 1970 +0000
320 --- a/second Mon Jan 12 13:46:40 1970 +0000
321 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
321 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
322 @@ -1,1 +0,0 @@
322 @@ -1,1 +0,0 @@
323 -second
323 -second
324 diff -r 29114dbae42b -r 95c24699272e third
324 diff -r 29114dbae42b -r 95c24699272e third
325 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
325 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
326 +++ b/third Wed Jan 01 10:01:00 2020 +0000
326 +++ b/third Wed Jan 01 10:01:00 2020 +0000
327 @@ -0,0 +1,1 @@
327 @@ -0,0 +1,1 @@
328 +third
328 +third
329
329
330 $ hg log -r 8 -T "{diff()}"
330 $ hg log -r 8 -T "{diff()}"
331 diff -r 29114dbae42b -r 95c24699272e fourth
331 diff -r 29114dbae42b -r 95c24699272e fourth
332 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
332 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
333 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
333 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
334 @@ -0,0 +1,1 @@
334 @@ -0,0 +1,1 @@
335 +second
335 +second
336 diff -r 29114dbae42b -r 95c24699272e second
336 diff -r 29114dbae42b -r 95c24699272e second
337 --- a/second Mon Jan 12 13:46:40 1970 +0000
337 --- a/second Mon Jan 12 13:46:40 1970 +0000
338 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
338 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
339 @@ -1,1 +0,0 @@
339 @@ -1,1 +0,0 @@
340 -second
340 -second
341 diff -r 29114dbae42b -r 95c24699272e third
341 diff -r 29114dbae42b -r 95c24699272e third
342 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
342 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
343 +++ b/third Wed Jan 01 10:01:00 2020 +0000
343 +++ b/third Wed Jan 01 10:01:00 2020 +0000
344 @@ -0,0 +1,1 @@
344 @@ -0,0 +1,1 @@
345 +third
345 +third
346
346
347 $ hg log -r 8 -T "{diff('glob:f*')}"
347 $ hg log -r 8 -T "{diff('glob:f*')}"
348 diff -r 29114dbae42b -r 95c24699272e fourth
348 diff -r 29114dbae42b -r 95c24699272e fourth
349 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
349 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
350 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
350 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
351 @@ -0,0 +1,1 @@
351 @@ -0,0 +1,1 @@
352 +second
352 +second
353
353
354 $ hg log -r 8 -T "{diff('', 'glob:f*')}"
354 $ hg log -r 8 -T "{diff('', 'glob:f*')}"
355 diff -r 29114dbae42b -r 95c24699272e second
355 diff -r 29114dbae42b -r 95c24699272e second
356 --- a/second Mon Jan 12 13:46:40 1970 +0000
356 --- a/second Mon Jan 12 13:46:40 1970 +0000
357 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
357 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
358 @@ -1,1 +0,0 @@
358 @@ -1,1 +0,0 @@
359 -second
359 -second
360 diff -r 29114dbae42b -r 95c24699272e third
360 diff -r 29114dbae42b -r 95c24699272e third
361 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
361 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
362 +++ b/third Wed Jan 01 10:01:00 2020 +0000
362 +++ b/third Wed Jan 01 10:01:00 2020 +0000
363 @@ -0,0 +1,1 @@
363 @@ -0,0 +1,1 @@
364 +third
364 +third
365
365
366 $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
366 $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
367 diff -r 29114dbae42b -r 95c24699272e fourth
367 diff -r 29114dbae42b -r 95c24699272e fourth
368 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
368 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
369 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
369 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
370 @@ -0,0 +1,1 @@
370 @@ -0,0 +1,1 @@
371 +second
371 +second
372
372
373 $ hg --config diff.git=true log -r 8 -T "{diff()}"
373 $ hg --config diff.git=true log -r 8 -T "{diff()}"
374 diff --git a/second b/fourth
374 diff --git a/second b/fourth
375 rename from second
375 rename from second
376 rename to fourth
376 rename to fourth
377 diff --git a/third b/third
377 diff --git a/third b/third
378 new file mode 100644
378 new file mode 100644
379 --- /dev/null
379 --- /dev/null
380 +++ b/third
380 +++ b/third
381 @@ -0,0 +1,1 @@
381 @@ -0,0 +1,1 @@
382 +third
382 +third
383
383
384 $ cd ..
384 $ cd ..
385
385
386 latesttag() function:
386 latesttag() function:
387
387
388 $ hg init latesttag
388 $ hg init latesttag
389 $ cd latesttag
389 $ cd latesttag
390
390
391 $ echo a > file
391 $ echo a > file
392 $ hg ci -Am a -d '0 0'
392 $ hg ci -Am a -d '0 0'
393 adding file
393 adding file
394
394
395 $ echo b >> file
395 $ echo b >> file
396 $ hg ci -m b -d '1 0'
396 $ hg ci -m b -d '1 0'
397
397
398 $ echo c >> head1
398 $ echo c >> head1
399 $ hg ci -Am h1c -d '2 0'
399 $ hg ci -Am h1c -d '2 0'
400 adding head1
400 adding head1
401
401
402 $ hg update -q 1
402 $ hg update -q 1
403 $ echo d >> head2
403 $ echo d >> head2
404 $ hg ci -Am h2d -d '3 0'
404 $ hg ci -Am h2d -d '3 0'
405 adding head2
405 adding head2
406 created new head
406 created new head
407
407
408 $ echo e >> head2
408 $ echo e >> head2
409 $ hg ci -m h2e -d '4 0'
409 $ hg ci -m h2e -d '4 0'
410
410
411 $ hg merge -q
411 $ hg merge -q
412 $ hg ci -m merge -d '5 -3600'
412 $ hg ci -m merge -d '5 -3600'
413
413
414 $ hg tag -r 1 -m t1 -d '6 0' t1
414 $ hg tag -r 1 -m t1 -d '6 0' t1
415 $ hg tag -r 2 -m t2 -d '7 0' t2
415 $ hg tag -r 2 -m t2 -d '7 0' t2
416 $ hg tag -r 3 -m t3 -d '8 0' t3
416 $ hg tag -r 3 -m t3 -d '8 0' t3
417 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
417 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
418 $ hg tag -r 5 -m t5 -d '9 0' t5
418 $ hg tag -r 5 -m t5 -d '9 0' t5
419 $ hg tag -r 3 -m at3 -d '10 0' at3
419 $ hg tag -r 3 -m at3 -d '10 0' at3
420
420
421 $ hg log -G --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
421 $ hg log -G --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
422 @ 11: t3, C: 9, D: 8
422 @ 11: t3, C: 9, D: 8
423 |
423 |
424 o 10: t3, C: 8, D: 7
424 o 10: t3, C: 8, D: 7
425 |
425 |
426 o 9: t3, C: 7, D: 6
426 o 9: t3, C: 7, D: 6
427 |
427 |
428 o 8: t3, C: 6, D: 5
428 o 8: t3, C: 6, D: 5
429 |
429 |
430 o 7: t3, C: 5, D: 4
430 o 7: t3, C: 5, D: 4
431 |
431 |
432 o 6: t3, C: 4, D: 3
432 o 6: t3, C: 4, D: 3
433 |
433 |
434 o 5: t3, C: 3, D: 2
434 o 5: t3, C: 3, D: 2
435 |\
435 |\
436 | o 4: t3, C: 1, D: 1
436 | o 4: t3, C: 1, D: 1
437 | |
437 | |
438 | o 3: t3, C: 0, D: 0
438 | o 3: t3, C: 0, D: 0
439 | |
439 | |
440 o | 2: t1, C: 1, D: 1
440 o | 2: t1, C: 1, D: 1
441 |/
441 |/
442 o 1: t1, C: 0, D: 0
442 o 1: t1, C: 0, D: 0
443 |
443 |
444 o 0: null, C: 1, D: 1
444 o 0: null, C: 1, D: 1
445
445
446
446
447 $ cd ..
447 $ cd ..
448
448
449 Test filter() empty values:
449 Test filter() empty values:
450
450
451 $ hg log -R a -r 1 -T '{filter(desc|splitlines) % "{line}\n"}'
451 $ hg log -R a -r 1 -T '{filter(desc|splitlines) % "{line}\n"}'
452 other 1
452 other 1
453 other 2
453 other 2
454 other 3
454 other 3
455 $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1) % "{ifeq(key, "a", "{value}\n")}")}'
455 $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1) % "{ifeq(key, "a", "{value}\n")}")}'
456 0
456 0
457
457
458 0 should not be falsy
458 0 should not be falsy
459
459
460 $ hg log -R a -r 0 -T '{filter(revset("0:2"))}\n'
460 $ hg log -R a -r 0 -T '{filter(revset("0:2"))}\n'
461 0 1 2
461 0 1 2
462
462
463 Test filter() by expression:
463 Test filter() by expression:
464
464
465 $ hg log -R a -r 1 -T '{filter(desc|splitlines, ifcontains("1", line, "t"))}\n'
465 $ hg log -R a -r 1 -T '{filter(desc|splitlines, ifcontains("1", line, "t"))}\n'
466 other 1
466 other 1
467 $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1), ifeq(key, "b", "t"))}\n'
467 $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1), ifeq(key, "b", "t"))}\n'
468 b=1
468 b=1
469
469
470 Test filter() shouldn't crash:
470 Test filter() shouldn't crash:
471
471
472 $ hg log -R a -r 0 -T '{filter(extras)}\n'
472 $ hg log -R a -r 0 -T '{filter(extras)}\n'
473 branch=default
473 branch=default
474 $ hg log -R a -r 0 -T '{filter(files)}\n'
474 $ hg log -R a -r 0 -T '{filter(files)}\n'
475 a
475 a
476
476
477 Test filter() unsupported arguments:
477 Test filter() unsupported arguments:
478
478
479 $ hg log -R a -r 0 -T '{filter()}\n'
479 $ hg log -R a -r 0 -T '{filter()}\n'
480 hg: parse error: filter expects one or two arguments
480 hg: parse error: filter expects one or two arguments
481 [255]
481 [255]
482 $ hg log -R a -r 0 -T '{filter(date)}\n'
482 $ hg log -R a -r 0 -T '{filter(date)}\n'
483 hg: parse error: date is not iterable
483 hg: parse error: date is not iterable
484 [255]
484 [255]
485 $ hg log -R a -r 0 -T '{filter(rev)}\n'
485 $ hg log -R a -r 0 -T '{filter(rev)}\n'
486 hg: parse error: 0 is not iterable
486 hg: parse error: 0 is not iterable
487 [255]
487 [255]
488 $ hg log -R a -r 0 -T '{filter(desc|firstline)}\n'
488 $ hg log -R a -r 0 -T '{filter(desc|firstline)}\n'
489 hg: parse error: 'line 1' is not filterable
489 hg: parse error: 'line 1' is not filterable
490 [255]
490 [255]
491 $ hg log -R a -r 0 -T '{filter(manifest)}\n'
491 $ hg log -R a -r 0 -T '{filter(manifest)}\n'
492 hg: parse error: '0:a0c8bcbbb45c' is not filterable
492 hg: parse error: '0:a0c8bcbbb45c' is not filterable
493 [255]
493 [255]
494 $ hg log -R a -r 0 -T '{filter(succsandmarkers)}\n'
494 $ hg log -R a -r 0 -T '{filter(succsandmarkers)}\n'
495 hg: parse error: not filterable without template
495 hg: parse error: not filterable without template
496 [255]
496 [255]
497 $ hg log -R a -r 0 -T '{filter(desc|splitlines % "{line}", "")}\n'
497 $ hg log -R a -r 0 -T '{filter(desc|splitlines % "{line}", "")}\n'
498 hg: parse error: not filterable by expression
498 hg: parse error: not filterable by expression
499 [255]
499 [255]
500
500
501 Test manifest/get() can be join()-ed as string, though it's silly:
501 Test manifest/get() can be join()-ed as string, though it's silly:
502
502
503 $ hg log -R latesttag -r tip -T '{join(manifest, ".")}\n'
503 $ hg log -R latesttag -r tip -T '{join(manifest, ".")}\n'
504 1.1.:.2.b.c.6.e.9.0.0.6.c.e.2
504 1.1.:.2.b.c.6.e.9.0.0.6.c.e.2
505 $ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), ".")}\n'
505 $ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), ".")}\n'
506 d.e.f.a.u.l.t
506 d.e.f.a.u.l.t
507
507
508 Test join() over string
508 Test join() over string
509
509
510 $ hg log -R latesttag -r tip -T '{join(rev|stringify, ".")}\n'
510 $ hg log -R latesttag -r tip -T '{join(rev|stringify, ".")}\n'
511 1.1
511 1.1
512
512
513 Test join() over uniterable
513 Test join() over uniterable
514
514
515 $ hg log -R latesttag -r tip -T '{join(rev, "")}\n'
515 $ hg log -R latesttag -r tip -T '{join(rev, "")}\n'
516 hg: parse error: 11 is not iterable
516 hg: parse error: 11 is not iterable
517 [255]
517 [255]
518
518
519 Test min/max of integers
519 Test min/max of integers
520
520
521 $ hg log -R latesttag -l1 -T '{min(revset("9:10"))}\n'
521 $ hg log -R latesttag -l1 -T '{min(revset("9:10"))}\n'
522 9
522 9
523 $ hg log -R latesttag -l1 -T '{max(revset("9:10"))}\n'
523 $ hg log -R latesttag -l1 -T '{max(revset("9:10"))}\n'
524 10
524 10
525
525
526 Test min/max over map operation:
526 Test min/max over map operation:
527
527
528 $ hg log -R latesttag -r3 -T '{min(tags % "{tag}")}\n'
528 $ hg log -R latesttag -r3 -T '{min(tags % "{tag}")}\n'
529 at3
529 at3
530 $ hg log -R latesttag -r3 -T '{max(tags % "{tag}")}\n'
530 $ hg log -R latesttag -r3 -T '{max(tags % "{tag}")}\n'
531 t3
531 t3
532
532
533 Test min/max of strings:
533 Test min/max of strings:
534
534
535 $ hg log -R latesttag -l1 -T '{min(desc)}\n'
535 $ hg log -R latesttag -l1 -T '{min(desc)}\n'
536 3
536 3
537 $ hg log -R latesttag -l1 -T '{max(desc)}\n'
537 $ hg log -R latesttag -l1 -T '{max(desc)}\n'
538 t
538 t
539
539
540 Test min/max of non-iterable:
540 Test min/max of non-iterable:
541
541
542 $ hg debugtemplate '{min(1)}'
542 $ hg debugtemplate '{min(1)}'
543 hg: parse error: 1 is not iterable
543 hg: parse error: 1 is not iterable
544 (min first argument should be an iterable)
544 (min first argument should be an iterable)
545 [255]
545 [255]
546 $ hg debugtemplate '{max(2)}'
546 $ hg debugtemplate '{max(2)}'
547 hg: parse error: 2 is not iterable
547 hg: parse error: 2 is not iterable
548 (max first argument should be an iterable)
548 (max first argument should be an iterable)
549 [255]
549 [255]
550
550
551 $ hg log -R latesttag -l1 -T '{min(date)}'
551 $ hg log -R latesttag -l1 -T '{min(date)}'
552 hg: parse error: date is not iterable
552 hg: parse error: date is not iterable
553 (min first argument should be an iterable)
553 (min first argument should be an iterable)
554 [255]
554 [255]
555 $ hg log -R latesttag -l1 -T '{max(date)}'
555 $ hg log -R latesttag -l1 -T '{max(date)}'
556 hg: parse error: date is not iterable
556 hg: parse error: date is not iterable
557 (max first argument should be an iterable)
557 (max first argument should be an iterable)
558 [255]
558 [255]
559
559
560 Test min/max of empty sequence:
560 Test min/max of empty sequence:
561
561
562 $ hg debugtemplate '{min("")}'
562 $ hg debugtemplate '{min("")}'
563 hg: parse error: empty string
563 hg: parse error: empty string
564 (min first argument should be an iterable)
564 (min first argument should be an iterable)
565 [255]
565 [255]
566 $ hg debugtemplate '{max("")}'
566 $ hg debugtemplate '{max("")}'
567 hg: parse error: empty string
567 hg: parse error: empty string
568 (max first argument should be an iterable)
568 (max first argument should be an iterable)
569 [255]
569 [255]
570 $ hg debugtemplate '{min(dict())}'
570 $ hg debugtemplate '{min(dict())}'
571 hg: parse error: empty sequence
571 hg: parse error: empty sequence
572 (min first argument should be an iterable)
572 (min first argument should be an iterable)
573 [255]
573 [255]
574 $ hg debugtemplate '{max(dict())}'
574 $ hg debugtemplate '{max(dict())}'
575 hg: parse error: empty sequence
575 hg: parse error: empty sequence
576 (max first argument should be an iterable)
576 (max first argument should be an iterable)
577 [255]
577 [255]
578 $ hg debugtemplate '{min(dict() % "")}'
578 $ hg debugtemplate '{min(dict() % "")}'
579 hg: parse error: empty sequence
579 hg: parse error: empty sequence
580 (min first argument should be an iterable)
580 (min first argument should be an iterable)
581 [255]
581 [255]
582 $ hg debugtemplate '{max(dict() % "")}'
582 $ hg debugtemplate '{max(dict() % "")}'
583 hg: parse error: empty sequence
583 hg: parse error: empty sequence
584 (max first argument should be an iterable)
584 (max first argument should be an iterable)
585 [255]
585 [255]
586
586
587 Test min/max of if() result
587 Test min/max of if() result
588
588
589 $ cd latesttag
589 $ cd latesttag
590 $ hg log -l1 -T '{min(if(true, revset("9:10"), ""))}\n'
590 $ hg log -l1 -T '{min(if(true, revset("9:10"), ""))}\n'
591 9
591 9
592 $ hg log -l1 -T '{max(if(false, "", revset("9:10")))}\n'
592 $ hg log -l1 -T '{max(if(false, "", revset("9:10")))}\n'
593 10
593 10
594 $ hg log -l1 -T '{min(ifcontains("a", "aa", revset("9:10"), ""))}\n'
594 $ hg log -l1 -T '{min(ifcontains("a", "aa", revset("9:10"), ""))}\n'
595 9
595 9
596 $ hg log -l1 -T '{max(ifcontains("a", "bb", "", revset("9:10")))}\n'
596 $ hg log -l1 -T '{max(ifcontains("a", "bb", "", revset("9:10")))}\n'
597 10
597 10
598 $ hg log -l1 -T '{min(ifeq(0, 0, revset("9:10"), ""))}\n'
598 $ hg log -l1 -T '{min(ifeq(0, 0, revset("9:10"), ""))}\n'
599 9
599 9
600 $ hg log -l1 -T '{max(ifeq(0, 1, "", revset("9:10")))}\n'
600 $ hg log -l1 -T '{max(ifeq(0, 1, "", revset("9:10")))}\n'
601 10
601 10
602 $ cd ..
602 $ cd ..
603
603
604 Test laziness of if() then/else clause
604 Test laziness of if() then/else clause
605
605
606 $ hg debugtemplate '{count(0)}'
606 $ hg debugtemplate '{count(0)}'
607 hg: parse error: not countable
607 hg: parse error: not countable
608 (incompatible use of template filter 'count')
608 (incompatible use of template filter 'count')
609 [255]
609 [255]
610 $ hg debugtemplate '{if(true, "", count(0))}'
610 $ hg debugtemplate '{if(true, "", count(0))}'
611 $ hg debugtemplate '{if(false, count(0), "")}'
611 $ hg debugtemplate '{if(false, count(0), "")}'
612 $ hg debugtemplate '{ifcontains("a", "aa", "", count(0))}'
612 $ hg debugtemplate '{ifcontains("a", "aa", "", count(0))}'
613 $ hg debugtemplate '{ifcontains("a", "bb", count(0), "")}'
613 $ hg debugtemplate '{ifcontains("a", "bb", count(0), "")}'
614 $ hg debugtemplate '{ifeq(0, 0, "", count(0))}'
614 $ hg debugtemplate '{ifeq(0, 0, "", count(0))}'
615 $ hg debugtemplate '{ifeq(0, 1, count(0), "")}'
615 $ hg debugtemplate '{ifeq(0, 1, count(0), "")}'
616
616
617 Test search() function:
617 Test search() function:
618
618
619 $ hg log -R a -r2 -T '{desc}\n'
619 $ hg log -R a -r2 -T '{desc}\n'
620 no person
620 no person
621
621
622 $ hg log -R a -r2 -T '{search(r"p.*", desc)}\n'
622 $ hg log -R a -r2 -T '{search(r"p.*", desc)}\n'
623 person
623 person
624
624
625 as bool
625 as bool
626
626
627 $ hg log -R a -r2 -T '{if(search(r"p.*", desc), "", "not ")}found\n'
627 $ hg log -R a -r2 -T '{if(search(r"p.*", desc), "", "not ")}found\n'
628 found
628 found
629 $ hg log -R a -r2 -T '{if(search(r"q", desc), "", "not ")}found\n'
629 $ hg log -R a -r2 -T '{if(search(r"q", desc), "", "not ")}found\n'
630 not found
630 not found
631
631
632 match as json
632 match as json
633
633
634 $ hg log -R a -r2 -T '{search(r"(no) p.*", desc)|json}\n'
634 $ hg log -R a -r2 -T '{search(r"(no) p.*", desc)|json}\n'
635 {"0": "no person", "1": "no"}
635 {"0": "no person", "1": "no"}
636 $ hg log -R a -r2 -T '{search(r"q", desc)|json}\n'
636 $ hg log -R a -r2 -T '{search(r"q", desc)|json}\n'
637 null
637 null
638
638
639 group reference
639 group reference
640
640
641 $ hg log -R a -r2 -T '{search(r"(no) (p.*)", desc) % "{1|upper} {2|hex}"}\n'
641 $ hg log -R a -r2 -T '{search(r"(no) (p.*)", desc) % "{1|upper} {2|hex}"}\n'
642 NO 706572736f6e
642 NO 706572736f6e
643 $ hg log -R a -r2 -T '{search(r"(?P<foo>[a-z]*)", desc) % "{foo}"}\n'
643 $ hg log -R a -r2 -T '{search(r"(?P<foo>[a-z]*)", desc) % "{foo}"}\n'
644 no
644 no
645 $ hg log -R a -r2 -T '{search(r"(?P<foo>[a-z]*)", desc).foo}\n'
645 $ hg log -R a -r2 -T '{search(r"(?P<foo>[a-z]*)", desc).foo}\n'
646 no
646 no
647
647
648 group reference with no match
648 group reference with no match
649
649
650 $ hg log -R a -r2 -T '{search(r"q", desc) % "match: {0}"}\n'
650 $ hg log -R a -r2 -T '{search(r"q", desc) % "match: {0}"}\n'
651
651
652
652
653 bad group names
653 bad group names
654
654
655 $ hg log -R a -r2 -T '{search(r"(?P<0>.)", desc) % "{0}"}\n'
655 $ hg log -R a -r2 -T '{search(r"(?P<0>.)", desc) % "{0}"}\n'
656 hg: parse error: search got an invalid pattern: (?P<0>.)
656 hg: parse error: search got an invalid pattern: (?P<0>.)
657 [255]
657 [255]
658 $ hg log -R a -r2 -T '{search(r"(?P<repo>.)", desc) % "{repo}"}\n'
658 $ hg log -R a -r2 -T '{search(r"(?P<repo>.)", desc) % "{repo}"}\n'
659 hg: parse error: invalid group 'repo' in search pattern: (?P<repo>.)
659 hg: parse error: invalid group 'repo' in search pattern: (?P<repo>.)
660 [255]
660 [255]
661
661
662 Test the sub function of templating for expansion:
662 Test the sub function of templating for expansion:
663
663
664 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
664 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
665 xx
665 xx
666
666
667 $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
667 $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
668 hg: parse error: sub got an invalid pattern: [
668 hg: parse error: sub got an invalid pattern: [
669 [255]
669 [255]
670 $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
670 $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
671 hg: parse error: sub got an invalid replacement: \1
671 hg: parse error: sub got an invalid replacement: \1
672 [255]
672 [255]
673
673
674 Test the strip function with chars specified:
674 Test the strip function with chars specified:
675
675
676 $ hg log -R latesttag --template '{desc}\n'
676 $ hg log -R latesttag --template '{desc}\n'
677 at3
677 at3
678 t5
678 t5
679 t4
679 t4
680 t3
680 t3
681 t2
681 t2
682 t1
682 t1
683 merge
683 merge
684 h2e
684 h2e
685 h2d
685 h2d
686 h1c
686 h1c
687 b
687 b
688 a
688 a
689
689
690 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
690 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
691 at3
691 at3
692 5
692 5
693 4
693 4
694 3
694 3
695 2
695 2
696 1
696 1
697 merg
697 merg
698 h2
698 h2
699 h2d
699 h2d
700 h1c
700 h1c
701 b
701 b
702 a
702 a
703
703
704 Test date format:
704 Test date format:
705
705
706 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
706 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
707 date: 70 01 01 10 +0000
707 date: 70 01 01 10 +0000
708 date: 70 01 01 09 +0000
708 date: 70 01 01 09 +0000
709 date: 70 01 01 04 +0000
709 date: 70 01 01 04 +0000
710 date: 70 01 01 08 +0000
710 date: 70 01 01 08 +0000
711 date: 70 01 01 07 +0000
711 date: 70 01 01 07 +0000
712 date: 70 01 01 06 +0000
712 date: 70 01 01 06 +0000
713 date: 70 01 01 05 +0100
713 date: 70 01 01 05 +0100
714 date: 70 01 01 04 +0000
714 date: 70 01 01 04 +0000
715 date: 70 01 01 03 +0000
715 date: 70 01 01 03 +0000
716 date: 70 01 01 02 +0000
716 date: 70 01 01 02 +0000
717 date: 70 01 01 01 +0000
717 date: 70 01 01 01 +0000
718 date: 70 01 01 00 +0000
718 date: 70 01 01 00 +0000
719
719
720 Test invalid date:
720 Test invalid date:
721
721
722 $ hg log -R latesttag -T '{date(rev)}\n'
722 $ hg log -R latesttag -T '{date(rev)}\n'
723 hg: parse error: date expects a date information
723 hg: parse error: date expects a date information
724 [255]
724 [255]
725
725
726 Set up repository containing template fragments in commit metadata:
726 Set up repository containing template fragments in commit metadata:
727
727
728 $ hg init r
728 $ hg init r
729 $ cd r
729 $ cd r
730 $ echo a > a
730 $ echo a > a
731 $ hg ci -Am '{rev}'
731 $ hg ci -Am '{rev}'
732 adding a
732 adding a
733
733
734 $ hg branch -q 'text.{rev}'
734 $ hg branch -q 'text.{rev}'
735 $ echo aa >> aa
735 $ echo aa >> aa
736 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
736 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
737
737
738 color effect can be specified without quoting:
738 color effect can be specified without quoting:
739
739
740 $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
740 $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
741 \x1b[0;31mtext\x1b[0m (esc)
741 \x1b[0;31mtext\x1b[0m (esc)
742
742
743 color effects can be nested (issue5413)
743 color effects can be nested (issue5413)
744
744
745 $ hg debugtemplate --color=always \
745 $ hg debugtemplate --color=always \
746 > '{label(red, "red{label(magenta, "ma{label(cyan, "cyan")}{label(yellow, "yellow")}genta")}")}\n'
746 > '{label(red, "red{label(magenta, "ma{label(cyan, "cyan")}{label(yellow, "yellow")}genta")}")}\n'
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)
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 pad() should interact well with color codes (issue5416)
749 pad() should interact well with color codes (issue5416)
750
750
751 $ hg debugtemplate --color=always \
751 $ hg debugtemplate --color=always \
752 > '{pad(label(red, "red"), 5, label(cyan, "-"))}\n'
752 > '{pad(label(red, "red"), 5, label(cyan, "-"))}\n'
753 \x1b[0;31mred\x1b[0m\x1b[0;36m-\x1b[0m\x1b[0;36m-\x1b[0m (esc)
753 \x1b[0;31mred\x1b[0m\x1b[0;36m-\x1b[0m\x1b[0;36m-\x1b[0m (esc)
754
754
755 pad() with truncate has to strip color codes, though
755 pad() with truncate has to strip color codes, though
756
756
757 $ hg debugtemplate --color=always \
757 $ hg debugtemplate --color=always \
758 > '{pad(label(red, "scarlet"), 5, truncate=true)}\n'
758 > '{pad(label(red, "scarlet"), 5, truncate=true)}\n'
759 scarl
759 scarl
760
760
761 label should be no-op if color is disabled:
761 label should be no-op if color is disabled:
762
762
763 $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
763 $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
764 text
764 text
765 $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
765 $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
766 text
766 text
767
767
768 Test branches inside if statement:
768 Test branches inside if statement:
769
769
770 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
770 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
771 no
771 no
772
772
773 Test dict constructor:
773 Test dict constructor:
774
774
775 $ hg log -r 0 -T '{dict(y=node|short, x=rev)}\n'
775 $ hg log -r 0 -T '{dict(y=node|short, x=rev)}\n'
776 y=f7769ec2ab97 x=0
776 y=f7769ec2ab97 x=0
777 $ hg log -r 0 -T '{dict(x=rev, y=node|short) % "{key}={value}\n"}'
777 $ hg log -r 0 -T '{dict(x=rev, y=node|short) % "{key}={value}\n"}'
778 x=0
778 x=0
779 y=f7769ec2ab97
779 y=f7769ec2ab97
780 $ hg log -r 0 -T '{dict(x=rev, y=node|short)|json}\n'
780 $ hg log -r 0 -T '{dict(x=rev, y=node|short)|json}\n'
781 {"x": 0, "y": "f7769ec2ab97"}
781 {"x": 0, "y": "f7769ec2ab97"}
782 $ hg log -r 0 -T '{dict()|json}\n'
782 $ hg log -r 0 -T '{dict()|json}\n'
783 {}
783 {}
784
784
785 $ hg log -r 0 -T '{dict(rev, node=node|short)}\n'
785 $ hg log -r 0 -T '{dict(rev, node=node|short)}\n'
786 rev=0 node=f7769ec2ab97
786 rev=0 node=f7769ec2ab97
787 $ hg log -r 0 -T '{dict(rev, node|short)}\n'
787 $ hg log -r 0 -T '{dict(rev, node|short)}\n'
788 rev=0 node=f7769ec2ab97
788 rev=0 node=f7769ec2ab97
789
789
790 $ hg log -r 0 -T '{dict(rev, rev=rev)}\n'
790 $ hg log -r 0 -T '{dict(rev, rev=rev)}\n'
791 hg: parse error: duplicated dict key 'rev' inferred
791 hg: parse error: duplicated dict key 'rev' inferred
792 [255]
792 [255]
793 $ hg log -r 0 -T '{dict(node, node|short)}\n'
793 $ hg log -r 0 -T '{dict(node, node|short)}\n'
794 hg: parse error: duplicated dict key 'node' inferred
794 hg: parse error: duplicated dict key 'node' inferred
795 [255]
795 [255]
796 $ hg log -r 0 -T '{dict(1 + 2)}'
796 $ hg log -r 0 -T '{dict(1 + 2)}'
797 hg: parse error: dict key cannot be inferred
797 hg: parse error: dict key cannot be inferred
798 [255]
798 [255]
799
799
800 $ hg log -r 0 -T '{dict(x=rev, x=node)}'
800 $ hg log -r 0 -T '{dict(x=rev, x=node)}'
801 hg: parse error: dict got multiple values for keyword argument 'x'
801 hg: parse error: dict got multiple values for keyword argument 'x'
802 [255]
802 [255]
803
803
804 Test get function:
804 Test get function:
805
805
806 $ hg log -r 0 --template '{get(extras, "branch")}\n'
806 $ hg log -r 0 --template '{get(extras, "branch")}\n'
807 default
807 default
808 $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
808 $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
809 default
809 default
810 $ hg log -r 0 --template '{get(files, "should_fail")}\n'
810 $ hg log -r 0 --template '{get(files, "should_fail")}\n'
811 hg: parse error: not a dictionary
811 hg: parse error: not a dictionary
812 (get() expects a dict as first argument)
812 (get() expects a dict as first argument)
813 [255]
813 [255]
814
814
815 Test json filter applied to wrapped object:
815 Test json filter applied to wrapped object:
816
816
817 $ hg log -r0 -T '{files|json}\n'
817 $ hg log -r0 -T '{files|json}\n'
818 ["a"]
818 ["a"]
819 $ hg log -r0 -T '{extras|json}\n'
819 $ hg log -r0 -T '{extras|json}\n'
820 {"branch": "default"}
820 {"branch": "default"}
821 $ hg log -r0 -T '{date|json}\n'
821 $ hg log -r0 -T '{date|json}\n'
822 [0, 0]
822 [0, 0]
823
823
824 Test json filter applied to map result:
824 Test json filter applied to map result:
825
825
826 $ hg log -r0 -T '{json(extras % "{key}")}\n'
826 $ hg log -r0 -T '{json(extras % "{key}")}\n'
827 ["branch"]
827 ["branch"]
828
828
829 Test localdate(date, tz) function:
829 Test localdate(date, tz) function:
830
830
831 $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
831 $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
832 1970-01-01 09:00 +0900
832 1970-01-01 09:00 +0900
833 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
833 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
834 1970-01-01 00:00 +0000
834 1970-01-01 00:00 +0000
835 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
835 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
836 hg: parse error: localdate expects a timezone
836 hg: parse error: localdate expects a timezone
837 [255]
837 [255]
838 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
838 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
839 1970-01-01 02:00 +0200
839 1970-01-01 02:00 +0200
840 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
840 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
841 1970-01-01 00:00 +0000
841 1970-01-01 00:00 +0000
842 $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
842 $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
843 1970-01-01 00:00 +0000
843 1970-01-01 00:00 +0000
844 $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
844 $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
845 hg: parse error: localdate expects a timezone
845 hg: parse error: localdate expects a timezone
846 [255]
846 [255]
847 $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
847 $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
848 hg: parse error: localdate expects a timezone
848 hg: parse error: localdate expects a timezone
849 [255]
849 [255]
850
850
851 Test shortest(node) function:
851 Test shortest(node) function:
852
852
853 $ echo b > b
853 $ echo b > b
854 $ hg ci -qAm b
854 $ hg ci -qAm b
855 $ hg log --template '{shortest(node)}\n'
855 $ hg log --template '{shortest(node)}\n'
856 e777
856 e777
857 bcc7
857 bcc7
858 f776
858 f776
859 $ hg log --template '{shortest(node, 10)}\n'
859 $ hg log --template '{shortest(node, 10)}\n'
860 e777603221
860 e777603221
861 bcc7ff960b
861 bcc7ff960b
862 f7769ec2ab
862 f7769ec2ab
863 $ hg log --template '{shortest(node, 1)}\n' -r null
863 $ hg log --template '{shortest(node, 1)}\n' -r null
864 00
864 00
865 $ hg log --template '{node|shortest}\n' -l1
865 $ hg log --template '{node|shortest}\n' -l1
866 e777
866 e777
867
867
868 $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
868 $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
869 f7769ec2ab
869 f7769ec2ab
870 $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
870 $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
871 hg: parse error: shortest() expects an integer minlength
871 hg: parse error: shortest() expects an integer minlength
872 [255]
872 [255]
873
873
874 $ hg log -r 'wdir()' -T '{node|shortest}\n'
874 $ hg log -r 'wdir()' -T '{node|shortest}\n'
875 ffff
875 ffff
876
876
877 $ hg log --template '{shortest("f")}\n' -l1
877 $ hg log --template '{shortest("f")}\n' -l1
878 f
878 f
879
879
880 $ hg log --template '{shortest("0123456789012345678901234567890123456789")}\n' -l1
880 $ hg log --template '{shortest("0123456789012345678901234567890123456789")}\n' -l1
881 0123456789012345678901234567890123456789
881 0123456789012345678901234567890123456789
882
882
883 $ hg log --template '{shortest("01234567890123456789012345678901234567890123456789")}\n' -l1
883 $ hg log --template '{shortest("01234567890123456789012345678901234567890123456789")}\n' -l1
884 01234567890123456789012345678901234567890123456789
884 01234567890123456789012345678901234567890123456789
885
885
886 $ hg log --template '{shortest("not a hex string")}\n' -l1
886 $ hg log --template '{shortest("not a hex string")}\n' -l1
887 not a hex string
887 not a hex string
888
888
889 $ hg log --template '{shortest("not a hex string, but it'\''s 40 bytes long")}\n' -l1
889 $ hg log --template '{shortest("not a hex string, but it'\''s 40 bytes long")}\n' -l1
890 not a hex string, but it's 40 bytes long
890 not a hex string, but it's 40 bytes long
891
891
892 $ hg log --template '{shortest("ffffffffffffffffffffffffffffffffffffffff")}\n' -l1
892 $ hg log --template '{shortest("ffffffffffffffffffffffffffffffffffffffff")}\n' -l1
893 ffff
893 ffff
894
894
895 $ hg log --template '{shortest("fffffff")}\n' -l1
895 $ hg log --template '{shortest("fffffff")}\n' -l1
896 ffff
896 ffff
897
897
898 $ hg log --template '{shortest("ff")}\n' -l1
898 $ hg log --template '{shortest("ff")}\n' -l1
899 ffff
899 ffff
900
900
901 $ cd ..
901 $ cd ..
902
902
903 Test shortest(node) with the repo having short hash collision:
903 Test shortest(node) with the repo having short hash collision:
904
904
905 $ hg init hashcollision
905 $ hg init hashcollision
906 $ cd hashcollision
906 $ cd hashcollision
907 $ cat <<EOF >> .hg/hgrc
907 $ cat <<EOF >> .hg/hgrc
908 > [experimental]
908 > [experimental]
909 > evolution.createmarkers=True
909 > evolution.createmarkers=True
910 > EOF
910 > EOF
911 $ echo 0 > a
911 $ echo 0 > a
912 $ hg ci -qAm 0
912 $ hg ci -qAm 0
913 $ for i in 17 129 248 242 480 580 617 1057 2857 4025; do
913 $ for i in 17 129 248 242 480 580 617 1057 2857 4025; do
914 > hg up -q 0
914 > hg up -q 0
915 > echo $i > a
915 > echo $i > a
916 > hg ci -qm $i
916 > hg ci -qm $i
917 > done
917 > done
918 $ hg up -q null
918 $ hg up -q null
919 $ hg log -r0: -T '{rev}:{node}\n'
919 $ hg log -r0: -T '{rev}:{node}\n'
920 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
920 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
921 1:11424df6dc1dd4ea255eae2b58eaca7831973bbc
921 1:11424df6dc1dd4ea255eae2b58eaca7831973bbc
922 2:11407b3f1b9c3e76a79c1ec5373924df096f0499
922 2:11407b3f1b9c3e76a79c1ec5373924df096f0499
923 3:11dd92fe0f39dfdaacdaa5f3997edc533875cfc4
923 3:11dd92fe0f39dfdaacdaa5f3997edc533875cfc4
924 4:10776689e627b465361ad5c296a20a487e153ca4
924 4:10776689e627b465361ad5c296a20a487e153ca4
925 5:a00be79088084cb3aff086ab799f8790e01a976b
925 5:a00be79088084cb3aff086ab799f8790e01a976b
926 6:a0b0acd79b4498d0052993d35a6a748dd51d13e6
926 6:a0b0acd79b4498d0052993d35a6a748dd51d13e6
927 7:a0457b3450b8e1b778f1163b31a435802987fe5d
927 7:a0457b3450b8e1b778f1163b31a435802987fe5d
928 8:c56256a09cd28e5764f32e8e2810d0f01e2e357a
928 8:c56256a09cd28e5764f32e8e2810d0f01e2e357a
929 9:c5623987d205cd6d9d8389bfc40fff9dbb670b48
929 9:c5623987d205cd6d9d8389bfc40fff9dbb670b48
930 10:c562ddd9c94164376c20b86b0b4991636a3bf84f
930 10:c562ddd9c94164376c20b86b0b4991636a3bf84f
931 $ hg debugobsolete a00be79088084cb3aff086ab799f8790e01a976b
931 $ hg debugobsolete a00be79088084cb3aff086ab799f8790e01a976b
932 1 new obsolescence markers
932 1 new obsolescence markers
933 obsoleted 1 changesets
933 obsoleted 1 changesets
934 $ hg debugobsolete c5623987d205cd6d9d8389bfc40fff9dbb670b48
934 $ hg debugobsolete c5623987d205cd6d9d8389bfc40fff9dbb670b48
935 1 new obsolescence markers
935 1 new obsolescence markers
936 obsoleted 1 changesets
936 obsoleted 1 changesets
937 $ hg debugobsolete c562ddd9c94164376c20b86b0b4991636a3bf84f
937 $ hg debugobsolete c562ddd9c94164376c20b86b0b4991636a3bf84f
938 1 new obsolescence markers
938 1 new obsolescence markers
939 obsoleted 1 changesets
939 obsoleted 1 changesets
940
940
941 nodes starting with '11' (we don't have the revision number '11' though)
941 nodes starting with '11' (we don't have the revision number '11' though)
942
942
943 $ hg log -r 1:3 -T '{rev}:{shortest(node, 0)}\n'
943 $ hg log -r 1:3 -T '{rev}:{shortest(node, 0)}\n'
944 1:1142
944 1:1142
945 2:1140
945 2:1140
946 3:11d
946 3:11d
947
947
948 '5:a00' is hidden, but still we have two nodes starting with 'a0'
948 '5:a00' is hidden, but still we have two nodes starting with 'a0'
949
949
950 $ hg log -r 6:7 -T '{rev}:{shortest(node, 0)}\n'
950 $ hg log -r 6:7 -T '{rev}:{shortest(node, 0)}\n'
951 6:a0b
951 6:a0b
952 7:a04
952 7:a04
953
953
954 node '10' conflicts with the revision number '10' even if it is hidden
954 node '10' conflicts with the revision number '10' even if it is hidden
955 (we could exclude hidden revision numbers, but currently we don't)
955 (we could exclude hidden revision numbers, but currently we don't)
956
956
957 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n'
957 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n'
958 4:107
958 4:107
959 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
959 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
960 4:107
960 4:107
961
961
962 $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n'
962 $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n'
963 4:x10
963 4:x10
964 $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
964 $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
965 4:x10
965 4:x10
966
966
967 node 'c562' should be unique if the other 'c562' nodes are hidden
967 node 'c562' should be unique if the other 'c562' nodes are hidden
968 (but we don't try the slow path to filter out hidden nodes for now)
968 (but we don't try the slow path to filter out hidden nodes for now)
969
969
970 $ hg log -r 8 -T '{rev}:{node|shortest}\n'
970 $ hg log -r 8 -T '{rev}:{node|shortest}\n'
971 8:c5625
971 8:c5625
972 $ hg log -r 8:10 -T '{rev}:{node|shortest}\n' --hidden
972 $ hg log -r 8:10 -T '{rev}:{node|shortest}\n' --hidden
973 8:c5625
973 8:c5625
974 9:c5623
974 9:c5623
975 10:c562d
975 10:c562d
976
976
977 $ cd ..
977 $ cd ..
978
978
979 Test prefixhexnode when the first character of the hash is 0.
979 Test prefixhexnode when the first character of the hash is 0.
980 $ hg init hashcollision2
980 $ hg init hashcollision2
981 $ cd hashcollision2
981 $ cd hashcollision2
982 $ cat <<EOF >> .hg/hgrc
982 $ cat <<EOF >> .hg/hgrc
983 > [experimental]
983 > [experimental]
984 > evolution.createmarkers=True
984 > evolution.createmarkers=True
985 > EOF
985 > EOF
986 $ echo 0 > a
986 $ echo 0 > a
987 $ hg ci -qAm 0
987 $ hg ci -qAm 0
988 $ echo 21 > a
988 $ echo 21 > a
989 $ hg ci -qm 21
989 $ hg ci -qm 21
990 $ hg up -q null
990 $ hg up -q null
991 $ hg log -r0: -T '{rev}:{node}\n'
991 $ hg log -r0: -T '{rev}:{node}\n'
992 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
992 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
993 1:0cf177ba2b1dc3862a00fb81715fec90950201be
993 1:0cf177ba2b1dc3862a00fb81715fec90950201be
994
994
995 we need the 'x' prefix to ensure we aren't colliding with rev0. We identify
995 we need the 'x' prefix to ensure we aren't colliding with rev0. We identify
996 the collision with nullid if we aren't using disambiguatewithin, so we need to set
996 the collision with nullid if we aren't using disambiguatewithin, so we need to set
997 that as well.
997 that as well.
998 $ hg --config experimental.revisions.disambiguatewithin='descendants(0)' \
998 $ hg --config experimental.revisions.disambiguatewithin='descendants(0)' \
999 > --config experimental.revisions.prefixhexnode=yes \
999 > --config experimental.revisions.prefixhexnode=yes \
1000 > log -r 1 -T '{rev}:{shortest(node, 0)}\n'
1000 > log -r 1 -T '{rev}:{shortest(node, 0)}\n'
1001 1:x0
1001 1:x0
1002
1002
1003 $ hg debugobsolete 0cf177ba2b1dc3862a00fb81715fec90950201be
1003 $ hg debugobsolete 0cf177ba2b1dc3862a00fb81715fec90950201be
1004 1 new obsolescence markers
1004 1 new obsolescence markers
1005 obsoleted 1 changesets
1005 obsoleted 1 changesets
1006 $ hg up -q 0
1006 $ hg up -q 0
1007 $ echo 61 > a
1007 $ echo 61 > a
1008 $ hg ci -m 61
1008 $ hg ci -m 61
1009 $ hg log -r0: -T '{rev}:{node}\n'
1009 $ hg log -r0: -T '{rev}:{node}\n'
1010 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
1010 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
1011 2:01384dde84b3a511ae0835f35ac40bd806c99bb8
1011 2:01384dde84b3a511ae0835f35ac40bd806c99bb8
1012
1012
1013 we still have the 'x' prefix because '0' is still the shortest prefix, since
1013 we still have the 'x' prefix because '0' is still the shortest prefix, since
1014 rev1's '0c' is hidden.
1014 rev1's '0c' is hidden.
1015 $ hg --config experimental.revisions.disambiguatewithin=0:-1-0 \
1015 $ hg --config experimental.revisions.disambiguatewithin=0:-1-0 \
1016 > --config experimental.revisions.prefixhexnode=yes \
1016 > --config experimental.revisions.prefixhexnode=yes \
1017 > log -r 0:-1-0 -T '{rev}:{shortest(node, 0)}\n'
1017 > log -r 0:-1-0 -T '{rev}:{shortest(node, 0)}\n'
1018 2:x0
1018 2:x0
1019
1019
1020 we don't have the 'x' prefix on 2 because '01' is not a synonym for rev1.
1020 we don't have the 'x' prefix on 2 because '01' is not a synonym for rev1.
1021 $ hg --config experimental.revisions.disambiguatewithin=0:-1-0 \
1021 $ hg --config experimental.revisions.disambiguatewithin=0:-1-0 \
1022 > --config experimental.revisions.prefixhexnode=yes \
1022 > --config experimental.revisions.prefixhexnode=yes \
1023 > log -r 0:-1-0 -T '{rev}:{shortest(node, 0)}\n' --hidden
1023 > log -r 0:-1-0 -T '{rev}:{shortest(node, 0)}\n' --hidden
1024 1:0c
1024 1:0c
1025 2:01
1025 2:01
1026
1026
1027 $ cd ..
1027 $ cd ..
1028
1028
1029 Test pad function
1029 Test pad function
1030
1030
1031 $ cd r
1031 $ cd r
1032
1032
1033 $ hg log --template '{pad(rev, 20)} {author|user}\n'
1033 $ hg log --template '{pad(rev, 20)} {author|user}\n'
1034 2 test
1034 2 test
1035 1 {node|short}
1035 1 {node|short}
1036 0 test
1036 0 test
1037
1037
1038 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
1038 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
1039 2 test
1039 2 test
1040 1 {node|short}
1040 1 {node|short}
1041 0 test
1041 0 test
1042
1042
1043 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
1043 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
1044 2------------------- test
1044 2------------------- test
1045 1------------------- {node|short}
1045 1------------------- {node|short}
1046 0------------------- test
1046 0------------------- test
1047
1047
1048 $ hg log --template '{pad(author, 5, "-", False, True)}\n'
1048 $ hg log --template '{pad(author, 5, "-", False, True)}\n'
1049 test-
1049 test-
1050 {node
1050 {node
1051 test-
1051 test-
1052 $ hg log --template '{pad(author, 5, "-", True, True)}\n'
1052 $ hg log --template '{pad(author, 5, "-", True, True)}\n'
1053 -test
1053 -test
1054 hort}
1054 hort}
1055 -test
1055 -test
1056
1056
1057 Test template string in pad function
1057 Test template string in pad function
1058
1058
1059 $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
1059 $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
1060 {0} test
1060 {0} test
1061
1061
1062 $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
1062 $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
1063 \{rev} test
1063 \{rev} test
1064
1064
1065 Test width argument passed to pad function
1065 Test width argument passed to pad function
1066
1066
1067 $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
1067 $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
1068 0 test
1068 0 test
1069 $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
1069 $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
1070 hg: parse error: pad() expects an integer width
1070 hg: parse error: pad() expects an integer width
1071 [255]
1071 [255]
1072
1072
1073 Test invalid fillchar passed to pad function
1073 Test invalid fillchar passed to pad function
1074
1074
1075 $ hg log -r 0 -T '{pad(rev, 10, "")}\n'
1075 $ hg log -r 0 -T '{pad(rev, 10, "")}\n'
1076 hg: parse error: pad() expects a single fill character
1076 hg: parse error: pad() expects a single fill character
1077 [255]
1077 [255]
1078 $ hg log -r 0 -T '{pad(rev, 10, "--")}\n'
1078 $ hg log -r 0 -T '{pad(rev, 10, "--")}\n'
1079 hg: parse error: pad() expects a single fill character
1079 hg: parse error: pad() expects a single fill character
1080 [255]
1080 [255]
1081
1081
1082 Test boolean argument passed to pad function
1082 Test boolean argument passed to pad function
1083
1083
1084 no crash
1084 no crash
1085
1085
1086 $ hg log -r 0 -T '{pad(rev, 10, "-", "f{"oo"}")}\n'
1086 $ hg log -r 0 -T '{pad(rev, 10, "-", "f{"oo"}")}\n'
1087 ---------0
1087 ---------0
1088
1088
1089 string/literal
1089 string/literal
1090
1090
1091 $ hg log -r 0 -T '{pad(rev, 10, "-", "false")}\n'
1091 $ hg log -r 0 -T '{pad(rev, 10, "-", "false")}\n'
1092 ---------0
1092 ---------0
1093 $ hg log -r 0 -T '{pad(rev, 10, "-", false)}\n'
1093 $ hg log -r 0 -T '{pad(rev, 10, "-", false)}\n'
1094 0---------
1094 0---------
1095 $ hg log -r 0 -T '{pad(rev, 10, "-", "")}\n'
1095 $ hg log -r 0 -T '{pad(rev, 10, "-", "")}\n'
1096 0---------
1096 0---------
1097
1097
1098 unknown keyword is evaluated to ''
1098 unknown keyword is evaluated to ''
1099
1099
1100 $ hg log -r 0 -T '{pad(rev, 10, "-", unknownkeyword)}\n'
1100 $ hg log -r 0 -T '{pad(rev, 10, "-", unknownkeyword)}\n'
1101 0---------
1101 0---------
1102
1102
1103 Test separate function
1103 Test separate function
1104
1104
1105 $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
1105 $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
1106 a-b-c
1106 a-b-c
1107 $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
1107 $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
1108 0:f7769ec2ab97 test default
1108 0:f7769ec2ab97 test default
1109 $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
1109 $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
1110 a \x1b[0;31mb\x1b[0m c d (esc)
1110 a \x1b[0;31mb\x1b[0m c d (esc)
1111
1111
1112 Test boolean expression/literal passed to if function
1112 Test boolean expression/literal passed to if function
1113
1113
1114 $ hg log -r 0 -T '{if(rev, "rev 0 is True")}\n'
1114 $ hg log -r 0 -T '{if(rev, "rev 0 is True")}\n'
1115 rev 0 is True
1115 rev 0 is True
1116 $ hg log -r 0 -T '{if(0, "literal 0 is True as well")}\n'
1116 $ hg log -r 0 -T '{if(0, "literal 0 is True as well")}\n'
1117 literal 0 is True as well
1117 literal 0 is True as well
1118 $ hg log -r 0 -T '{if(min(revset(r"0")), "0 of hybriditem is also True")}\n'
1118 $ hg log -r 0 -T '{if(min(revset(r"0")), "0 of hybriditem is also True")}\n'
1119 0 of hybriditem is also True
1119 0 of hybriditem is also True
1120 $ hg log -r 0 -T '{if("", "", "empty string is False")}\n'
1120 $ hg log -r 0 -T '{if("", "", "empty string is False")}\n'
1121 empty string is False
1121 empty string is False
1122 $ hg log -r 0 -T '{if(revset(r"0 - 0"), "", "empty list is False")}\n'
1122 $ hg log -r 0 -T '{if(revset(r"0 - 0"), "", "empty list is False")}\n'
1123 empty list is False
1123 empty list is False
1124 $ hg log -r 0 -T '{if(revset(r"0"), "non-empty list is True")}\n'
1124 $ hg log -r 0 -T '{if(revset(r"0"), "non-empty list is True")}\n'
1125 non-empty list is True
1125 non-empty list is True
1126 $ hg log -r 0 -T '{if(revset(r"0") % "", "list of empty strings is True")}\n'
1126 $ hg log -r 0 -T '{if(revset(r"0") % "", "list of empty strings is True")}\n'
1127 list of empty strings is True
1127 list of empty strings is True
1128 $ hg log -r 0 -T '{if(true, "true is True")}\n'
1128 $ hg log -r 0 -T '{if(true, "true is True")}\n'
1129 true is True
1129 true is True
1130 $ hg log -r 0 -T '{if(false, "", "false is False")}\n'
1130 $ hg log -r 0 -T '{if(false, "", "false is False")}\n'
1131 false is False
1131 false is False
1132 $ hg log -r 0 -T '{if("false", "non-empty string is True")}\n'
1132 $ hg log -r 0 -T '{if("false", "non-empty string is True")}\n'
1133 non-empty string is True
1133 non-empty string is True
1134
1134
1135 Test ifcontains function
1135 Test ifcontains function
1136
1136
1137 $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
1137 $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
1138 2 is in the string
1138 2 is in the string
1139 1 is not
1139 1 is not
1140 0 is in the string
1140 0 is in the string
1141
1141
1142 $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
1142 $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
1143 2 is in the string
1143 2 is in the string
1144 1 is not
1144 1 is not
1145 0 is in the string
1145 0 is in the string
1146
1146
1147 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
1147 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
1148 2 did not add a
1148 2 did not add a
1149 1 did not add a
1149 1 did not add a
1150 0 added a
1150 0 added a
1151
1151
1152 $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
1152 $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
1153 2 is parent of 1
1153 2 is parent of 1
1154 1
1154 1
1155 0
1155 0
1156
1156
1157 $ hg log -l1 -T '{ifcontains("branch", extras, "t", "f")}\n'
1157 $ hg log -l1 -T '{ifcontains("branch", extras, "t", "f")}\n'
1158 t
1158 t
1159 $ hg log -l1 -T '{ifcontains("branch", extras % "{key}", "t", "f")}\n'
1159 $ hg log -l1 -T '{ifcontains("branch", extras % "{key}", "t", "f")}\n'
1160 t
1160 t
1161 $ hg log -l1 -T '{ifcontains("branc", extras % "{key}", "t", "f")}\n'
1161 $ hg log -l1 -T '{ifcontains("branc", extras % "{key}", "t", "f")}\n'
1162 f
1162 f
1163 $ hg log -l1 -T '{ifcontains("branc", stringify(extras % "{key}"), "t", "f")}\n'
1163 $ hg log -l1 -T '{ifcontains("branc", stringify(extras % "{key}"), "t", "f")}\n'
1164 t
1164 t
1165
1165
1166 Test revset function
1166 Test revset function
1167
1167
1168 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
1168 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
1169 2 current rev
1169 2 current rev
1170 1 not current rev
1170 1 not current rev
1171 0 not current rev
1171 0 not current rev
1172
1172
1173 $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
1173 $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
1174 2 match rev
1174 2 match rev
1175 1 match rev
1175 1 match rev
1176 0 not match rev
1176 0 not match rev
1177
1177
1178 $ hg log -T '{ifcontains(desc, revset(":"), "", "type not match")}\n' -l1
1178 $ hg log -T '{ifcontains(desc, revset(":"), "", "type not match")}\n' -l1
1179 type not match
1179 type not match
1180
1180
1181 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
1181 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
1182 2 Parents: 1
1182 2 Parents: 1
1183 1 Parents: 0
1183 1 Parents: 0
1184 0 Parents:
1184 0 Parents:
1185
1185
1186 $ cat >> .hg/hgrc <<EOF
1186 $ cat >> .hg/hgrc <<EOF
1187 > [revsetalias]
1187 > [revsetalias]
1188 > myparents(\$1) = parents(\$1)
1188 > myparents(\$1) = parents(\$1)
1189 > EOF
1189 > EOF
1190 $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
1190 $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
1191 2 Parents: 1
1191 2 Parents: 1
1192 1 Parents: 0
1192 1 Parents: 0
1193 0 Parents:
1193 0 Parents:
1194
1194
1195 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
1195 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
1196 Rev: 2
1196 Rev: 2
1197 Ancestor: 0
1197 Ancestor: 0
1198 Ancestor: 1
1198 Ancestor: 1
1199 Ancestor: 2
1199 Ancestor: 2
1200
1200
1201 Rev: 1
1201 Rev: 1
1202 Ancestor: 0
1202 Ancestor: 0
1203 Ancestor: 1
1203 Ancestor: 1
1204
1204
1205 Rev: 0
1205 Rev: 0
1206 Ancestor: 0
1206 Ancestor: 0
1207
1207
1208 $ hg log --template '{revset("TIP"|lower)}\n' -l1
1208 $ hg log --template '{revset("TIP"|lower)}\n' -l1
1209 2
1209 2
1210
1210
1211 $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
1211 $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
1212 2
1212 2
1213
1213
1214 a list template is evaluated for each item of revset/parents
1214 a list template is evaluated for each item of revset/parents
1215
1215
1216 $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
1216 $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
1217 2 p: 1:bcc7ff960b8e
1217 2 p: 1:bcc7ff960b8e
1218 1 p: 0:f7769ec2ab97
1218 1 p: 0:f7769ec2ab97
1219 0 p:
1219 0 p:
1220
1220
1221 $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
1221 $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
1222 2 p: 1:bcc7ff960b8e -1:000000000000
1222 2 p: 1:bcc7ff960b8e -1:000000000000
1223 1 p: 0:f7769ec2ab97 -1:000000000000
1223 1 p: 0:f7769ec2ab97 -1:000000000000
1224 0 p: -1:000000000000 -1:000000000000
1224 0 p: -1:000000000000 -1:000000000000
1225
1225
1226 therefore, 'revcache' should be recreated for each rev
1226 therefore, 'revcache' should be recreated for each rev
1227
1227
1228 $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
1228 $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
1229 2 aa b
1229 2 aa b
1230 p
1230 p
1231 1
1231 1
1232 p a
1232 p a
1233 0 a
1233 0 a
1234 p
1234 p
1235
1235
1236 $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
1236 $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
1237 2 aa b
1237 2 aa b
1238 p
1238 p
1239 1
1239 1
1240 p a
1240 p a
1241 0 a
1241 0 a
1242 p
1242 p
1243
1243
1244 a revset item must be evaluated as an integer revision, not an offset from tip
1244 a revset item must be evaluated as an integer revision, not an offset from tip
1245
1245
1246 $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
1246 $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
1247 -1:000000000000
1247 -1:000000000000
1248 $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
1248 $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
1249 -1:000000000000
1249 -1:000000000000
1250
1250
1251 join() should pick '{rev}' from revset items:
1251 join() should pick '{rev}' from revset items:
1252
1252
1253 $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
1253 $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
1254 4, 5
1254 4, 5
1255
1255
1256 on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
1256 on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
1257 default. join() should agree with the default formatting:
1257 default. join() should agree with the default formatting:
1258
1258
1259 $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
1259 $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
1260 5:13207e5a10d9, 4:bbe44766e73d
1260 5:13207e5a10d9, 4:bbe44766e73d
1261
1261
1262 $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
1262 $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
1263 5:13207e5a10d9fd28ec424934298e176197f2c67f,
1263 5:13207e5a10d9fd28ec424934298e176197f2c67f,
1264 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1264 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1265
1265
1266 %d parameter handling:
1267
1268 $ hg log -T '{revset("%d", rev)}\n' -r'wdir()'
1269 2147483647
1270 $ hg log -T '{revset("%d", rev)}\n' -r'null'
1271 -1
1272
1266 Invalid arguments passed to revset()
1273 Invalid arguments passed to revset()
1267
1274
1268 $ hg log -T '{revset("%whatever", 0)}\n'
1275 $ hg log -T '{revset("%whatever", 0)}\n'
1269 hg: parse error: unexpected revspec format character w
1276 hg: parse error: unexpected revspec format character w
1270 [255]
1277 [255]
1271 $ hg log -T '{revset("%lwhatever", files)}\n'
1278 $ hg log -T '{revset("%lwhatever", files)}\n'
1272 hg: parse error: unexpected revspec format character w
1279 hg: parse error: unexpected revspec format character w
1273 [255]
1280 [255]
1274 $ hg log -T '{revset("%s %s", 0)}\n'
1281 $ hg log -T '{revset("%s %s", 0)}\n'
1275 hg: parse error: missing argument for revspec
1282 hg: parse error: missing argument for revspec
1276 [255]
1283 [255]
1277 $ hg log -T '{revset("", 0)}\n'
1284 $ hg log -T '{revset("", 0)}\n'
1278 hg: parse error: too many revspec arguments specified
1285 hg: parse error: too many revspec arguments specified
1279 [255]
1286 [255]
1280 $ hg log -T '{revset("%s", 0, 1)}\n'
1287 $ hg log -T '{revset("%s", 0, 1)}\n'
1281 hg: parse error: too many revspec arguments specified
1288 hg: parse error: too many revspec arguments specified
1282 [255]
1289 [255]
1283 $ hg log -T '{revset("%", 0)}\n'
1290 $ hg log -T '{revset("%", 0)}\n'
1284 hg: parse error: incomplete revspec format character
1291 hg: parse error: incomplete revspec format character
1285 [255]
1292 [255]
1286 $ hg log -T '{revset("%l", 0)}\n'
1293 $ hg log -T '{revset("%l", 0)}\n'
1287 hg: parse error: incomplete revspec format character
1294 hg: parse error: incomplete revspec format character
1288 [255]
1295 [255]
1289 $ hg log -T '{revset("%d", 'foo')}\n'
1296 $ hg log -T '{revset("%d", 'foo')}\n'
1290 hg: parse error: invalid argument for revspec
1297 hg: parse error: invalid argument for revspec
1291 [255]
1298 [255]
1292 $ hg log -T '{revset("%ld", files)}\n'
1299 $ hg log -T '{revset("%ld", files)}\n'
1293 hg: parse error: invalid argument for revspec
1300 hg: parse error: invalid argument for revspec
1294 [255]
1301 [255]
1295 $ hg log -T '{revset("%ls", 0)}\n'
1302 $ hg log -T '{revset("%ls", 0)}\n'
1296 hg: parse error: invalid argument for revspec
1303 hg: parse error: invalid argument for revspec
1297 [255]
1304 [255]
1298 $ hg log -T '{revset("%b", 'foo')}\n'
1305 $ hg log -T '{revset("%b", 'foo')}\n'
1299 hg: parse error: invalid argument for revspec
1306 hg: parse error: invalid argument for revspec
1300 [255]
1307 [255]
1301 $ hg log -T '{revset("%lb", files)}\n'
1308 $ hg log -T '{revset("%lb", files)}\n'
1302 hg: parse error: invalid argument for revspec
1309 hg: parse error: invalid argument for revspec
1303 [255]
1310 [255]
1304 $ hg log -T '{revset("%r", 0)}\n'
1311 $ hg log -T '{revset("%r", 0)}\n'
1305 hg: parse error: invalid argument for revspec
1312 hg: parse error: invalid argument for revspec
1306 [255]
1313 [255]
1307
1314
1308 Test files function
1315 Test files function
1309
1316
1310 $ hg log -T "{rev}\n{join(files('*'), '\n')}\n"
1317 $ hg log -T "{rev}\n{join(files('*'), '\n')}\n"
1311 2
1318 2
1312 a
1319 a
1313 aa
1320 aa
1314 b
1321 b
1315 1
1322 1
1316 a
1323 a
1317 0
1324 0
1318 a
1325 a
1319
1326
1320 $ hg log -T "{rev}\n{join(files('aa'), '\n')}\n"
1327 $ hg log -T "{rev}\n{join(files('aa'), '\n')}\n"
1321 2
1328 2
1322 aa
1329 aa
1323 1
1330 1
1324
1331
1325 0
1332 0
1326
1333
1327
1334
1328 $ hg log -l1 -T "{files('aa') % '{file}\n'}"
1335 $ hg log -l1 -T "{files('aa') % '{file}\n'}"
1329 aa
1336 aa
1330 $ hg log -l1 -T "{files('aa') % '{path}\n'}"
1337 $ hg log -l1 -T "{files('aa') % '{path}\n'}"
1331 aa
1338 aa
1332
1339
1333 $ hg rm a
1340 $ hg rm a
1334 $ hg log -r "wdir()" -T "{rev}\n{join(files('*'), '\n')}\n"
1341 $ hg log -r "wdir()" -T "{rev}\n{join(files('*'), '\n')}\n"
1335 2147483647
1342 2147483647
1336 aa
1343 aa
1337 b
1344 b
1338 $ hg revert a
1345 $ hg revert a
1339
1346
1340 Test relpath function
1347 Test relpath function
1341
1348
1342 $ hg log -r0 -T '{files % "{file|relpath}\n"}'
1349 $ hg log -r0 -T '{files % "{file|relpath}\n"}'
1343 a
1350 a
1344 $ cd ..
1351 $ cd ..
1345 $ hg log -R r -r0 -T '{files % "{file|relpath}\n"}'
1352 $ hg log -R r -r0 -T '{files % "{file|relpath}\n"}'
1346 r/a
1353 r/a
1347
1354
1348 Test stringify on sub expressions
1355 Test stringify on sub expressions
1349
1356
1350 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
1357 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
1351 fourth, second, third
1358 fourth, second, third
1352 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
1359 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
1353 abc
1360 abc
1354
1361
1355 Test splitlines
1362 Test splitlines
1356
1363
1357 $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
1364 $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
1358 @ foo Modify, add, remove, rename
1365 @ foo Modify, add, remove, rename
1359 |
1366 |
1360 o foo future
1367 o foo future
1361 |
1368 |
1362 o foo third
1369 o foo third
1363 |
1370 |
1364 o foo second
1371 o foo second
1365
1372
1366 o foo merge
1373 o foo merge
1367 |\
1374 |\
1368 | o foo new head
1375 | o foo new head
1369 | |
1376 | |
1370 o | foo new branch
1377 o | foo new branch
1371 |/
1378 |/
1372 o foo no user, no domain
1379 o foo no user, no domain
1373 |
1380 |
1374 o foo no person
1381 o foo no person
1375 |
1382 |
1376 o foo other 1
1383 o foo other 1
1377 | foo other 2
1384 | foo other 2
1378 | foo
1385 | foo
1379 | foo other 3
1386 | foo other 3
1380 o foo line 1
1387 o foo line 1
1381 foo line 2
1388 foo line 2
1382
1389
1383 $ hg log -R a -r0 -T '{desc|splitlines}\n'
1390 $ hg log -R a -r0 -T '{desc|splitlines}\n'
1384 line 1 line 2
1391 line 1 line 2
1385 $ hg log -R a -r0 -T '{join(desc|splitlines, "|")}\n'
1392 $ hg log -R a -r0 -T '{join(desc|splitlines, "|")}\n'
1386 line 1|line 2
1393 line 1|line 2
1387
1394
1388 Test startswith
1395 Test startswith
1389 $ hg log -Gv -R a --template "{startswith(desc)}"
1396 $ hg log -Gv -R a --template "{startswith(desc)}"
1390 hg: parse error: startswith expects two arguments
1397 hg: parse error: startswith expects two arguments
1391 [255]
1398 [255]
1392
1399
1393 $ hg log -Gv -R a --template "{startswith('line', desc)}"
1400 $ hg log -Gv -R a --template "{startswith('line', desc)}"
1394 @
1401 @
1395 |
1402 |
1396 o
1403 o
1397 |
1404 |
1398 o
1405 o
1399 |
1406 |
1400 o
1407 o
1401
1408
1402 o
1409 o
1403 |\
1410 |\
1404 | o
1411 | o
1405 | |
1412 | |
1406 o |
1413 o |
1407 |/
1414 |/
1408 o
1415 o
1409 |
1416 |
1410 o
1417 o
1411 |
1418 |
1412 o
1419 o
1413 |
1420 |
1414 o line 1
1421 o line 1
1415 line 2
1422 line 2
1416
1423
1417 Test word function (including index out of bounds graceful failure)
1424 Test word function (including index out of bounds graceful failure)
1418
1425
1419 $ hg log -Gv -R a --template "{word('1', desc)}"
1426 $ hg log -Gv -R a --template "{word('1', desc)}"
1420 @ add,
1427 @ add,
1421 |
1428 |
1422 o
1429 o
1423 |
1430 |
1424 o
1431 o
1425 |
1432 |
1426 o
1433 o
1427
1434
1428 o
1435 o
1429 |\
1436 |\
1430 | o head
1437 | o head
1431 | |
1438 | |
1432 o | branch
1439 o | branch
1433 |/
1440 |/
1434 o user,
1441 o user,
1435 |
1442 |
1436 o person
1443 o person
1437 |
1444 |
1438 o 1
1445 o 1
1439 |
1446 |
1440 o 1
1447 o 1
1441
1448
1442
1449
1443 Test word third parameter used as splitter
1450 Test word third parameter used as splitter
1444
1451
1445 $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
1452 $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
1446 @ M
1453 @ M
1447 |
1454 |
1448 o future
1455 o future
1449 |
1456 |
1450 o third
1457 o third
1451 |
1458 |
1452 o sec
1459 o sec
1453
1460
1454 o merge
1461 o merge
1455 |\
1462 |\
1456 | o new head
1463 | o new head
1457 | |
1464 | |
1458 o | new branch
1465 o | new branch
1459 |/
1466 |/
1460 o n
1467 o n
1461 |
1468 |
1462 o n
1469 o n
1463 |
1470 |
1464 o
1471 o
1465 |
1472 |
1466 o line 1
1473 o line 1
1467 line 2
1474 line 2
1468
1475
1469 Test word error messages for not enough and too many arguments
1476 Test word error messages for not enough and too many arguments
1470
1477
1471 $ hg log -Gv -R a --template "{word('0')}"
1478 $ hg log -Gv -R a --template "{word('0')}"
1472 hg: parse error: word expects two or three arguments, got 1
1479 hg: parse error: word expects two or three arguments, got 1
1473 [255]
1480 [255]
1474
1481
1475 $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
1482 $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
1476 hg: parse error: word expects two or three arguments, got 7
1483 hg: parse error: word expects two or three arguments, got 7
1477 [255]
1484 [255]
1478
1485
1479 Test word for integer literal
1486 Test word for integer literal
1480
1487
1481 $ hg log -R a --template "{word(2, desc)}\n" -r0
1488 $ hg log -R a --template "{word(2, desc)}\n" -r0
1482 line
1489 line
1483
1490
1484 Test word for invalid numbers
1491 Test word for invalid numbers
1485
1492
1486 $ hg log -Gv -R a --template "{word('a', desc)}"
1493 $ hg log -Gv -R a --template "{word('a', desc)}"
1487 hg: parse error: word expects an integer index
1494 hg: parse error: word expects an integer index
1488 [255]
1495 [255]
1489
1496
1490 Test word for out of range
1497 Test word for out of range
1491
1498
1492 $ hg log -R a --template "{word(10000, desc)}"
1499 $ hg log -R a --template "{word(10000, desc)}"
1493 $ hg log -R a --template "{word(-10000, desc)}"
1500 $ hg log -R a --template "{word(-10000, desc)}"
1494
1501
1495 Test indent and not adding to empty lines
1502 Test indent and not adding to empty lines
1496
1503
1497 $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
1504 $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
1498 -----
1505 -----
1499 > line 1
1506 > line 1
1500 >> line 2
1507 >> line 2
1501 -----
1508 -----
1502 > other 1
1509 > other 1
1503 >> other 2
1510 >> other 2
1504
1511
1505 >> other 3
1512 >> other 3
1506
1513
1507 Test indent with empty first line
1514 Test indent with empty first line
1508
1515
1509 $ hg version -T "{indent('', '>> ')}\n"
1516 $ hg version -T "{indent('', '>> ')}\n"
1510
1517
1511
1518
1512 $ hg version -T "{indent('
1519 $ hg version -T "{indent('
1513 > second', '>> ')}\n"
1520 > second', '>> ')}\n"
1514
1521
1515 >> second
1522 >> second
1516
1523
1517 $ hg version -T "{indent('
1524 $ hg version -T "{indent('
1518 > second', '>> ', ' > ')}\n"
1525 > second', '>> ', ' > ')}\n"
1519
1526
1520 >> second
1527 >> second
1521
1528
1522 Test with non-strings like dates
1529 Test with non-strings like dates
1523
1530
1524 $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a
1531 $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a
1525 1200000.00
1532 1200000.00
1526 1300000.00
1533 1300000.00
1527
1534
1528 Test cbor filter:
1535 Test cbor filter:
1529
1536
1530 $ cat <<'EOF' > "$TESTTMP/decodecbor.py"
1537 $ cat <<'EOF' > "$TESTTMP/decodecbor.py"
1531 > from __future__ import absolute_import
1538 > from __future__ import absolute_import
1532 > from mercurial import (
1539 > from mercurial import (
1533 > dispatch,
1540 > dispatch,
1534 > pycompat,
1541 > pycompat,
1535 > )
1542 > )
1536 > from mercurial.utils import (
1543 > from mercurial.utils import (
1537 > cborutil,
1544 > cborutil,
1538 > stringutil,
1545 > stringutil,
1539 > )
1546 > )
1540 > dispatch.initstdio()
1547 > dispatch.initstdio()
1541 > items = cborutil.decodeall(pycompat.stdin.read())
1548 > items = cborutil.decodeall(pycompat.stdin.read())
1542 > pycompat.stdout.write(stringutil.pprint(items, indent=1) + b'\n')
1549 > pycompat.stdout.write(stringutil.pprint(items, indent=1) + b'\n')
1543 > EOF
1550 > EOF
1544
1551
1545 $ hg log -T "{rev|cbor}" -R a -l2 | "$PYTHON" "$TESTTMP/decodecbor.py"
1552 $ hg log -T "{rev|cbor}" -R a -l2 | "$PYTHON" "$TESTTMP/decodecbor.py"
1546 [
1553 [
1547 10,
1554 10,
1548 9
1555 9
1549 ]
1556 ]
1550
1557
1551 $ hg log -T "{extras|cbor}" -R a -l1 | "$PYTHON" "$TESTTMP/decodecbor.py"
1558 $ hg log -T "{extras|cbor}" -R a -l1 | "$PYTHON" "$TESTTMP/decodecbor.py"
1552 [
1559 [
1553 {
1560 {
1554 'branch': 'default'
1561 'branch': 'default'
1555 }
1562 }
1556 ]
1563 ]
1557
1564
1558 json filter should escape HTML tags so that the output can be embedded in hgweb:
1565 json filter should escape HTML tags so that the output can be embedded in hgweb:
1559
1566
1560 $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
1567 $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
1561 "\u003cfoo@example.org\u003e"
1568 "\u003cfoo@example.org\u003e"
1562
1569
1563 Set up repository for non-ascii encoding tests:
1570 Set up repository for non-ascii encoding tests:
1564
1571
1565 $ hg init nonascii
1572 $ hg init nonascii
1566 $ cd nonascii
1573 $ cd nonascii
1567 $ "$PYTHON" <<EOF
1574 $ "$PYTHON" <<EOF
1568 > open('latin1', 'wb').write(b'\xe9')
1575 > open('latin1', 'wb').write(b'\xe9')
1569 > open('utf-8', 'wb').write(b'\xc3\xa9')
1576 > open('utf-8', 'wb').write(b'\xc3\xa9')
1570 > EOF
1577 > EOF
1571 $ HGENCODING=utf-8 hg branch -q `cat utf-8`
1578 $ HGENCODING=utf-8 hg branch -q `cat utf-8`
1572 $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
1579 $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
1573
1580
1574 json filter should try round-trip conversion to utf-8:
1581 json filter should try round-trip conversion to utf-8:
1575
1582
1576 $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
1583 $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
1577 "\u00e9"
1584 "\u00e9"
1578 $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
1585 $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
1579 "non-ascii branch: \u00e9"
1586 "non-ascii branch: \u00e9"
1580
1587
1581 json filter should take input as utf-8 if it was converted from utf-8:
1588 json filter should take input as utf-8 if it was converted from utf-8:
1582
1589
1583 $ HGENCODING=latin-1 hg log -T "{branch|json}\n" -r0
1590 $ HGENCODING=latin-1 hg log -T "{branch|json}\n" -r0
1584 "\u00e9"
1591 "\u00e9"
1585 $ HGENCODING=latin-1 hg log -T "{desc|json}\n" -r0
1592 $ HGENCODING=latin-1 hg log -T "{desc|json}\n" -r0
1586 "non-ascii branch: \u00e9"
1593 "non-ascii branch: \u00e9"
1587
1594
1588 json filter takes input as utf-8b:
1595 json filter takes input as utf-8b:
1589
1596
1590 $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
1597 $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
1591 "\u00e9"
1598 "\u00e9"
1592 $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
1599 $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
1593 "\udce9"
1600 "\udce9"
1594
1601
1595 cbor filter is bytes transparent, which should handle bytes subtypes
1602 cbor filter is bytes transparent, which should handle bytes subtypes
1596 as bytes:
1603 as bytes:
1597
1604
1598 $ HGENCODING=ascii hg log -T "{branch|cbor}" -r0 \
1605 $ HGENCODING=ascii hg log -T "{branch|cbor}" -r0 \
1599 > | "$PYTHON" "$TESTTMP/decodecbor.py"
1606 > | "$PYTHON" "$TESTTMP/decodecbor.py"
1600 [
1607 [
1601 '?'
1608 '?'
1602 ]
1609 ]
1603 $ HGENCODING=latin-1 hg log -T "{branch|cbor}" -r0 \
1610 $ HGENCODING=latin-1 hg log -T "{branch|cbor}" -r0 \
1604 > | "$PYTHON" "$TESTTMP/decodecbor.py"
1611 > | "$PYTHON" "$TESTTMP/decodecbor.py"
1605 [
1612 [
1606 '\xe9'
1613 '\xe9'
1607 ]
1614 ]
1608
1615
1609 utf8 filter:
1616 utf8 filter:
1610
1617
1611 $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
1618 $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
1612 round-trip: c3a9
1619 round-trip: c3a9
1613 $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
1620 $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
1614 decoded: c3a9
1621 decoded: c3a9
1615 $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
1622 $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
1616 abort: decoding near * (glob)
1623 abort: decoding near * (glob)
1617 [255]
1624 [255]
1618 $ hg log -T "coerced to string: {rev|utf8}\n" -r0
1625 $ hg log -T "coerced to string: {rev|utf8}\n" -r0
1619 coerced to string: 0
1626 coerced to string: 0
1620
1627
1621 pad width:
1628 pad width:
1622
1629
1623 $ HGENCODING=utf-8 hg debugtemplate "{pad('`cat utf-8`', 2, '-')}\n"
1630 $ HGENCODING=utf-8 hg debugtemplate "{pad('`cat utf-8`', 2, '-')}\n"
1624 \xc3\xa9- (esc)
1631 \xc3\xa9- (esc)
1625
1632
1626 read config options:
1633 read config options:
1627
1634
1628 $ hg log -T "{config('templateconfig', 'knob', 'foo')}\n"
1635 $ hg log -T "{config('templateconfig', 'knob', 'foo')}\n"
1629 foo
1636 foo
1630 $ hg log -T "{config('templateconfig', 'knob', 'foo')}\n" \
1637 $ hg log -T "{config('templateconfig', 'knob', 'foo')}\n" \
1631 > --config templateconfig.knob=bar
1638 > --config templateconfig.knob=bar
1632 bar
1639 bar
1633 $ hg log -T "{configbool('templateconfig', 'knob', True)}\n"
1640 $ hg log -T "{configbool('templateconfig', 'knob', True)}\n"
1634 True
1641 True
1635 $ hg log -T "{configbool('templateconfig', 'knob', True)}\n" \
1642 $ hg log -T "{configbool('templateconfig', 'knob', True)}\n" \
1636 > --config templateconfig.knob=0
1643 > --config templateconfig.knob=0
1637 False
1644 False
1638 $ hg log -T "{configint('templateconfig', 'knob', 123)}\n"
1645 $ hg log -T "{configint('templateconfig', 'knob', 123)}\n"
1639 123
1646 123
1640 $ hg log -T "{configint('templateconfig', 'knob', 123)}\n" \
1647 $ hg log -T "{configint('templateconfig', 'knob', 123)}\n" \
1641 > --config templateconfig.knob=456
1648 > --config templateconfig.knob=456
1642 456
1649 456
1643 $ hg log -T "{config('templateconfig', 'knob')}\n"
1650 $ hg log -T "{config('templateconfig', 'knob')}\n"
1644 devel-warn: config item requires an explicit default value: 'templateconfig.knob' at: * (glob)
1651 devel-warn: config item requires an explicit default value: 'templateconfig.knob' at: * (glob)
1645
1652
1646 $ hg log -T "{configbool('ui', 'interactive')}\n"
1653 $ hg log -T "{configbool('ui', 'interactive')}\n"
1647 False
1654 False
1648 $ hg log -T "{configbool('ui', 'interactive')}\n" --config ui.interactive=1
1655 $ hg log -T "{configbool('ui', 'interactive')}\n" --config ui.interactive=1
1649 True
1656 True
1650 $ hg log -T "{config('templateconfig', 'knob', if(true, 'foo', 'bar'))}\n"
1657 $ hg log -T "{config('templateconfig', 'knob', if(true, 'foo', 'bar'))}\n"
1651 foo
1658 foo
1652
1659
1653 $ cd ..
1660 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now