##// END OF EJS Templates
revset: add an assertion to help pytype...
Matt Harbison -
r44119:6c6d67fc default
parent child Browse files
Show More
@@ -1,2693 +1,2695 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
688 assert m is not None # help pytype
687 if not m.anypats() and len(m.files()) == 1:
689 if not m.anypats() and len(m.files()) == 1:
688 fname = m.files()[0]
690 fname = m.files()[0]
689 if fname is not None:
691 if fname is not None:
690 if fname not in c.files():
692 if fname not in c.files():
691 return False
693 return False
692 else:
694 else:
693 if not any(m(f) for f in c.files()):
695 if not any(m(f) for f in c.files()):
694 return False
696 return False
695 files = getattr(repo.status(c.p1().node(), c.node()), label)
697 files = getattr(repo.status(c.p1().node(), c.node()), label)
696 if fname is not None:
698 if fname is not None:
697 if fname in files:
699 if fname in files:
698 return True
700 return True
699 else:
701 else:
700 if any(m(f) for f in files):
702 if any(m(f) for f in files):
701 return True
703 return True
702
704
703 return subset.filter(matches, condrepr=(b'<status[%r] %r>', field, pat))
705 return subset.filter(matches, condrepr=(b'<status[%r] %r>', field, pat))
704
706
705
707
706 def _children(repo, subset, parentset):
708 def _children(repo, subset, parentset):
707 if not parentset:
709 if not parentset:
708 return baseset()
710 return baseset()
709 cs = set()
711 cs = set()
710 pr = repo.changelog.parentrevs
712 pr = repo.changelog.parentrevs
711 minrev = parentset.min()
713 minrev = parentset.min()
712 nullrev = node.nullrev
714 nullrev = node.nullrev
713 for r in subset:
715 for r in subset:
714 if r <= minrev:
716 if r <= minrev:
715 continue
717 continue
716 p1, p2 = pr(r)
718 p1, p2 = pr(r)
717 if p1 in parentset:
719 if p1 in parentset:
718 cs.add(r)
720 cs.add(r)
719 if p2 != nullrev and p2 in parentset:
721 if p2 != nullrev and p2 in parentset:
720 cs.add(r)
722 cs.add(r)
721 return baseset(cs)
723 return baseset(cs)
722
724
723
725
724 @predicate(b'children(set)', safe=True)
726 @predicate(b'children(set)', safe=True)
725 def children(repo, subset, x):
727 def children(repo, subset, x):
726 """Child changesets of changesets in set.
728 """Child changesets of changesets in set.
727 """
729 """
728 s = getset(repo, fullreposet(repo), x)
730 s = getset(repo, fullreposet(repo), x)
729 cs = _children(repo, subset, s)
731 cs = _children(repo, subset, s)
730 return subset & cs
732 return subset & cs
731
733
732
734
733 @predicate(b'closed()', safe=True, weight=10)
735 @predicate(b'closed()', safe=True, weight=10)
734 def closed(repo, subset, x):
736 def closed(repo, subset, x):
735 """Changeset is closed.
737 """Changeset is closed.
736 """
738 """
737 # i18n: "closed" is a keyword
739 # i18n: "closed" is a keyword
738 getargs(x, 0, 0, _(b"closed takes no arguments"))
740 getargs(x, 0, 0, _(b"closed takes no arguments"))
739 return subset.filter(
741 return subset.filter(
740 lambda r: repo[r].closesbranch(), condrepr=b'<branch closed>'
742 lambda r: repo[r].closesbranch(), condrepr=b'<branch closed>'
741 )
743 )
742
744
743
745
744 # for internal use
746 # for internal use
745 @predicate(b'_commonancestorheads(set)', safe=True)
747 @predicate(b'_commonancestorheads(set)', safe=True)
746 def _commonancestorheads(repo, subset, x):
748 def _commonancestorheads(repo, subset, x):
747 # 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
748 # ::y)"
750 # ::y)"
749
751
750 # 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
751 # merge will find.
753 # merge will find.
752 startrevs = getset(repo, fullreposet(repo), x, order=anyorder)
754 startrevs = getset(repo, fullreposet(repo), x, order=anyorder)
753
755
754 ancs = repo.changelog._commonancestorsheads(*list(startrevs))
756 ancs = repo.changelog._commonancestorsheads(*list(startrevs))
755 return subset & baseset(ancs)
757 return subset & baseset(ancs)
756
758
757
759
758 @predicate(b'commonancestors(set)', safe=True)
760 @predicate(b'commonancestors(set)', safe=True)
759 def commonancestors(repo, subset, x):
761 def commonancestors(repo, subset, x):
760 """Changesets that are ancestors of every changeset in set.
762 """Changesets that are ancestors of every changeset in set.
761 """
763 """
762 startrevs = getset(repo, fullreposet(repo), x, order=anyorder)
764 startrevs = getset(repo, fullreposet(repo), x, order=anyorder)
763 if not startrevs:
765 if not startrevs:
764 return baseset()
766 return baseset()
765 for r in startrevs:
767 for r in startrevs:
766 subset &= dagop.revancestors(repo, baseset([r]))
768 subset &= dagop.revancestors(repo, baseset([r]))
767 return subset
769 return subset
768
770
769
771
770 @predicate(b'contains(pattern)', weight=100)
772 @predicate(b'contains(pattern)', weight=100)
771 def contains(repo, subset, x):
773 def contains(repo, subset, x):
772 """The revision's manifest contains a file matching pattern (but might not
774 """The revision's manifest contains a file matching pattern (but might not
773 modify it). See :hg:`help patterns` for information about file patterns.
775 modify it). See :hg:`help patterns` for information about file patterns.
774
776
775 The pattern without explicit kind like ``glob:`` is expected to be
777 The pattern without explicit kind like ``glob:`` is expected to be
776 relative to the current directory and match against a file exactly
778 relative to the current directory and match against a file exactly
777 for efficiency.
779 for efficiency.
778 """
780 """
779 # i18n: "contains" is a keyword
781 # i18n: "contains" is a keyword
780 pat = getstring(x, _(b"contains requires a pattern"))
782 pat = getstring(x, _(b"contains requires a pattern"))
781
783
782 def matches(x):
784 def matches(x):
783 if not matchmod.patkind(pat):
785 if not matchmod.patkind(pat):
784 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
786 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
785 if pats in repo[x]:
787 if pats in repo[x]:
786 return True
788 return True
787 else:
789 else:
788 c = repo[x]
790 c = repo[x]
789 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
791 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
790 for f in c.manifest():
792 for f in c.manifest():
791 if m(f):
793 if m(f):
792 return True
794 return True
793 return False
795 return False
794
796
795 return subset.filter(matches, condrepr=(b'<contains %r>', pat))
797 return subset.filter(matches, condrepr=(b'<contains %r>', pat))
796
798
797
799
798 @predicate(b'converted([id])', safe=True)
800 @predicate(b'converted([id])', safe=True)
799 def converted(repo, subset, x):
801 def converted(repo, subset, x):
800 """Changesets converted from the given identifier in the old repository if
802 """Changesets converted from the given identifier in the old repository if
801 present, or all converted changesets if no identifier is specified.
803 present, or all converted changesets if no identifier is specified.
802 """
804 """
803
805
804 # There is exactly no chance of resolving the revision, so do a simple
806 # There is exactly no chance of resolving the revision, so do a simple
805 # string compare and hope for the best
807 # string compare and hope for the best
806
808
807 rev = None
809 rev = None
808 # i18n: "converted" is a keyword
810 # i18n: "converted" is a keyword
809 l = getargs(x, 0, 1, _(b'converted takes one or no arguments'))
811 l = getargs(x, 0, 1, _(b'converted takes one or no arguments'))
810 if l:
812 if l:
811 # i18n: "converted" is a keyword
813 # i18n: "converted" is a keyword
812 rev = getstring(l[0], _(b'converted requires a revision'))
814 rev = getstring(l[0], _(b'converted requires a revision'))
813
815
814 def _matchvalue(r):
816 def _matchvalue(r):
815 source = repo[r].extra().get(b'convert_revision', None)
817 source = repo[r].extra().get(b'convert_revision', None)
816 return source is not None and (rev is None or source.startswith(rev))
818 return source is not None and (rev is None or source.startswith(rev))
817
819
818 return subset.filter(
820 return subset.filter(
819 lambda r: _matchvalue(r), condrepr=(b'<converted %r>', rev)
821 lambda r: _matchvalue(r), condrepr=(b'<converted %r>', rev)
820 )
822 )
821
823
822
824
823 @predicate(b'date(interval)', safe=True, weight=10)
825 @predicate(b'date(interval)', safe=True, weight=10)
824 def date(repo, subset, x):
826 def date(repo, subset, x):
825 """Changesets within the interval, see :hg:`help dates`.
827 """Changesets within the interval, see :hg:`help dates`.
826 """
828 """
827 # i18n: "date" is a keyword
829 # i18n: "date" is a keyword
828 ds = getstring(x, _(b"date requires a string"))
830 ds = getstring(x, _(b"date requires a string"))
829 dm = dateutil.matchdate(ds)
831 dm = dateutil.matchdate(ds)
830 return subset.filter(
832 return subset.filter(
831 lambda x: dm(repo[x].date()[0]), condrepr=(b'<date %r>', ds)
833 lambda x: dm(repo[x].date()[0]), condrepr=(b'<date %r>', ds)
832 )
834 )
833
835
834
836
835 @predicate(b'desc(string)', safe=True, weight=10)
837 @predicate(b'desc(string)', safe=True, weight=10)
836 def desc(repo, subset, x):
838 def desc(repo, subset, x):
837 """Search commit message for string. The match is case-insensitive.
839 """Search commit message for string. The match is case-insensitive.
838
840
839 Pattern matching is supported for `string`. See
841 Pattern matching is supported for `string`. See
840 :hg:`help revisions.patterns`.
842 :hg:`help revisions.patterns`.
841 """
843 """
842 # i18n: "desc" is a keyword
844 # i18n: "desc" is a keyword
843 ds = getstring(x, _(b"desc requires a string"))
845 ds = getstring(x, _(b"desc requires a string"))
844
846
845 kind, pattern, matcher = _substringmatcher(ds, casesensitive=False)
847 kind, pattern, matcher = _substringmatcher(ds, casesensitive=False)
846
848
847 return subset.filter(
849 return subset.filter(
848 lambda r: matcher(repo[r].description()), condrepr=(b'<desc %r>', ds)
850 lambda r: matcher(repo[r].description()), condrepr=(b'<desc %r>', ds)
849 )
851 )
850
852
851
853
852 def _descendants(
854 def _descendants(
853 repo, subset, x, followfirst=False, startdepth=None, stopdepth=None
855 repo, subset, x, followfirst=False, startdepth=None, stopdepth=None
854 ):
856 ):
855 roots = getset(repo, fullreposet(repo), x)
857 roots = getset(repo, fullreposet(repo), x)
856 if not roots:
858 if not roots:
857 return baseset()
859 return baseset()
858 s = dagop.revdescendants(repo, roots, followfirst, startdepth, stopdepth)
860 s = dagop.revdescendants(repo, roots, followfirst, startdepth, stopdepth)
859 return subset & s
861 return subset & s
860
862
861
863
862 @predicate(b'descendants(set[, depth])', safe=True)
864 @predicate(b'descendants(set[, depth])', safe=True)
863 def descendants(repo, subset, x):
865 def descendants(repo, subset, x):
864 """Changesets which are descendants of changesets in set, including the
866 """Changesets which are descendants of changesets in set, including the
865 given changesets themselves.
867 given changesets themselves.
866
868
867 If depth is specified, the result only includes changesets up to
869 If depth is specified, the result only includes changesets up to
868 the specified generation.
870 the specified generation.
869 """
871 """
870 # startdepth is for internal use only until we can decide the UI
872 # startdepth is for internal use only until we can decide the UI
871 args = getargsdict(x, b'descendants', b'set depth startdepth')
873 args = getargsdict(x, b'descendants', b'set depth startdepth')
872 if b'set' not in args:
874 if b'set' not in args:
873 # i18n: "descendants" is a keyword
875 # i18n: "descendants" is a keyword
874 raise error.ParseError(_(b'descendants takes at least 1 argument'))
876 raise error.ParseError(_(b'descendants takes at least 1 argument'))
875 startdepth = stopdepth = None
877 startdepth = stopdepth = None
876 if b'startdepth' in args:
878 if b'startdepth' in args:
877 n = getinteger(
879 n = getinteger(
878 args[b'startdepth'], b"descendants expects an integer startdepth"
880 args[b'startdepth'], b"descendants expects an integer startdepth"
879 )
881 )
880 if n < 0:
882 if n < 0:
881 raise error.ParseError(b"negative startdepth")
883 raise error.ParseError(b"negative startdepth")
882 startdepth = n
884 startdepth = n
883 if b'depth' in args:
885 if b'depth' in args:
884 # i18n: "descendants" is a keyword
886 # i18n: "descendants" is a keyword
885 n = getinteger(
887 n = getinteger(
886 args[b'depth'], _(b"descendants expects an integer depth")
888 args[b'depth'], _(b"descendants expects an integer depth")
887 )
889 )
888 if n < 0:
890 if n < 0:
889 raise error.ParseError(_(b"negative depth"))
891 raise error.ParseError(_(b"negative depth"))
890 stopdepth = n + 1
892 stopdepth = n + 1
891 return _descendants(
893 return _descendants(
892 repo, subset, args[b'set'], startdepth=startdepth, stopdepth=stopdepth
894 repo, subset, args[b'set'], startdepth=startdepth, stopdepth=stopdepth
893 )
895 )
894
896
895
897
896 @predicate(b'_firstdescendants', safe=True)
898 @predicate(b'_firstdescendants', safe=True)
897 def _firstdescendants(repo, subset, x):
899 def _firstdescendants(repo, subset, x):
898 # ``_firstdescendants(set)``
900 # ``_firstdescendants(set)``
899 # Like ``descendants(set)`` but follows only the first parents.
901 # Like ``descendants(set)`` but follows only the first parents.
900 return _descendants(repo, subset, x, followfirst=True)
902 return _descendants(repo, subset, x, followfirst=True)
901
903
902
904
903 @predicate(b'destination([set])', safe=True, weight=10)
905 @predicate(b'destination([set])', safe=True, weight=10)
904 def destination(repo, subset, x):
906 def destination(repo, subset, x):
905 """Changesets that were created by a graft, transplant or rebase operation,
907 """Changesets that were created by a graft, transplant or rebase operation,
906 with the given revisions specified as the source. Omitting the optional set
908 with the given revisions specified as the source. Omitting the optional set
907 is the same as passing all().
909 is the same as passing all().
908 """
910 """
909 if x is not None:
911 if x is not None:
910 sources = getset(repo, fullreposet(repo), x)
912 sources = getset(repo, fullreposet(repo), x)
911 else:
913 else:
912 sources = fullreposet(repo)
914 sources = fullreposet(repo)
913
915
914 dests = set()
916 dests = set()
915
917
916 # subset contains all of the possible destinations that can be returned, so
918 # subset contains all of the possible destinations that can be returned, so
917 # iterate over them and see if their source(s) were provided in the arg set.
919 # iterate over them and see if their source(s) were provided in the arg set.
918 # Even if the immediate src of r is not in the arg set, src's source (or
920 # Even if the immediate src of r is not in the arg set, src's source (or
919 # further back) may be. Scanning back further than the immediate src allows
921 # further back) may be. Scanning back further than the immediate src allows
920 # transitive transplants and rebases to yield the same results as transitive
922 # transitive transplants and rebases to yield the same results as transitive
921 # grafts.
923 # grafts.
922 for r in subset:
924 for r in subset:
923 src = _getrevsource(repo, r)
925 src = _getrevsource(repo, r)
924 lineage = None
926 lineage = None
925
927
926 while src is not None:
928 while src is not None:
927 if lineage is None:
929 if lineage is None:
928 lineage = list()
930 lineage = list()
929
931
930 lineage.append(r)
932 lineage.append(r)
931
933
932 # The visited lineage is a match if the current source is in the arg
934 # The visited lineage is a match if the current source is in the arg
933 # set. Since every candidate dest is visited by way of iterating
935 # set. Since every candidate dest is visited by way of iterating
934 # subset, any dests further back in the lineage will be tested by a
936 # subset, any dests further back in the lineage will be tested by a
935 # different iteration over subset. Likewise, if the src was already
937 # different iteration over subset. Likewise, if the src was already
936 # selected, the current lineage can be selected without going back
938 # selected, the current lineage can be selected without going back
937 # further.
939 # further.
938 if src in sources or src in dests:
940 if src in sources or src in dests:
939 dests.update(lineage)
941 dests.update(lineage)
940 break
942 break
941
943
942 r = src
944 r = src
943 src = _getrevsource(repo, r)
945 src = _getrevsource(repo, r)
944
946
945 return subset.filter(
947 return subset.filter(
946 dests.__contains__,
948 dests.__contains__,
947 condrepr=lambda: b'<destination %r>' % _sortedb(dests),
949 condrepr=lambda: b'<destination %r>' % _sortedb(dests),
948 )
950 )
949
951
950
952
951 @predicate(b'contentdivergent()', safe=True)
953 @predicate(b'contentdivergent()', safe=True)
952 def contentdivergent(repo, subset, x):
954 def contentdivergent(repo, subset, x):
953 """
955 """
954 Final successors of changesets with an alternative set of final
956 Final successors of changesets with an alternative set of final
955 successors. (EXPERIMENTAL)
957 successors. (EXPERIMENTAL)
956 """
958 """
957 # i18n: "contentdivergent" is a keyword
959 # i18n: "contentdivergent" is a keyword
958 getargs(x, 0, 0, _(b"contentdivergent takes no arguments"))
960 getargs(x, 0, 0, _(b"contentdivergent takes no arguments"))
959 contentdivergent = obsmod.getrevs(repo, b'contentdivergent')
961 contentdivergent = obsmod.getrevs(repo, b'contentdivergent')
960 return subset & contentdivergent
962 return subset & contentdivergent
961
963
962
964
963 @predicate(b'expectsize(set[, size])', safe=True, takeorder=True)
965 @predicate(b'expectsize(set[, size])', safe=True, takeorder=True)
964 def expectsize(repo, subset, x, order):
966 def expectsize(repo, subset, x, order):
965 """Return the given revset if size matches the revset size.
967 """Return the given revset if size matches the revset size.
966 Abort if the revset doesn't expect given size.
968 Abort if the revset doesn't expect given size.
967 size can either be an integer range or an integer.
969 size can either be an integer range or an integer.
968
970
969 For example, ``expectsize(0:1, 3:5)`` will abort as revset size is 2 and
971 For example, ``expectsize(0:1, 3:5)`` will abort as revset size is 2 and
970 2 is not between 3 and 5 inclusive."""
972 2 is not between 3 and 5 inclusive."""
971
973
972 args = getargsdict(x, b'expectsize', b'set size')
974 args = getargsdict(x, b'expectsize', b'set size')
973 minsize = 0
975 minsize = 0
974 maxsize = len(repo) + 1
976 maxsize = len(repo) + 1
975 err = b''
977 err = b''
976 if b'size' not in args or b'set' not in args:
978 if b'size' not in args or b'set' not in args:
977 raise error.ParseError(_(b'invalid set of arguments'))
979 raise error.ParseError(_(b'invalid set of arguments'))
978 minsize, maxsize = getintrange(
980 minsize, maxsize = getintrange(
979 args[b'size'],
981 args[b'size'],
980 _(b'expectsize requires a size range or a positive integer'),
982 _(b'expectsize requires a size range or a positive integer'),
981 _(b'size range bounds must be integers'),
983 _(b'size range bounds must be integers'),
982 minsize,
984 minsize,
983 maxsize,
985 maxsize,
984 )
986 )
985 if minsize < 0 or maxsize < 0:
987 if minsize < 0 or maxsize < 0:
986 raise error.ParseError(_(b'negative size'))
988 raise error.ParseError(_(b'negative size'))
987 rev = getset(repo, fullreposet(repo), args[b'set'], order=order)
989 rev = getset(repo, fullreposet(repo), args[b'set'], order=order)
988 if minsize != maxsize and (len(rev) < minsize or len(rev) > maxsize):
990 if minsize != maxsize and (len(rev) < minsize or len(rev) > maxsize):
989 err = _(b'revset size mismatch. expected between %d and %d, got %d') % (
991 err = _(b'revset size mismatch. expected between %d and %d, got %d') % (
990 minsize,
992 minsize,
991 maxsize,
993 maxsize,
992 len(rev),
994 len(rev),
993 )
995 )
994 elif minsize == maxsize and len(rev) != minsize:
996 elif minsize == maxsize and len(rev) != minsize:
995 err = _(b'revset size mismatch. expected %d, got %d') % (
997 err = _(b'revset size mismatch. expected %d, got %d') % (
996 minsize,
998 minsize,
997 len(rev),
999 len(rev),
998 )
1000 )
999 if err:
1001 if err:
1000 raise error.RepoLookupError(err)
1002 raise error.RepoLookupError(err)
1001 if order == followorder:
1003 if order == followorder:
1002 return subset & rev
1004 return subset & rev
1003 else:
1005 else:
1004 return rev & subset
1006 return rev & subset
1005
1007
1006
1008
1007 @predicate(b'extdata(source)', safe=False, weight=100)
1009 @predicate(b'extdata(source)', safe=False, weight=100)
1008 def extdata(repo, subset, x):
1010 def extdata(repo, subset, x):
1009 """Changesets in the specified extdata source. (EXPERIMENTAL)"""
1011 """Changesets in the specified extdata source. (EXPERIMENTAL)"""
1010 # i18n: "extdata" is a keyword
1012 # i18n: "extdata" is a keyword
1011 args = getargsdict(x, b'extdata', b'source')
1013 args = getargsdict(x, b'extdata', b'source')
1012 source = getstring(
1014 source = getstring(
1013 args.get(b'source'),
1015 args.get(b'source'),
1014 # i18n: "extdata" is a keyword
1016 # i18n: "extdata" is a keyword
1015 _(b'extdata takes at least 1 string argument'),
1017 _(b'extdata takes at least 1 string argument'),
1016 )
1018 )
1017 data = scmutil.extdatasource(repo, source)
1019 data = scmutil.extdatasource(repo, source)
1018 return subset & baseset(data)
1020 return subset & baseset(data)
1019
1021
1020
1022
1021 @predicate(b'extinct()', safe=True)
1023 @predicate(b'extinct()', safe=True)
1022 def extinct(repo, subset, x):
1024 def extinct(repo, subset, x):
1023 """Obsolete changesets with obsolete descendants only.
1025 """Obsolete changesets with obsolete descendants only.
1024 """
1026 """
1025 # i18n: "extinct" is a keyword
1027 # i18n: "extinct" is a keyword
1026 getargs(x, 0, 0, _(b"extinct takes no arguments"))
1028 getargs(x, 0, 0, _(b"extinct takes no arguments"))
1027 extincts = obsmod.getrevs(repo, b'extinct')
1029 extincts = obsmod.getrevs(repo, b'extinct')
1028 return subset & extincts
1030 return subset & extincts
1029
1031
1030
1032
1031 @predicate(b'extra(label, [value])', safe=True)
1033 @predicate(b'extra(label, [value])', safe=True)
1032 def extra(repo, subset, x):
1034 def extra(repo, subset, x):
1033 """Changesets with the given label in the extra metadata, with the given
1035 """Changesets with the given label in the extra metadata, with the given
1034 optional value.
1036 optional value.
1035
1037
1036 Pattern matching is supported for `value`. See
1038 Pattern matching is supported for `value`. See
1037 :hg:`help revisions.patterns`.
1039 :hg:`help revisions.patterns`.
1038 """
1040 """
1039 args = getargsdict(x, b'extra', b'label value')
1041 args = getargsdict(x, b'extra', b'label value')
1040 if b'label' not in args:
1042 if b'label' not in args:
1041 # i18n: "extra" is a keyword
1043 # i18n: "extra" is a keyword
1042 raise error.ParseError(_(b'extra takes at least 1 argument'))
1044 raise error.ParseError(_(b'extra takes at least 1 argument'))
1043 # i18n: "extra" is a keyword
1045 # i18n: "extra" is a keyword
1044 label = getstring(
1046 label = getstring(
1045 args[b'label'], _(b'first argument to extra must be a string')
1047 args[b'label'], _(b'first argument to extra must be a string')
1046 )
1048 )
1047 value = None
1049 value = None
1048
1050
1049 if b'value' in args:
1051 if b'value' in args:
1050 # i18n: "extra" is a keyword
1052 # i18n: "extra" is a keyword
1051 value = getstring(
1053 value = getstring(
1052 args[b'value'], _(b'second argument to extra must be a string')
1054 args[b'value'], _(b'second argument to extra must be a string')
1053 )
1055 )
1054 kind, value, matcher = stringutil.stringmatcher(value)
1056 kind, value, matcher = stringutil.stringmatcher(value)
1055
1057
1056 def _matchvalue(r):
1058 def _matchvalue(r):
1057 extra = repo[r].extra()
1059 extra = repo[r].extra()
1058 return label in extra and (value is None or matcher(extra[label]))
1060 return label in extra and (value is None or matcher(extra[label]))
1059
1061
1060 return subset.filter(
1062 return subset.filter(
1061 lambda r: _matchvalue(r), condrepr=(b'<extra[%r] %r>', label, value)
1063 lambda r: _matchvalue(r), condrepr=(b'<extra[%r] %r>', label, value)
1062 )
1064 )
1063
1065
1064
1066
1065 @predicate(b'filelog(pattern)', safe=True)
1067 @predicate(b'filelog(pattern)', safe=True)
1066 def filelog(repo, subset, x):
1068 def filelog(repo, subset, x):
1067 """Changesets connected to the specified filelog.
1069 """Changesets connected to the specified filelog.
1068
1070
1069 For performance reasons, visits only revisions mentioned in the file-level
1071 For performance reasons, visits only revisions mentioned in the file-level
1070 filelog, rather than filtering through all changesets (much faster, but
1072 filelog, rather than filtering through all changesets (much faster, but
1071 doesn't include deletes or duplicate changes). For a slower, more accurate
1073 doesn't include deletes or duplicate changes). For a slower, more accurate
1072 result, use ``file()``.
1074 result, use ``file()``.
1073
1075
1074 The pattern without explicit kind like ``glob:`` is expected to be
1076 The pattern without explicit kind like ``glob:`` is expected to be
1075 relative to the current directory and match against a file exactly
1077 relative to the current directory and match against a file exactly
1076 for efficiency.
1078 for efficiency.
1077 """
1079 """
1078
1080
1079 # i18n: "filelog" is a keyword
1081 # i18n: "filelog" is a keyword
1080 pat = getstring(x, _(b"filelog requires a pattern"))
1082 pat = getstring(x, _(b"filelog requires a pattern"))
1081 s = set()
1083 s = set()
1082 cl = repo.changelog
1084 cl = repo.changelog
1083
1085
1084 if not matchmod.patkind(pat):
1086 if not matchmod.patkind(pat):
1085 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
1087 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
1086 files = [f]
1088 files = [f]
1087 else:
1089 else:
1088 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
1090 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
1089 files = (f for f in repo[None] if m(f))
1091 files = (f for f in repo[None] if m(f))
1090
1092
1091 for f in files:
1093 for f in files:
1092 fl = repo.file(f)
1094 fl = repo.file(f)
1093 known = {}
1095 known = {}
1094 scanpos = 0
1096 scanpos = 0
1095 for fr in list(fl):
1097 for fr in list(fl):
1096 fn = fl.node(fr)
1098 fn = fl.node(fr)
1097 if fn in known:
1099 if fn in known:
1098 s.add(known[fn])
1100 s.add(known[fn])
1099 continue
1101 continue
1100
1102
1101 lr = fl.linkrev(fr)
1103 lr = fl.linkrev(fr)
1102 if lr in cl:
1104 if lr in cl:
1103 s.add(lr)
1105 s.add(lr)
1104 elif scanpos is not None:
1106 elif scanpos is not None:
1105 # lowest matching changeset is filtered, scan further
1107 # lowest matching changeset is filtered, scan further
1106 # ahead in changelog
1108 # ahead in changelog
1107 start = max(lr, scanpos) + 1
1109 start = max(lr, scanpos) + 1
1108 scanpos = None
1110 scanpos = None
1109 for r in cl.revs(start):
1111 for r in cl.revs(start):
1110 # minimize parsing of non-matching entries
1112 # minimize parsing of non-matching entries
1111 if f in cl.revision(r) and f in cl.readfiles(r):
1113 if f in cl.revision(r) and f in cl.readfiles(r):
1112 try:
1114 try:
1113 # try to use manifest delta fastpath
1115 # try to use manifest delta fastpath
1114 n = repo[r].filenode(f)
1116 n = repo[r].filenode(f)
1115 if n not in known:
1117 if n not in known:
1116 if n == fn:
1118 if n == fn:
1117 s.add(r)
1119 s.add(r)
1118 scanpos = r
1120 scanpos = r
1119 break
1121 break
1120 else:
1122 else:
1121 known[n] = r
1123 known[n] = r
1122 except error.ManifestLookupError:
1124 except error.ManifestLookupError:
1123 # deletion in changelog
1125 # deletion in changelog
1124 continue
1126 continue
1125
1127
1126 return subset & s
1128 return subset & s
1127
1129
1128
1130
1129 @predicate(b'first(set, [n])', safe=True, takeorder=True, weight=0)
1131 @predicate(b'first(set, [n])', safe=True, takeorder=True, weight=0)
1130 def first(repo, subset, x, order):
1132 def first(repo, subset, x, order):
1131 """An alias for limit().
1133 """An alias for limit().
1132 """
1134 """
1133 return limit(repo, subset, x, order)
1135 return limit(repo, subset, x, order)
1134
1136
1135
1137
1136 def _follow(repo, subset, x, name, followfirst=False):
1138 def _follow(repo, subset, x, name, followfirst=False):
1137 args = getargsdict(x, name, b'file startrev')
1139 args = getargsdict(x, name, b'file startrev')
1138 revs = None
1140 revs = None
1139 if b'startrev' in args:
1141 if b'startrev' in args:
1140 revs = getset(repo, fullreposet(repo), args[b'startrev'])
1142 revs = getset(repo, fullreposet(repo), args[b'startrev'])
1141 if b'file' in args:
1143 if b'file' in args:
1142 x = getstring(args[b'file'], _(b"%s expected a pattern") % name)
1144 x = getstring(args[b'file'], _(b"%s expected a pattern") % name)
1143 if revs is None:
1145 if revs is None:
1144 revs = [None]
1146 revs = [None]
1145 fctxs = []
1147 fctxs = []
1146 for r in revs:
1148 for r in revs:
1147 ctx = mctx = repo[r]
1149 ctx = mctx = repo[r]
1148 if r is None:
1150 if r is None:
1149 ctx = repo[b'.']
1151 ctx = repo[b'.']
1150 m = matchmod.match(
1152 m = matchmod.match(
1151 repo.root, repo.getcwd(), [x], ctx=mctx, default=b'path'
1153 repo.root, repo.getcwd(), [x], ctx=mctx, default=b'path'
1152 )
1154 )
1153 fctxs.extend(ctx[f].introfilectx() for f in ctx.manifest().walk(m))
1155 fctxs.extend(ctx[f].introfilectx() for f in ctx.manifest().walk(m))
1154 s = dagop.filerevancestors(fctxs, followfirst)
1156 s = dagop.filerevancestors(fctxs, followfirst)
1155 else:
1157 else:
1156 if revs is None:
1158 if revs is None:
1157 revs = baseset([repo[b'.'].rev()])
1159 revs = baseset([repo[b'.'].rev()])
1158 s = dagop.revancestors(repo, revs, followfirst)
1160 s = dagop.revancestors(repo, revs, followfirst)
1159
1161
1160 return subset & s
1162 return subset & s
1161
1163
1162
1164
1163 @predicate(b'follow([file[, startrev]])', safe=True)
1165 @predicate(b'follow([file[, startrev]])', safe=True)
1164 def follow(repo, subset, x):
1166 def follow(repo, subset, x):
1165 """
1167 """
1166 An alias for ``::.`` (ancestors of the working directory's first parent).
1168 An alias for ``::.`` (ancestors of the working directory's first parent).
1167 If file pattern is specified, the histories of files matching given
1169 If file pattern is specified, the histories of files matching given
1168 pattern in the revision given by startrev are followed, including copies.
1170 pattern in the revision given by startrev are followed, including copies.
1169 """
1171 """
1170 return _follow(repo, subset, x, b'follow')
1172 return _follow(repo, subset, x, b'follow')
1171
1173
1172
1174
1173 @predicate(b'_followfirst', safe=True)
1175 @predicate(b'_followfirst', safe=True)
1174 def _followfirst(repo, subset, x):
1176 def _followfirst(repo, subset, x):
1175 # ``followfirst([file[, startrev]])``
1177 # ``followfirst([file[, startrev]])``
1176 # Like ``follow([file[, startrev]])`` but follows only the first parent
1178 # Like ``follow([file[, startrev]])`` but follows only the first parent
1177 # of every revisions or files revisions.
1179 # of every revisions or files revisions.
1178 return _follow(repo, subset, x, b'_followfirst', followfirst=True)
1180 return _follow(repo, subset, x, b'_followfirst', followfirst=True)
1179
1181
1180
1182
1181 @predicate(
1183 @predicate(
1182 b'followlines(file, fromline:toline[, startrev=., descend=False])',
1184 b'followlines(file, fromline:toline[, startrev=., descend=False])',
1183 safe=True,
1185 safe=True,
1184 )
1186 )
1185 def followlines(repo, subset, x):
1187 def followlines(repo, subset, x):
1186 """Changesets modifying `file` in line range ('fromline', 'toline').
1188 """Changesets modifying `file` in line range ('fromline', 'toline').
1187
1189
1188 Line range corresponds to 'file' content at 'startrev' and should hence be
1190 Line range corresponds to 'file' content at 'startrev' and should hence be
1189 consistent with file size. If startrev is not specified, working directory's
1191 consistent with file size. If startrev is not specified, working directory's
1190 parent is used.
1192 parent is used.
1191
1193
1192 By default, ancestors of 'startrev' are returned. If 'descend' is True,
1194 By default, ancestors of 'startrev' are returned. If 'descend' is True,
1193 descendants of 'startrev' are returned though renames are (currently) not
1195 descendants of 'startrev' are returned though renames are (currently) not
1194 followed in this direction.
1196 followed in this direction.
1195 """
1197 """
1196 args = getargsdict(x, b'followlines', b'file *lines startrev descend')
1198 args = getargsdict(x, b'followlines', b'file *lines startrev descend')
1197 if len(args[b'lines']) != 1:
1199 if len(args[b'lines']) != 1:
1198 raise error.ParseError(_(b"followlines requires a line range"))
1200 raise error.ParseError(_(b"followlines requires a line range"))
1199
1201
1200 rev = b'.'
1202 rev = b'.'
1201 if b'startrev' in args:
1203 if b'startrev' in args:
1202 revs = getset(repo, fullreposet(repo), args[b'startrev'])
1204 revs = getset(repo, fullreposet(repo), args[b'startrev'])
1203 if len(revs) != 1:
1205 if len(revs) != 1:
1204 raise error.ParseError(
1206 raise error.ParseError(
1205 # i18n: "followlines" is a keyword
1207 # i18n: "followlines" is a keyword
1206 _(b"followlines expects exactly one revision")
1208 _(b"followlines expects exactly one revision")
1207 )
1209 )
1208 rev = revs.last()
1210 rev = revs.last()
1209
1211
1210 pat = getstring(args[b'file'], _(b"followlines requires a pattern"))
1212 pat = getstring(args[b'file'], _(b"followlines requires a pattern"))
1211 # i18n: "followlines" is a keyword
1213 # i18n: "followlines" is a keyword
1212 msg = _(b"followlines expects exactly one file")
1214 msg = _(b"followlines expects exactly one file")
1213 fname = scmutil.parsefollowlinespattern(repo, rev, pat, msg)
1215 fname = scmutil.parsefollowlinespattern(repo, rev, pat, msg)
1214 fromline, toline = util.processlinerange(
1216 fromline, toline = util.processlinerange(
1215 *getintrange(
1217 *getintrange(
1216 args[b'lines'][0],
1218 args[b'lines'][0],
1217 # i18n: "followlines" is a keyword
1219 # i18n: "followlines" is a keyword
1218 _(b"followlines expects a line number or a range"),
1220 _(b"followlines expects a line number or a range"),
1219 _(b"line range bounds must be integers"),
1221 _(b"line range bounds must be integers"),
1220 )
1222 )
1221 )
1223 )
1222
1224
1223 fctx = repo[rev].filectx(fname)
1225 fctx = repo[rev].filectx(fname)
1224 descend = False
1226 descend = False
1225 if b'descend' in args:
1227 if b'descend' in args:
1226 descend = getboolean(
1228 descend = getboolean(
1227 args[b'descend'],
1229 args[b'descend'],
1228 # i18n: "descend" is a keyword
1230 # i18n: "descend" is a keyword
1229 _(b"descend argument must be a boolean"),
1231 _(b"descend argument must be a boolean"),
1230 )
1232 )
1231 if descend:
1233 if descend:
1232 rs = generatorset(
1234 rs = generatorset(
1233 (
1235 (
1234 c.rev()
1236 c.rev()
1235 for c, _linerange in dagop.blockdescendants(
1237 for c, _linerange in dagop.blockdescendants(
1236 fctx, fromline, toline
1238 fctx, fromline, toline
1237 )
1239 )
1238 ),
1240 ),
1239 iterasc=True,
1241 iterasc=True,
1240 )
1242 )
1241 else:
1243 else:
1242 rs = generatorset(
1244 rs = generatorset(
1243 (
1245 (
1244 c.rev()
1246 c.rev()
1245 for c, _linerange in dagop.blockancestors(
1247 for c, _linerange in dagop.blockancestors(
1246 fctx, fromline, toline
1248 fctx, fromline, toline
1247 )
1249 )
1248 ),
1250 ),
1249 iterasc=False,
1251 iterasc=False,
1250 )
1252 )
1251 return subset & rs
1253 return subset & rs
1252
1254
1253
1255
1254 @predicate(b'all()', safe=True)
1256 @predicate(b'all()', safe=True)
1255 def getall(repo, subset, x):
1257 def getall(repo, subset, x):
1256 """All changesets, the same as ``0:tip``.
1258 """All changesets, the same as ``0:tip``.
1257 """
1259 """
1258 # i18n: "all" is a keyword
1260 # i18n: "all" is a keyword
1259 getargs(x, 0, 0, _(b"all takes no arguments"))
1261 getargs(x, 0, 0, _(b"all takes no arguments"))
1260 return subset & spanset(repo) # drop "null" if any
1262 return subset & spanset(repo) # drop "null" if any
1261
1263
1262
1264
1263 @predicate(b'grep(regex)', weight=10)
1265 @predicate(b'grep(regex)', weight=10)
1264 def grep(repo, subset, x):
1266 def grep(repo, subset, x):
1265 """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1267 """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1266 to ensure special escape characters are handled correctly. Unlike
1268 to ensure special escape characters are handled correctly. Unlike
1267 ``keyword(string)``, the match is case-sensitive.
1269 ``keyword(string)``, the match is case-sensitive.
1268 """
1270 """
1269 try:
1271 try:
1270 # i18n: "grep" is a keyword
1272 # i18n: "grep" is a keyword
1271 gr = re.compile(getstring(x, _(b"grep requires a string")))
1273 gr = re.compile(getstring(x, _(b"grep requires a string")))
1272 except re.error as e:
1274 except re.error as e:
1273 raise error.ParseError(
1275 raise error.ParseError(
1274 _(b'invalid match pattern: %s') % stringutil.forcebytestr(e)
1276 _(b'invalid match pattern: %s') % stringutil.forcebytestr(e)
1275 )
1277 )
1276
1278
1277 def matches(x):
1279 def matches(x):
1278 c = repo[x]
1280 c = repo[x]
1279 for e in c.files() + [c.user(), c.description()]:
1281 for e in c.files() + [c.user(), c.description()]:
1280 if gr.search(e):
1282 if gr.search(e):
1281 return True
1283 return True
1282 return False
1284 return False
1283
1285
1284 return subset.filter(matches, condrepr=(b'<grep %r>', gr.pattern))
1286 return subset.filter(matches, condrepr=(b'<grep %r>', gr.pattern))
1285
1287
1286
1288
1287 @predicate(b'_matchfiles', safe=True)
1289 @predicate(b'_matchfiles', safe=True)
1288 def _matchfiles(repo, subset, x):
1290 def _matchfiles(repo, subset, x):
1289 # _matchfiles takes a revset list of prefixed arguments:
1291 # _matchfiles takes a revset list of prefixed arguments:
1290 #
1292 #
1291 # [p:foo, i:bar, x:baz]
1293 # [p:foo, i:bar, x:baz]
1292 #
1294 #
1293 # builds a match object from them and filters subset. Allowed
1295 # builds a match object from them and filters subset. Allowed
1294 # prefixes are 'p:' for regular patterns, 'i:' for include
1296 # prefixes are 'p:' for regular patterns, 'i:' for include
1295 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1297 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1296 # a revision identifier, or the empty string to reference the
1298 # a revision identifier, or the empty string to reference the
1297 # working directory, from which the match object is
1299 # working directory, from which the match object is
1298 # initialized. Use 'd:' to set the default matching mode, default
1300 # initialized. Use 'd:' to set the default matching mode, default
1299 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1301 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1300
1302
1301 l = getargs(x, 1, -1, b"_matchfiles requires at least one argument")
1303 l = getargs(x, 1, -1, b"_matchfiles requires at least one argument")
1302 pats, inc, exc = [], [], []
1304 pats, inc, exc = [], [], []
1303 rev, default = None, None
1305 rev, default = None, None
1304 for arg in l:
1306 for arg in l:
1305 s = getstring(arg, b"_matchfiles requires string arguments")
1307 s = getstring(arg, b"_matchfiles requires string arguments")
1306 prefix, value = s[:2], s[2:]
1308 prefix, value = s[:2], s[2:]
1307 if prefix == b'p:':
1309 if prefix == b'p:':
1308 pats.append(value)
1310 pats.append(value)
1309 elif prefix == b'i:':
1311 elif prefix == b'i:':
1310 inc.append(value)
1312 inc.append(value)
1311 elif prefix == b'x:':
1313 elif prefix == b'x:':
1312 exc.append(value)
1314 exc.append(value)
1313 elif prefix == b'r:':
1315 elif prefix == b'r:':
1314 if rev is not None:
1316 if rev is not None:
1315 raise error.ParseError(
1317 raise error.ParseError(
1316 b'_matchfiles expected at most one revision'
1318 b'_matchfiles expected at most one revision'
1317 )
1319 )
1318 if value == b'': # empty means working directory
1320 if value == b'': # empty means working directory
1319 rev = node.wdirrev
1321 rev = node.wdirrev
1320 else:
1322 else:
1321 rev = value
1323 rev = value
1322 elif prefix == b'd:':
1324 elif prefix == b'd:':
1323 if default is not None:
1325 if default is not None:
1324 raise error.ParseError(
1326 raise error.ParseError(
1325 b'_matchfiles expected at most one default mode'
1327 b'_matchfiles expected at most one default mode'
1326 )
1328 )
1327 default = value
1329 default = value
1328 else:
1330 else:
1329 raise error.ParseError(b'invalid _matchfiles prefix: %s' % prefix)
1331 raise error.ParseError(b'invalid _matchfiles prefix: %s' % prefix)
1330 if not default:
1332 if not default:
1331 default = b'glob'
1333 default = b'glob'
1332 hasset = any(matchmod.patkind(p) == b'set' for p in pats + inc + exc)
1334 hasset = any(matchmod.patkind(p) == b'set' for p in pats + inc + exc)
1333
1335
1334 mcache = [None]
1336 mcache = [None]
1335
1337
1336 # This directly read the changelog data as creating changectx for all
1338 # This directly read the changelog data as creating changectx for all
1337 # revisions is quite expensive.
1339 # revisions is quite expensive.
1338 getfiles = repo.changelog.readfiles
1340 getfiles = repo.changelog.readfiles
1339 wdirrev = node.wdirrev
1341 wdirrev = node.wdirrev
1340
1342
1341 def matches(x):
1343 def matches(x):
1342 if x == wdirrev:
1344 if x == wdirrev:
1343 files = repo[x].files()
1345 files = repo[x].files()
1344 else:
1346 else:
1345 files = getfiles(x)
1347 files = getfiles(x)
1346
1348
1347 if not mcache[0] or (hasset and rev is None):
1349 if not mcache[0] or (hasset and rev is None):
1348 r = x if rev is None else rev
1350 r = x if rev is None else rev
1349 mcache[0] = matchmod.match(
1351 mcache[0] = matchmod.match(
1350 repo.root,
1352 repo.root,
1351 repo.getcwd(),
1353 repo.getcwd(),
1352 pats,
1354 pats,
1353 include=inc,
1355 include=inc,
1354 exclude=exc,
1356 exclude=exc,
1355 ctx=repo[r],
1357 ctx=repo[r],
1356 default=default,
1358 default=default,
1357 )
1359 )
1358 m = mcache[0]
1360 m = mcache[0]
1359
1361
1360 for f in files:
1362 for f in files:
1361 if m(f):
1363 if m(f):
1362 return True
1364 return True
1363 return False
1365 return False
1364
1366
1365 return subset.filter(
1367 return subset.filter(
1366 matches,
1368 matches,
1367 condrepr=(
1369 condrepr=(
1368 b'<matchfiles patterns=%r, include=%r '
1370 b'<matchfiles patterns=%r, include=%r '
1369 b'exclude=%r, default=%r, rev=%r>',
1371 b'exclude=%r, default=%r, rev=%r>',
1370 pats,
1372 pats,
1371 inc,
1373 inc,
1372 exc,
1374 exc,
1373 default,
1375 default,
1374 rev,
1376 rev,
1375 ),
1377 ),
1376 )
1378 )
1377
1379
1378
1380
1379 @predicate(b'file(pattern)', safe=True, weight=10)
1381 @predicate(b'file(pattern)', safe=True, weight=10)
1380 def hasfile(repo, subset, x):
1382 def hasfile(repo, subset, x):
1381 """Changesets affecting files matched by pattern.
1383 """Changesets affecting files matched by pattern.
1382
1384
1383 For a faster but less accurate result, consider using ``filelog()``
1385 For a faster but less accurate result, consider using ``filelog()``
1384 instead.
1386 instead.
1385
1387
1386 This predicate uses ``glob:`` as the default kind of pattern.
1388 This predicate uses ``glob:`` as the default kind of pattern.
1387 """
1389 """
1388 # i18n: "file" is a keyword
1390 # i18n: "file" is a keyword
1389 pat = getstring(x, _(b"file requires a pattern"))
1391 pat = getstring(x, _(b"file requires a pattern"))
1390 return _matchfiles(repo, subset, (b'string', b'p:' + pat))
1392 return _matchfiles(repo, subset, (b'string', b'p:' + pat))
1391
1393
1392
1394
1393 @predicate(b'head()', safe=True)
1395 @predicate(b'head()', safe=True)
1394 def head(repo, subset, x):
1396 def head(repo, subset, x):
1395 """Changeset is a named branch head.
1397 """Changeset is a named branch head.
1396 """
1398 """
1397 # i18n: "head" is a keyword
1399 # i18n: "head" is a keyword
1398 getargs(x, 0, 0, _(b"head takes no arguments"))
1400 getargs(x, 0, 0, _(b"head takes no arguments"))
1399 hs = set()
1401 hs = set()
1400 cl = repo.changelog
1402 cl = repo.changelog
1401 for ls in repo.branchmap().iterheads():
1403 for ls in repo.branchmap().iterheads():
1402 hs.update(cl.rev(h) for h in ls)
1404 hs.update(cl.rev(h) for h in ls)
1403 return subset & baseset(hs)
1405 return subset & baseset(hs)
1404
1406
1405
1407
1406 @predicate(b'heads(set)', safe=True, takeorder=True)
1408 @predicate(b'heads(set)', safe=True, takeorder=True)
1407 def heads(repo, subset, x, order):
1409 def heads(repo, subset, x, order):
1408 """Members of set with no children in set.
1410 """Members of set with no children in set.
1409 """
1411 """
1410 # argument set should never define order
1412 # argument set should never define order
1411 if order == defineorder:
1413 if order == defineorder:
1412 order = followorder
1414 order = followorder
1413 inputset = getset(repo, fullreposet(repo), x, order=order)
1415 inputset = getset(repo, fullreposet(repo), x, order=order)
1414 wdirparents = None
1416 wdirparents = None
1415 if node.wdirrev in inputset:
1417 if node.wdirrev in inputset:
1416 # a bit slower, but not common so good enough for now
1418 # a bit slower, but not common so good enough for now
1417 wdirparents = [p.rev() for p in repo[None].parents()]
1419 wdirparents = [p.rev() for p in repo[None].parents()]
1418 inputset = set(inputset)
1420 inputset = set(inputset)
1419 inputset.discard(node.wdirrev)
1421 inputset.discard(node.wdirrev)
1420 heads = repo.changelog.headrevs(inputset)
1422 heads = repo.changelog.headrevs(inputset)
1421 if wdirparents is not None:
1423 if wdirparents is not None:
1422 heads.difference_update(wdirparents)
1424 heads.difference_update(wdirparents)
1423 heads.add(node.wdirrev)
1425 heads.add(node.wdirrev)
1424 heads = baseset(heads)
1426 heads = baseset(heads)
1425 return subset & heads
1427 return subset & heads
1426
1428
1427
1429
1428 @predicate(b'hidden()', safe=True)
1430 @predicate(b'hidden()', safe=True)
1429 def hidden(repo, subset, x):
1431 def hidden(repo, subset, x):
1430 """Hidden changesets.
1432 """Hidden changesets.
1431 """
1433 """
1432 # i18n: "hidden" is a keyword
1434 # i18n: "hidden" is a keyword
1433 getargs(x, 0, 0, _(b"hidden takes no arguments"))
1435 getargs(x, 0, 0, _(b"hidden takes no arguments"))
1434 hiddenrevs = repoview.filterrevs(repo, b'visible')
1436 hiddenrevs = repoview.filterrevs(repo, b'visible')
1435 return subset & hiddenrevs
1437 return subset & hiddenrevs
1436
1438
1437
1439
1438 @predicate(b'keyword(string)', safe=True, weight=10)
1440 @predicate(b'keyword(string)', safe=True, weight=10)
1439 def keyword(repo, subset, x):
1441 def keyword(repo, subset, x):
1440 """Search commit message, user name, and names of changed files for
1442 """Search commit message, user name, and names of changed files for
1441 string. The match is case-insensitive.
1443 string. The match is case-insensitive.
1442
1444
1443 For a regular expression or case sensitive search of these fields, use
1445 For a regular expression or case sensitive search of these fields, use
1444 ``grep(regex)``.
1446 ``grep(regex)``.
1445 """
1447 """
1446 # i18n: "keyword" is a keyword
1448 # i18n: "keyword" is a keyword
1447 kw = encoding.lower(getstring(x, _(b"keyword requires a string")))
1449 kw = encoding.lower(getstring(x, _(b"keyword requires a string")))
1448
1450
1449 def matches(r):
1451 def matches(r):
1450 c = repo[r]
1452 c = repo[r]
1451 return any(
1453 return any(
1452 kw in encoding.lower(t)
1454 kw in encoding.lower(t)
1453 for t in c.files() + [c.user(), c.description()]
1455 for t in c.files() + [c.user(), c.description()]
1454 )
1456 )
1455
1457
1456 return subset.filter(matches, condrepr=(b'<keyword %r>', kw))
1458 return subset.filter(matches, condrepr=(b'<keyword %r>', kw))
1457
1459
1458
1460
1459 @predicate(b'limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0)
1461 @predicate(b'limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0)
1460 def limit(repo, subset, x, order):
1462 def limit(repo, subset, x, order):
1461 """First n members of set, defaulting to 1, starting from offset.
1463 """First n members of set, defaulting to 1, starting from offset.
1462 """
1464 """
1463 args = getargsdict(x, b'limit', b'set n offset')
1465 args = getargsdict(x, b'limit', b'set n offset')
1464 if b'set' not in args:
1466 if b'set' not in args:
1465 # i18n: "limit" is a keyword
1467 # i18n: "limit" is a keyword
1466 raise error.ParseError(_(b"limit requires one to three arguments"))
1468 raise error.ParseError(_(b"limit requires one to three arguments"))
1467 # i18n: "limit" is a keyword
1469 # i18n: "limit" is a keyword
1468 lim = getinteger(args.get(b'n'), _(b"limit expects a number"), default=1)
1470 lim = getinteger(args.get(b'n'), _(b"limit expects a number"), default=1)
1469 if lim < 0:
1471 if lim < 0:
1470 raise error.ParseError(_(b"negative number to select"))
1472 raise error.ParseError(_(b"negative number to select"))
1471 # i18n: "limit" is a keyword
1473 # i18n: "limit" is a keyword
1472 ofs = getinteger(
1474 ofs = getinteger(
1473 args.get(b'offset'), _(b"limit expects a number"), default=0
1475 args.get(b'offset'), _(b"limit expects a number"), default=0
1474 )
1476 )
1475 if ofs < 0:
1477 if ofs < 0:
1476 raise error.ParseError(_(b"negative offset"))
1478 raise error.ParseError(_(b"negative offset"))
1477 os = getset(repo, fullreposet(repo), args[b'set'])
1479 os = getset(repo, fullreposet(repo), args[b'set'])
1478 ls = os.slice(ofs, ofs + lim)
1480 ls = os.slice(ofs, ofs + lim)
1479 if order == followorder and lim > 1:
1481 if order == followorder and lim > 1:
1480 return subset & ls
1482 return subset & ls
1481 return ls & subset
1483 return ls & subset
1482
1484
1483
1485
1484 @predicate(b'last(set, [n])', safe=True, takeorder=True)
1486 @predicate(b'last(set, [n])', safe=True, takeorder=True)
1485 def last(repo, subset, x, order):
1487 def last(repo, subset, x, order):
1486 """Last n members of set, defaulting to 1.
1488 """Last n members of set, defaulting to 1.
1487 """
1489 """
1488 # i18n: "last" is a keyword
1490 # i18n: "last" is a keyword
1489 l = getargs(x, 1, 2, _(b"last requires one or two arguments"))
1491 l = getargs(x, 1, 2, _(b"last requires one or two arguments"))
1490 lim = 1
1492 lim = 1
1491 if len(l) == 2:
1493 if len(l) == 2:
1492 # i18n: "last" is a keyword
1494 # i18n: "last" is a keyword
1493 lim = getinteger(l[1], _(b"last expects a number"))
1495 lim = getinteger(l[1], _(b"last expects a number"))
1494 if lim < 0:
1496 if lim < 0:
1495 raise error.ParseError(_(b"negative number to select"))
1497 raise error.ParseError(_(b"negative number to select"))
1496 os = getset(repo, fullreposet(repo), l[0])
1498 os = getset(repo, fullreposet(repo), l[0])
1497 os.reverse()
1499 os.reverse()
1498 ls = os.slice(0, lim)
1500 ls = os.slice(0, lim)
1499 if order == followorder and lim > 1:
1501 if order == followorder and lim > 1:
1500 return subset & ls
1502 return subset & ls
1501 ls.reverse()
1503 ls.reverse()
1502 return ls & subset
1504 return ls & subset
1503
1505
1504
1506
1505 @predicate(b'max(set)', safe=True)
1507 @predicate(b'max(set)', safe=True)
1506 def maxrev(repo, subset, x):
1508 def maxrev(repo, subset, x):
1507 """Changeset with highest revision number in set.
1509 """Changeset with highest revision number in set.
1508 """
1510 """
1509 os = getset(repo, fullreposet(repo), x)
1511 os = getset(repo, fullreposet(repo), x)
1510 try:
1512 try:
1511 m = os.max()
1513 m = os.max()
1512 if m in subset:
1514 if m in subset:
1513 return baseset([m], datarepr=(b'<max %r, %r>', subset, os))
1515 return baseset([m], datarepr=(b'<max %r, %r>', subset, os))
1514 except ValueError:
1516 except ValueError:
1515 # os.max() throws a ValueError when the collection is empty.
1517 # os.max() throws a ValueError when the collection is empty.
1516 # Same as python's max().
1518 # Same as python's max().
1517 pass
1519 pass
1518 return baseset(datarepr=(b'<max %r, %r>', subset, os))
1520 return baseset(datarepr=(b'<max %r, %r>', subset, os))
1519
1521
1520
1522
1521 @predicate(b'merge()', safe=True)
1523 @predicate(b'merge()', safe=True)
1522 def merge(repo, subset, x):
1524 def merge(repo, subset, x):
1523 """Changeset is a merge changeset.
1525 """Changeset is a merge changeset.
1524 """
1526 """
1525 # i18n: "merge" is a keyword
1527 # i18n: "merge" is a keyword
1526 getargs(x, 0, 0, _(b"merge takes no arguments"))
1528 getargs(x, 0, 0, _(b"merge takes no arguments"))
1527 cl = repo.changelog
1529 cl = repo.changelog
1528 nullrev = node.nullrev
1530 nullrev = node.nullrev
1529
1531
1530 def ismerge(r):
1532 def ismerge(r):
1531 try:
1533 try:
1532 return cl.parentrevs(r)[1] != nullrev
1534 return cl.parentrevs(r)[1] != nullrev
1533 except error.WdirUnsupported:
1535 except error.WdirUnsupported:
1534 return bool(repo[r].p2())
1536 return bool(repo[r].p2())
1535
1537
1536 return subset.filter(ismerge, condrepr=b'<merge>')
1538 return subset.filter(ismerge, condrepr=b'<merge>')
1537
1539
1538
1540
1539 @predicate(b'branchpoint()', safe=True)
1541 @predicate(b'branchpoint()', safe=True)
1540 def branchpoint(repo, subset, x):
1542 def branchpoint(repo, subset, x):
1541 """Changesets with more than one child.
1543 """Changesets with more than one child.
1542 """
1544 """
1543 # i18n: "branchpoint" is a keyword
1545 # i18n: "branchpoint" is a keyword
1544 getargs(x, 0, 0, _(b"branchpoint takes no arguments"))
1546 getargs(x, 0, 0, _(b"branchpoint takes no arguments"))
1545 cl = repo.changelog
1547 cl = repo.changelog
1546 if not subset:
1548 if not subset:
1547 return baseset()
1549 return baseset()
1548 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1550 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1549 # (and if it is not, it should.)
1551 # (and if it is not, it should.)
1550 baserev = min(subset)
1552 baserev = min(subset)
1551 parentscount = [0] * (len(repo) - baserev)
1553 parentscount = [0] * (len(repo) - baserev)
1552 for r in cl.revs(start=baserev + 1):
1554 for r in cl.revs(start=baserev + 1):
1553 for p in cl.parentrevs(r):
1555 for p in cl.parentrevs(r):
1554 if p >= baserev:
1556 if p >= baserev:
1555 parentscount[p - baserev] += 1
1557 parentscount[p - baserev] += 1
1556 return subset.filter(
1558 return subset.filter(
1557 lambda r: parentscount[r - baserev] > 1, condrepr=b'<branchpoint>'
1559 lambda r: parentscount[r - baserev] > 1, condrepr=b'<branchpoint>'
1558 )
1560 )
1559
1561
1560
1562
1561 @predicate(b'min(set)', safe=True)
1563 @predicate(b'min(set)', safe=True)
1562 def minrev(repo, subset, x):
1564 def minrev(repo, subset, x):
1563 """Changeset with lowest revision number in set.
1565 """Changeset with lowest revision number in set.
1564 """
1566 """
1565 os = getset(repo, fullreposet(repo), x)
1567 os = getset(repo, fullreposet(repo), x)
1566 try:
1568 try:
1567 m = os.min()
1569 m = os.min()
1568 if m in subset:
1570 if m in subset:
1569 return baseset([m], datarepr=(b'<min %r, %r>', subset, os))
1571 return baseset([m], datarepr=(b'<min %r, %r>', subset, os))
1570 except ValueError:
1572 except ValueError:
1571 # os.min() throws a ValueError when the collection is empty.
1573 # os.min() throws a ValueError when the collection is empty.
1572 # Same as python's min().
1574 # Same as python's min().
1573 pass
1575 pass
1574 return baseset(datarepr=(b'<min %r, %r>', subset, os))
1576 return baseset(datarepr=(b'<min %r, %r>', subset, os))
1575
1577
1576
1578
1577 @predicate(b'modifies(pattern)', safe=True, weight=30)
1579 @predicate(b'modifies(pattern)', safe=True, weight=30)
1578 def modifies(repo, subset, x):
1580 def modifies(repo, subset, x):
1579 """Changesets modifying files matched by pattern.
1581 """Changesets modifying files matched by pattern.
1580
1582
1581 The pattern without explicit kind like ``glob:`` is expected to be
1583 The pattern without explicit kind like ``glob:`` is expected to be
1582 relative to the current directory and match against a file or a
1584 relative to the current directory and match against a file or a
1583 directory.
1585 directory.
1584 """
1586 """
1585 # i18n: "modifies" is a keyword
1587 # i18n: "modifies" is a keyword
1586 pat = getstring(x, _(b"modifies requires a pattern"))
1588 pat = getstring(x, _(b"modifies requires a pattern"))
1587 return checkstatus(repo, subset, pat, 0)
1589 return checkstatus(repo, subset, pat, 0)
1588
1590
1589
1591
1590 @predicate(b'named(namespace)')
1592 @predicate(b'named(namespace)')
1591 def named(repo, subset, x):
1593 def named(repo, subset, x):
1592 """The changesets in a given namespace.
1594 """The changesets in a given namespace.
1593
1595
1594 Pattern matching is supported for `namespace`. See
1596 Pattern matching is supported for `namespace`. See
1595 :hg:`help revisions.patterns`.
1597 :hg:`help revisions.patterns`.
1596 """
1598 """
1597 # i18n: "named" is a keyword
1599 # i18n: "named" is a keyword
1598 args = getargs(x, 1, 1, _(b'named requires a namespace argument'))
1600 args = getargs(x, 1, 1, _(b'named requires a namespace argument'))
1599
1601
1600 ns = getstring(
1602 ns = getstring(
1601 args[0],
1603 args[0],
1602 # i18n: "named" is a keyword
1604 # i18n: "named" is a keyword
1603 _(b'the argument to named must be a string'),
1605 _(b'the argument to named must be a string'),
1604 )
1606 )
1605 kind, pattern, matcher = stringutil.stringmatcher(ns)
1607 kind, pattern, matcher = stringutil.stringmatcher(ns)
1606 namespaces = set()
1608 namespaces = set()
1607 if kind == b'literal':
1609 if kind == b'literal':
1608 if pattern not in repo.names:
1610 if pattern not in repo.names:
1609 raise error.RepoLookupError(
1611 raise error.RepoLookupError(
1610 _(b"namespace '%s' does not exist") % ns
1612 _(b"namespace '%s' does not exist") % ns
1611 )
1613 )
1612 namespaces.add(repo.names[pattern])
1614 namespaces.add(repo.names[pattern])
1613 else:
1615 else:
1614 for name, ns in pycompat.iteritems(repo.names):
1616 for name, ns in pycompat.iteritems(repo.names):
1615 if matcher(name):
1617 if matcher(name):
1616 namespaces.add(ns)
1618 namespaces.add(ns)
1617
1619
1618 names = set()
1620 names = set()
1619 for ns in namespaces:
1621 for ns in namespaces:
1620 for name in ns.listnames(repo):
1622 for name in ns.listnames(repo):
1621 if name not in ns.deprecated:
1623 if name not in ns.deprecated:
1622 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1624 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1623
1625
1624 names -= {node.nullrev}
1626 names -= {node.nullrev}
1625 return subset & names
1627 return subset & names
1626
1628
1627
1629
1628 @predicate(b'id(string)', safe=True)
1630 @predicate(b'id(string)', safe=True)
1629 def node_(repo, subset, x):
1631 def node_(repo, subset, x):
1630 """Revision non-ambiguously specified by the given hex string prefix.
1632 """Revision non-ambiguously specified by the given hex string prefix.
1631 """
1633 """
1632 # i18n: "id" is a keyword
1634 # i18n: "id" is a keyword
1633 l = getargs(x, 1, 1, _(b"id requires one argument"))
1635 l = getargs(x, 1, 1, _(b"id requires one argument"))
1634 # i18n: "id" is a keyword
1636 # i18n: "id" is a keyword
1635 n = getstring(l[0], _(b"id requires a string"))
1637 n = getstring(l[0], _(b"id requires a string"))
1636 if len(n) == 40:
1638 if len(n) == 40:
1637 try:
1639 try:
1638 rn = repo.changelog.rev(node.bin(n))
1640 rn = repo.changelog.rev(node.bin(n))
1639 except error.WdirUnsupported:
1641 except error.WdirUnsupported:
1640 rn = node.wdirrev
1642 rn = node.wdirrev
1641 except (LookupError, TypeError):
1643 except (LookupError, TypeError):
1642 rn = None
1644 rn = None
1643 else:
1645 else:
1644 rn = None
1646 rn = None
1645 try:
1647 try:
1646 pm = scmutil.resolvehexnodeidprefix(repo, n)
1648 pm = scmutil.resolvehexnodeidprefix(repo, n)
1647 if pm is not None:
1649 if pm is not None:
1648 rn = repo.changelog.rev(pm)
1650 rn = repo.changelog.rev(pm)
1649 except LookupError:
1651 except LookupError:
1650 pass
1652 pass
1651 except error.WdirUnsupported:
1653 except error.WdirUnsupported:
1652 rn = node.wdirrev
1654 rn = node.wdirrev
1653
1655
1654 if rn is None:
1656 if rn is None:
1655 return baseset()
1657 return baseset()
1656 result = baseset([rn])
1658 result = baseset([rn])
1657 return result & subset
1659 return result & subset
1658
1660
1659
1661
1660 @predicate(b'none()', safe=True)
1662 @predicate(b'none()', safe=True)
1661 def none(repo, subset, x):
1663 def none(repo, subset, x):
1662 """No changesets.
1664 """No changesets.
1663 """
1665 """
1664 # i18n: "none" is a keyword
1666 # i18n: "none" is a keyword
1665 getargs(x, 0, 0, _(b"none takes no arguments"))
1667 getargs(x, 0, 0, _(b"none takes no arguments"))
1666 return baseset()
1668 return baseset()
1667
1669
1668
1670
1669 @predicate(b'obsolete()', safe=True)
1671 @predicate(b'obsolete()', safe=True)
1670 def obsolete(repo, subset, x):
1672 def obsolete(repo, subset, x):
1671 """Mutable changeset with a newer version."""
1673 """Mutable changeset with a newer version."""
1672 # i18n: "obsolete" is a keyword
1674 # i18n: "obsolete" is a keyword
1673 getargs(x, 0, 0, _(b"obsolete takes no arguments"))
1675 getargs(x, 0, 0, _(b"obsolete takes no arguments"))
1674 obsoletes = obsmod.getrevs(repo, b'obsolete')
1676 obsoletes = obsmod.getrevs(repo, b'obsolete')
1675 return subset & obsoletes
1677 return subset & obsoletes
1676
1678
1677
1679
1678 @predicate(b'only(set, [set])', safe=True)
1680 @predicate(b'only(set, [set])', safe=True)
1679 def only(repo, subset, x):
1681 def only(repo, subset, x):
1680 """Changesets that are ancestors of the first set that are not ancestors
1682 """Changesets that are ancestors of the first set that are not ancestors
1681 of any other head in the repo. If a second set is specified, the result
1683 of any other head in the repo. If a second set is specified, the result
1682 is ancestors of the first set that are not ancestors of the second set
1684 is ancestors of the first set that are not ancestors of the second set
1683 (i.e. ::<set1> - ::<set2>).
1685 (i.e. ::<set1> - ::<set2>).
1684 """
1686 """
1685 cl = repo.changelog
1687 cl = repo.changelog
1686 # i18n: "only" is a keyword
1688 # i18n: "only" is a keyword
1687 args = getargs(x, 1, 2, _(b'only takes one or two arguments'))
1689 args = getargs(x, 1, 2, _(b'only takes one or two arguments'))
1688 include = getset(repo, fullreposet(repo), args[0])
1690 include = getset(repo, fullreposet(repo), args[0])
1689 if len(args) == 1:
1691 if len(args) == 1:
1690 if not include:
1692 if not include:
1691 return baseset()
1693 return baseset()
1692
1694
1693 descendants = set(dagop.revdescendants(repo, include, False))
1695 descendants = set(dagop.revdescendants(repo, include, False))
1694 exclude = [
1696 exclude = [
1695 rev
1697 rev
1696 for rev in cl.headrevs()
1698 for rev in cl.headrevs()
1697 if not rev in descendants and not rev in include
1699 if not rev in descendants and not rev in include
1698 ]
1700 ]
1699 else:
1701 else:
1700 exclude = getset(repo, fullreposet(repo), args[1])
1702 exclude = getset(repo, fullreposet(repo), args[1])
1701
1703
1702 results = set(cl.findmissingrevs(common=exclude, heads=include))
1704 results = set(cl.findmissingrevs(common=exclude, heads=include))
1703 # XXX we should turn this into a baseset instead of a set, smartset may do
1705 # XXX we should turn this into a baseset instead of a set, smartset may do
1704 # some optimizations from the fact this is a baseset.
1706 # some optimizations from the fact this is a baseset.
1705 return subset & results
1707 return subset & results
1706
1708
1707
1709
1708 @predicate(b'origin([set])', safe=True)
1710 @predicate(b'origin([set])', safe=True)
1709 def origin(repo, subset, x):
1711 def origin(repo, subset, x):
1710 """
1712 """
1711 Changesets that were specified as a source for the grafts, transplants or
1713 Changesets that were specified as a source for the grafts, transplants or
1712 rebases that created the given revisions. Omitting the optional set is the
1714 rebases that created the given revisions. Omitting the optional set is the
1713 same as passing all(). If a changeset created by these operations is itself
1715 same as passing all(). If a changeset created by these operations is itself
1714 specified as a source for one of these operations, only the source changeset
1716 specified as a source for one of these operations, only the source changeset
1715 for the first operation is selected.
1717 for the first operation is selected.
1716 """
1718 """
1717 if x is not None:
1719 if x is not None:
1718 dests = getset(repo, fullreposet(repo), x)
1720 dests = getset(repo, fullreposet(repo), x)
1719 else:
1721 else:
1720 dests = fullreposet(repo)
1722 dests = fullreposet(repo)
1721
1723
1722 def _firstsrc(rev):
1724 def _firstsrc(rev):
1723 src = _getrevsource(repo, rev)
1725 src = _getrevsource(repo, rev)
1724 if src is None:
1726 if src is None:
1725 return None
1727 return None
1726
1728
1727 while True:
1729 while True:
1728 prev = _getrevsource(repo, src)
1730 prev = _getrevsource(repo, src)
1729
1731
1730 if prev is None:
1732 if prev is None:
1731 return src
1733 return src
1732 src = prev
1734 src = prev
1733
1735
1734 o = {_firstsrc(r) for r in dests}
1736 o = {_firstsrc(r) for r in dests}
1735 o -= {None}
1737 o -= {None}
1736 # XXX we should turn this into a baseset instead of a set, smartset may do
1738 # XXX we should turn this into a baseset instead of a set, smartset may do
1737 # some optimizations from the fact this is a baseset.
1739 # some optimizations from the fact this is a baseset.
1738 return subset & o
1740 return subset & o
1739
1741
1740
1742
1741 @predicate(b'outgoing([path])', safe=False, weight=10)
1743 @predicate(b'outgoing([path])', safe=False, weight=10)
1742 def outgoing(repo, subset, x):
1744 def outgoing(repo, subset, x):
1743 """Changesets not found in the specified destination repository, or the
1745 """Changesets not found in the specified destination repository, or the
1744 default push location.
1746 default push location.
1745 """
1747 """
1746 # Avoid cycles.
1748 # Avoid cycles.
1747 from . import (
1749 from . import (
1748 discovery,
1750 discovery,
1749 hg,
1751 hg,
1750 )
1752 )
1751
1753
1752 # i18n: "outgoing" is a keyword
1754 # i18n: "outgoing" is a keyword
1753 l = getargs(x, 0, 1, _(b"outgoing takes one or no arguments"))
1755 l = getargs(x, 0, 1, _(b"outgoing takes one or no arguments"))
1754 # i18n: "outgoing" is a keyword
1756 # i18n: "outgoing" is a keyword
1755 dest = (
1757 dest = (
1756 l and getstring(l[0], _(b"outgoing requires a repository path")) or b''
1758 l and getstring(l[0], _(b"outgoing requires a repository path")) or b''
1757 )
1759 )
1758 if not dest:
1760 if not dest:
1759 # ui.paths.getpath() explicitly tests for None, not just a boolean
1761 # ui.paths.getpath() explicitly tests for None, not just a boolean
1760 dest = None
1762 dest = None
1761 path = repo.ui.paths.getpath(dest, default=(b'default-push', b'default'))
1763 path = repo.ui.paths.getpath(dest, default=(b'default-push', b'default'))
1762 if not path:
1764 if not path:
1763 raise error.Abort(
1765 raise error.Abort(
1764 _(b'default repository not configured!'),
1766 _(b'default repository not configured!'),
1765 hint=_(b"see 'hg help config.paths'"),
1767 hint=_(b"see 'hg help config.paths'"),
1766 )
1768 )
1767 dest = path.pushloc or path.loc
1769 dest = path.pushloc or path.loc
1768 branches = path.branch, []
1770 branches = path.branch, []
1769
1771
1770 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1772 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1771 if revs:
1773 if revs:
1772 revs = [repo.lookup(rev) for rev in revs]
1774 revs = [repo.lookup(rev) for rev in revs]
1773 other = hg.peer(repo, {}, dest)
1775 other = hg.peer(repo, {}, dest)
1774 repo.ui.pushbuffer()
1776 repo.ui.pushbuffer()
1775 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1777 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1776 repo.ui.popbuffer()
1778 repo.ui.popbuffer()
1777 cl = repo.changelog
1779 cl = repo.changelog
1778 o = {cl.rev(r) for r in outgoing.missing}
1780 o = {cl.rev(r) for r in outgoing.missing}
1779 return subset & o
1781 return subset & o
1780
1782
1781
1783
1782 @predicate(b'p1([set])', safe=True)
1784 @predicate(b'p1([set])', safe=True)
1783 def p1(repo, subset, x):
1785 def p1(repo, subset, x):
1784 """First parent of changesets in set, or the working directory.
1786 """First parent of changesets in set, or the working directory.
1785 """
1787 """
1786 if x is None:
1788 if x is None:
1787 p = repo[x].p1().rev()
1789 p = repo[x].p1().rev()
1788 if p >= 0:
1790 if p >= 0:
1789 return subset & baseset([p])
1791 return subset & baseset([p])
1790 return baseset()
1792 return baseset()
1791
1793
1792 ps = set()
1794 ps = set()
1793 cl = repo.changelog
1795 cl = repo.changelog
1794 for r in getset(repo, fullreposet(repo), x):
1796 for r in getset(repo, fullreposet(repo), x):
1795 try:
1797 try:
1796 ps.add(cl.parentrevs(r)[0])
1798 ps.add(cl.parentrevs(r)[0])
1797 except error.WdirUnsupported:
1799 except error.WdirUnsupported:
1798 ps.add(repo[r].p1().rev())
1800 ps.add(repo[r].p1().rev())
1799 ps -= {node.nullrev}
1801 ps -= {node.nullrev}
1800 # XXX we should turn this into a baseset instead of a set, smartset may do
1802 # XXX we should turn this into a baseset instead of a set, smartset may do
1801 # some optimizations from the fact this is a baseset.
1803 # some optimizations from the fact this is a baseset.
1802 return subset & ps
1804 return subset & ps
1803
1805
1804
1806
1805 @predicate(b'p2([set])', safe=True)
1807 @predicate(b'p2([set])', safe=True)
1806 def p2(repo, subset, x):
1808 def p2(repo, subset, x):
1807 """Second parent of changesets in set, or the working directory.
1809 """Second parent of changesets in set, or the working directory.
1808 """
1810 """
1809 if x is None:
1811 if x is None:
1810 ps = repo[x].parents()
1812 ps = repo[x].parents()
1811 try:
1813 try:
1812 p = ps[1].rev()
1814 p = ps[1].rev()
1813 if p >= 0:
1815 if p >= 0:
1814 return subset & baseset([p])
1816 return subset & baseset([p])
1815 return baseset()
1817 return baseset()
1816 except IndexError:
1818 except IndexError:
1817 return baseset()
1819 return baseset()
1818
1820
1819 ps = set()
1821 ps = set()
1820 cl = repo.changelog
1822 cl = repo.changelog
1821 for r in getset(repo, fullreposet(repo), x):
1823 for r in getset(repo, fullreposet(repo), x):
1822 try:
1824 try:
1823 ps.add(cl.parentrevs(r)[1])
1825 ps.add(cl.parentrevs(r)[1])
1824 except error.WdirUnsupported:
1826 except error.WdirUnsupported:
1825 parents = repo[r].parents()
1827 parents = repo[r].parents()
1826 if len(parents) == 2:
1828 if len(parents) == 2:
1827 ps.add(parents[1])
1829 ps.add(parents[1])
1828 ps -= {node.nullrev}
1830 ps -= {node.nullrev}
1829 # XXX we should turn this into a baseset instead of a set, smartset may do
1831 # XXX we should turn this into a baseset instead of a set, smartset may do
1830 # some optimizations from the fact this is a baseset.
1832 # some optimizations from the fact this is a baseset.
1831 return subset & ps
1833 return subset & ps
1832
1834
1833
1835
1834 def parentpost(repo, subset, x, order):
1836 def parentpost(repo, subset, x, order):
1835 return p1(repo, subset, x)
1837 return p1(repo, subset, x)
1836
1838
1837
1839
1838 @predicate(b'parents([set])', safe=True)
1840 @predicate(b'parents([set])', safe=True)
1839 def parents(repo, subset, x):
1841 def parents(repo, subset, x):
1840 """
1842 """
1841 The set of all parents for all changesets in set, or the working directory.
1843 The set of all parents for all changesets in set, or the working directory.
1842 """
1844 """
1843 if x is None:
1845 if x is None:
1844 ps = set(p.rev() for p in repo[x].parents())
1846 ps = set(p.rev() for p in repo[x].parents())
1845 else:
1847 else:
1846 ps = set()
1848 ps = set()
1847 cl = repo.changelog
1849 cl = repo.changelog
1848 up = ps.update
1850 up = ps.update
1849 parentrevs = cl.parentrevs
1851 parentrevs = cl.parentrevs
1850 for r in getset(repo, fullreposet(repo), x):
1852 for r in getset(repo, fullreposet(repo), x):
1851 try:
1853 try:
1852 up(parentrevs(r))
1854 up(parentrevs(r))
1853 except error.WdirUnsupported:
1855 except error.WdirUnsupported:
1854 up(p.rev() for p in repo[r].parents())
1856 up(p.rev() for p in repo[r].parents())
1855 ps -= {node.nullrev}
1857 ps -= {node.nullrev}
1856 return subset & ps
1858 return subset & ps
1857
1859
1858
1860
1859 def _phase(repo, subset, *targets):
1861 def _phase(repo, subset, *targets):
1860 """helper to select all rev in <targets> phases"""
1862 """helper to select all rev in <targets> phases"""
1861 return repo._phasecache.getrevset(repo, targets, subset)
1863 return repo._phasecache.getrevset(repo, targets, subset)
1862
1864
1863
1865
1864 @predicate(b'_phase(idx)', safe=True)
1866 @predicate(b'_phase(idx)', safe=True)
1865 def phase(repo, subset, x):
1867 def phase(repo, subset, x):
1866 l = getargs(x, 1, 1, b"_phase requires one argument")
1868 l = getargs(x, 1, 1, b"_phase requires one argument")
1867 target = getinteger(l[0], b"_phase expects a number")
1869 target = getinteger(l[0], b"_phase expects a number")
1868 return _phase(repo, subset, target)
1870 return _phase(repo, subset, target)
1869
1871
1870
1872
1871 @predicate(b'draft()', safe=True)
1873 @predicate(b'draft()', safe=True)
1872 def draft(repo, subset, x):
1874 def draft(repo, subset, x):
1873 """Changeset in draft phase."""
1875 """Changeset in draft phase."""
1874 # i18n: "draft" is a keyword
1876 # i18n: "draft" is a keyword
1875 getargs(x, 0, 0, _(b"draft takes no arguments"))
1877 getargs(x, 0, 0, _(b"draft takes no arguments"))
1876 target = phases.draft
1878 target = phases.draft
1877 return _phase(repo, subset, target)
1879 return _phase(repo, subset, target)
1878
1880
1879
1881
1880 @predicate(b'secret()', safe=True)
1882 @predicate(b'secret()', safe=True)
1881 def secret(repo, subset, x):
1883 def secret(repo, subset, x):
1882 """Changeset in secret phase."""
1884 """Changeset in secret phase."""
1883 # i18n: "secret" is a keyword
1885 # i18n: "secret" is a keyword
1884 getargs(x, 0, 0, _(b"secret takes no arguments"))
1886 getargs(x, 0, 0, _(b"secret takes no arguments"))
1885 target = phases.secret
1887 target = phases.secret
1886 return _phase(repo, subset, target)
1888 return _phase(repo, subset, target)
1887
1889
1888
1890
1889 @predicate(b'stack([revs])', safe=True)
1891 @predicate(b'stack([revs])', safe=True)
1890 def stack(repo, subset, x):
1892 def stack(repo, subset, x):
1891 """Experimental revset for the stack of changesets or working directory
1893 """Experimental revset for the stack of changesets or working directory
1892 parent. (EXPERIMENTAL)
1894 parent. (EXPERIMENTAL)
1893 """
1895 """
1894 if x is None:
1896 if x is None:
1895 stacks = stackmod.getstack(repo)
1897 stacks = stackmod.getstack(repo)
1896 else:
1898 else:
1897 stacks = smartset.baseset([])
1899 stacks = smartset.baseset([])
1898 for revision in getset(repo, fullreposet(repo), x):
1900 for revision in getset(repo, fullreposet(repo), x):
1899 currentstack = stackmod.getstack(repo, revision)
1901 currentstack = stackmod.getstack(repo, revision)
1900 stacks = stacks + currentstack
1902 stacks = stacks + currentstack
1901
1903
1902 return subset & stacks
1904 return subset & stacks
1903
1905
1904
1906
1905 def parentspec(repo, subset, x, n, order):
1907 def parentspec(repo, subset, x, n, order):
1906 """``set^0``
1908 """``set^0``
1907 The set.
1909 The set.
1908 ``set^1`` (or ``set^``), ``set^2``
1910 ``set^1`` (or ``set^``), ``set^2``
1909 First or second parent, respectively, of all changesets in set.
1911 First or second parent, respectively, of all changesets in set.
1910 """
1912 """
1911 try:
1913 try:
1912 n = int(n[1])
1914 n = int(n[1])
1913 if n not in (0, 1, 2):
1915 if n not in (0, 1, 2):
1914 raise ValueError
1916 raise ValueError
1915 except (TypeError, ValueError):
1917 except (TypeError, ValueError):
1916 raise error.ParseError(_(b"^ expects a number 0, 1, or 2"))
1918 raise error.ParseError(_(b"^ expects a number 0, 1, or 2"))
1917 ps = set()
1919 ps = set()
1918 cl = repo.changelog
1920 cl = repo.changelog
1919 for r in getset(repo, fullreposet(repo), x):
1921 for r in getset(repo, fullreposet(repo), x):
1920 if n == 0:
1922 if n == 0:
1921 ps.add(r)
1923 ps.add(r)
1922 elif n == 1:
1924 elif n == 1:
1923 try:
1925 try:
1924 ps.add(cl.parentrevs(r)[0])
1926 ps.add(cl.parentrevs(r)[0])
1925 except error.WdirUnsupported:
1927 except error.WdirUnsupported:
1926 ps.add(repo[r].p1().rev())
1928 ps.add(repo[r].p1().rev())
1927 else:
1929 else:
1928 try:
1930 try:
1929 parents = cl.parentrevs(r)
1931 parents = cl.parentrevs(r)
1930 if parents[1] != node.nullrev:
1932 if parents[1] != node.nullrev:
1931 ps.add(parents[1])
1933 ps.add(parents[1])
1932 except error.WdirUnsupported:
1934 except error.WdirUnsupported:
1933 parents = repo[r].parents()
1935 parents = repo[r].parents()
1934 if len(parents) == 2:
1936 if len(parents) == 2:
1935 ps.add(parents[1].rev())
1937 ps.add(parents[1].rev())
1936 return subset & ps
1938 return subset & ps
1937
1939
1938
1940
1939 @predicate(b'present(set)', safe=True, takeorder=True)
1941 @predicate(b'present(set)', safe=True, takeorder=True)
1940 def present(repo, subset, x, order):
1942 def present(repo, subset, x, order):
1941 """An empty set, if any revision in set isn't found; otherwise,
1943 """An empty set, if any revision in set isn't found; otherwise,
1942 all revisions in set.
1944 all revisions in set.
1943
1945
1944 If any of specified revisions is not present in the local repository,
1946 If any of specified revisions is not present in the local repository,
1945 the query is normally aborted. But this predicate allows the query
1947 the query is normally aborted. But this predicate allows the query
1946 to continue even in such cases.
1948 to continue even in such cases.
1947 """
1949 """
1948 try:
1950 try:
1949 return getset(repo, subset, x, order)
1951 return getset(repo, subset, x, order)
1950 except error.RepoLookupError:
1952 except error.RepoLookupError:
1951 return baseset()
1953 return baseset()
1952
1954
1953
1955
1954 # for internal use
1956 # for internal use
1955 @predicate(b'_notpublic', safe=True)
1957 @predicate(b'_notpublic', safe=True)
1956 def _notpublic(repo, subset, x):
1958 def _notpublic(repo, subset, x):
1957 getargs(x, 0, 0, b"_notpublic takes no arguments")
1959 getargs(x, 0, 0, b"_notpublic takes no arguments")
1958 return _phase(repo, subset, phases.draft, phases.secret)
1960 return _phase(repo, subset, phases.draft, phases.secret)
1959
1961
1960
1962
1961 # for internal use
1963 # for internal use
1962 @predicate(b'_phaseandancestors(phasename, set)', safe=True)
1964 @predicate(b'_phaseandancestors(phasename, set)', safe=True)
1963 def _phaseandancestors(repo, subset, x):
1965 def _phaseandancestors(repo, subset, x):
1964 # equivalent to (phasename() & ancestors(set)) but more efficient
1966 # equivalent to (phasename() & ancestors(set)) but more efficient
1965 # phasename could be one of 'draft', 'secret', or '_notpublic'
1967 # phasename could be one of 'draft', 'secret', or '_notpublic'
1966 args = getargs(x, 2, 2, b"_phaseandancestors requires two arguments")
1968 args = getargs(x, 2, 2, b"_phaseandancestors requires two arguments")
1967 phasename = getsymbol(args[0])
1969 phasename = getsymbol(args[0])
1968 s = getset(repo, fullreposet(repo), args[1])
1970 s = getset(repo, fullreposet(repo), args[1])
1969
1971
1970 draft = phases.draft
1972 draft = phases.draft
1971 secret = phases.secret
1973 secret = phases.secret
1972 phasenamemap = {
1974 phasenamemap = {
1973 b'_notpublic': draft,
1975 b'_notpublic': draft,
1974 b'draft': draft, # follow secret's ancestors
1976 b'draft': draft, # follow secret's ancestors
1975 b'secret': secret,
1977 b'secret': secret,
1976 }
1978 }
1977 if phasename not in phasenamemap:
1979 if phasename not in phasenamemap:
1978 raise error.ParseError(b'%r is not a valid phasename' % phasename)
1980 raise error.ParseError(b'%r is not a valid phasename' % phasename)
1979
1981
1980 minimalphase = phasenamemap[phasename]
1982 minimalphase = phasenamemap[phasename]
1981 getphase = repo._phasecache.phase
1983 getphase = repo._phasecache.phase
1982
1984
1983 def cutfunc(rev):
1985 def cutfunc(rev):
1984 return getphase(repo, rev) < minimalphase
1986 return getphase(repo, rev) < minimalphase
1985
1987
1986 revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
1988 revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
1987
1989
1988 if phasename == b'draft': # need to remove secret changesets
1990 if phasename == b'draft': # need to remove secret changesets
1989 revs = revs.filter(lambda r: getphase(repo, r) == draft)
1991 revs = revs.filter(lambda r: getphase(repo, r) == draft)
1990 return subset & revs
1992 return subset & revs
1991
1993
1992
1994
1993 @predicate(b'public()', safe=True)
1995 @predicate(b'public()', safe=True)
1994 def public(repo, subset, x):
1996 def public(repo, subset, x):
1995 """Changeset in public phase."""
1997 """Changeset in public phase."""
1996 # i18n: "public" is a keyword
1998 # i18n: "public" is a keyword
1997 getargs(x, 0, 0, _(b"public takes no arguments"))
1999 getargs(x, 0, 0, _(b"public takes no arguments"))
1998 return _phase(repo, subset, phases.public)
2000 return _phase(repo, subset, phases.public)
1999
2001
2000
2002
2001 @predicate(b'remote([id [,path]])', safe=False)
2003 @predicate(b'remote([id [,path]])', safe=False)
2002 def remote(repo, subset, x):
2004 def remote(repo, subset, x):
2003 """Local revision that corresponds to the given identifier in a
2005 """Local revision that corresponds to the given identifier in a
2004 remote repository, if present. Here, the '.' identifier is a
2006 remote repository, if present. Here, the '.' identifier is a
2005 synonym for the current local branch.
2007 synonym for the current local branch.
2006 """
2008 """
2007
2009
2008 from . import hg # avoid start-up nasties
2010 from . import hg # avoid start-up nasties
2009
2011
2010 # i18n: "remote" is a keyword
2012 # i18n: "remote" is a keyword
2011 l = getargs(x, 0, 2, _(b"remote takes zero, one, or two arguments"))
2013 l = getargs(x, 0, 2, _(b"remote takes zero, one, or two arguments"))
2012
2014
2013 q = b'.'
2015 q = b'.'
2014 if len(l) > 0:
2016 if len(l) > 0:
2015 # i18n: "remote" is a keyword
2017 # i18n: "remote" is a keyword
2016 q = getstring(l[0], _(b"remote requires a string id"))
2018 q = getstring(l[0], _(b"remote requires a string id"))
2017 if q == b'.':
2019 if q == b'.':
2018 q = repo[b'.'].branch()
2020 q = repo[b'.'].branch()
2019
2021
2020 dest = b''
2022 dest = b''
2021 if len(l) > 1:
2023 if len(l) > 1:
2022 # i18n: "remote" is a keyword
2024 # i18n: "remote" is a keyword
2023 dest = getstring(l[1], _(b"remote requires a repository path"))
2025 dest = getstring(l[1], _(b"remote requires a repository path"))
2024 dest = repo.ui.expandpath(dest or b'default')
2026 dest = repo.ui.expandpath(dest or b'default')
2025 dest, branches = hg.parseurl(dest)
2027 dest, branches = hg.parseurl(dest)
2026 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
2028 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
2027 if revs:
2029 if revs:
2028 revs = [repo.lookup(rev) for rev in revs]
2030 revs = [repo.lookup(rev) for rev in revs]
2029 other = hg.peer(repo, {}, dest)
2031 other = hg.peer(repo, {}, dest)
2030 n = other.lookup(q)
2032 n = other.lookup(q)
2031 if n in repo:
2033 if n in repo:
2032 r = repo[n].rev()
2034 r = repo[n].rev()
2033 if r in subset:
2035 if r in subset:
2034 return baseset([r])
2036 return baseset([r])
2035 return baseset()
2037 return baseset()
2036
2038
2037
2039
2038 @predicate(b'removes(pattern)', safe=True, weight=30)
2040 @predicate(b'removes(pattern)', safe=True, weight=30)
2039 def removes(repo, subset, x):
2041 def removes(repo, subset, x):
2040 """Changesets which remove files matching pattern.
2042 """Changesets which remove files matching pattern.
2041
2043
2042 The pattern without explicit kind like ``glob:`` is expected to be
2044 The pattern without explicit kind like ``glob:`` is expected to be
2043 relative to the current directory and match against a file or a
2045 relative to the current directory and match against a file or a
2044 directory.
2046 directory.
2045 """
2047 """
2046 # i18n: "removes" is a keyword
2048 # i18n: "removes" is a keyword
2047 pat = getstring(x, _(b"removes requires a pattern"))
2049 pat = getstring(x, _(b"removes requires a pattern"))
2048 return checkstatus(repo, subset, pat, 2)
2050 return checkstatus(repo, subset, pat, 2)
2049
2051
2050
2052
2051 @predicate(b'rev(number)', safe=True)
2053 @predicate(b'rev(number)', safe=True)
2052 def rev(repo, subset, x):
2054 def rev(repo, subset, x):
2053 """Revision with the given numeric identifier.
2055 """Revision with the given numeric identifier.
2054 """
2056 """
2055 # i18n: "rev" is a keyword
2057 # i18n: "rev" is a keyword
2056 l = getargs(x, 1, 1, _(b"rev requires one argument"))
2058 l = getargs(x, 1, 1, _(b"rev requires one argument"))
2057 try:
2059 try:
2058 # i18n: "rev" is a keyword
2060 # i18n: "rev" is a keyword
2059 l = int(getstring(l[0], _(b"rev requires a number")))
2061 l = int(getstring(l[0], _(b"rev requires a number")))
2060 except (TypeError, ValueError):
2062 except (TypeError, ValueError):
2061 # i18n: "rev" is a keyword
2063 # i18n: "rev" is a keyword
2062 raise error.ParseError(_(b"rev expects a number"))
2064 raise error.ParseError(_(b"rev expects a number"))
2063 if l not in repo.changelog and l not in _virtualrevs:
2065 if l not in repo.changelog and l not in _virtualrevs:
2064 return baseset()
2066 return baseset()
2065 return subset & baseset([l])
2067 return subset & baseset([l])
2066
2068
2067
2069
2068 @predicate(b'_rev(number)', safe=True)
2070 @predicate(b'_rev(number)', safe=True)
2069 def _rev(repo, subset, x):
2071 def _rev(repo, subset, x):
2070 # internal version of "rev(x)" that raise error if "x" is invalid
2072 # internal version of "rev(x)" that raise error if "x" is invalid
2071 # i18n: "rev" is a keyword
2073 # i18n: "rev" is a keyword
2072 l = getargs(x, 1, 1, _(b"rev requires one argument"))
2074 l = getargs(x, 1, 1, _(b"rev requires one argument"))
2073 try:
2075 try:
2074 # i18n: "rev" is a keyword
2076 # i18n: "rev" is a keyword
2075 l = int(getstring(l[0], _(b"rev requires a number")))
2077 l = int(getstring(l[0], _(b"rev requires a number")))
2076 except (TypeError, ValueError):
2078 except (TypeError, ValueError):
2077 # i18n: "rev" is a keyword
2079 # i18n: "rev" is a keyword
2078 raise error.ParseError(_(b"rev expects a number"))
2080 raise error.ParseError(_(b"rev expects a number"))
2079 repo.changelog.node(l) # check that the rev exists
2081 repo.changelog.node(l) # check that the rev exists
2080 return subset & baseset([l])
2082 return subset & baseset([l])
2081
2083
2082
2084
2083 @predicate(b'revset(set)', safe=True, takeorder=True)
2085 @predicate(b'revset(set)', safe=True, takeorder=True)
2084 def revsetpredicate(repo, subset, x, order):
2086 def revsetpredicate(repo, subset, x, order):
2085 """Strictly interpret the content as a revset.
2087 """Strictly interpret the content as a revset.
2086
2088
2087 The content of this special predicate will be strictly interpreted as a
2089 The content of this special predicate will be strictly interpreted as a
2088 revset. For example, ``revset(id(0))`` will be interpreted as "id(0)"
2090 revset. For example, ``revset(id(0))`` will be interpreted as "id(0)"
2089 without possible ambiguity with a "id(0)" bookmark or tag.
2091 without possible ambiguity with a "id(0)" bookmark or tag.
2090 """
2092 """
2091 return getset(repo, subset, x, order)
2093 return getset(repo, subset, x, order)
2092
2094
2093
2095
2094 @predicate(b'matching(revision [, field])', safe=True)
2096 @predicate(b'matching(revision [, field])', safe=True)
2095 def matching(repo, subset, x):
2097 def matching(repo, subset, x):
2096 """Changesets in which a given set of fields match the set of fields in the
2098 """Changesets in which a given set of fields match the set of fields in the
2097 selected revision or set.
2099 selected revision or set.
2098
2100
2099 To match more than one field pass the list of fields to match separated
2101 To match more than one field pass the list of fields to match separated
2100 by spaces (e.g. ``author description``).
2102 by spaces (e.g. ``author description``).
2101
2103
2102 Valid fields are most regular revision fields and some special fields.
2104 Valid fields are most regular revision fields and some special fields.
2103
2105
2104 Regular revision fields are ``description``, ``author``, ``branch``,
2106 Regular revision fields are ``description``, ``author``, ``branch``,
2105 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
2107 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
2106 and ``diff``.
2108 and ``diff``.
2107 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
2109 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
2108 contents of the revision. Two revisions matching their ``diff`` will
2110 contents of the revision. Two revisions matching their ``diff`` will
2109 also match their ``files``.
2111 also match their ``files``.
2110
2112
2111 Special fields are ``summary`` and ``metadata``:
2113 Special fields are ``summary`` and ``metadata``:
2112 ``summary`` matches the first line of the description.
2114 ``summary`` matches the first line of the description.
2113 ``metadata`` is equivalent to matching ``description user date``
2115 ``metadata`` is equivalent to matching ``description user date``
2114 (i.e. it matches the main metadata fields).
2116 (i.e. it matches the main metadata fields).
2115
2117
2116 ``metadata`` is the default field which is used when no fields are
2118 ``metadata`` is the default field which is used when no fields are
2117 specified. You can match more than one field at a time.
2119 specified. You can match more than one field at a time.
2118 """
2120 """
2119 # i18n: "matching" is a keyword
2121 # i18n: "matching" is a keyword
2120 l = getargs(x, 1, 2, _(b"matching takes 1 or 2 arguments"))
2122 l = getargs(x, 1, 2, _(b"matching takes 1 or 2 arguments"))
2121
2123
2122 revs = getset(repo, fullreposet(repo), l[0])
2124 revs = getset(repo, fullreposet(repo), l[0])
2123
2125
2124 fieldlist = [b'metadata']
2126 fieldlist = [b'metadata']
2125 if len(l) > 1:
2127 if len(l) > 1:
2126 fieldlist = getstring(
2128 fieldlist = getstring(
2127 l[1],
2129 l[1],
2128 # i18n: "matching" is a keyword
2130 # i18n: "matching" is a keyword
2129 _(b"matching requires a string as its second argument"),
2131 _(b"matching requires a string as its second argument"),
2130 ).split()
2132 ).split()
2131
2133
2132 # Make sure that there are no repeated fields,
2134 # Make sure that there are no repeated fields,
2133 # expand the 'special' 'metadata' field type
2135 # expand the 'special' 'metadata' field type
2134 # and check the 'files' whenever we check the 'diff'
2136 # and check the 'files' whenever we check the 'diff'
2135 fields = []
2137 fields = []
2136 for field in fieldlist:
2138 for field in fieldlist:
2137 if field == b'metadata':
2139 if field == b'metadata':
2138 fields += [b'user', b'description', b'date']
2140 fields += [b'user', b'description', b'date']
2139 elif field == b'diff':
2141 elif field == b'diff':
2140 # a revision matching the diff must also match the files
2142 # a revision matching the diff must also match the files
2141 # since matching the diff is very costly, make sure to
2143 # since matching the diff is very costly, make sure to
2142 # also match the files first
2144 # also match the files first
2143 fields += [b'files', b'diff']
2145 fields += [b'files', b'diff']
2144 else:
2146 else:
2145 if field == b'author':
2147 if field == b'author':
2146 field = b'user'
2148 field = b'user'
2147 fields.append(field)
2149 fields.append(field)
2148 fields = set(fields)
2150 fields = set(fields)
2149 if b'summary' in fields and b'description' in fields:
2151 if b'summary' in fields and b'description' in fields:
2150 # If a revision matches its description it also matches its summary
2152 # If a revision matches its description it also matches its summary
2151 fields.discard(b'summary')
2153 fields.discard(b'summary')
2152
2154
2153 # We may want to match more than one field
2155 # We may want to match more than one field
2154 # Not all fields take the same amount of time to be matched
2156 # Not all fields take the same amount of time to be matched
2155 # Sort the selected fields in order of increasing matching cost
2157 # Sort the selected fields in order of increasing matching cost
2156 fieldorder = [
2158 fieldorder = [
2157 b'phase',
2159 b'phase',
2158 b'parents',
2160 b'parents',
2159 b'user',
2161 b'user',
2160 b'date',
2162 b'date',
2161 b'branch',
2163 b'branch',
2162 b'summary',
2164 b'summary',
2163 b'files',
2165 b'files',
2164 b'description',
2166 b'description',
2165 b'substate',
2167 b'substate',
2166 b'diff',
2168 b'diff',
2167 ]
2169 ]
2168
2170
2169 def fieldkeyfunc(f):
2171 def fieldkeyfunc(f):
2170 try:
2172 try:
2171 return fieldorder.index(f)
2173 return fieldorder.index(f)
2172 except ValueError:
2174 except ValueError:
2173 # assume an unknown field is very costly
2175 # assume an unknown field is very costly
2174 return len(fieldorder)
2176 return len(fieldorder)
2175
2177
2176 fields = list(fields)
2178 fields = list(fields)
2177 fields.sort(key=fieldkeyfunc)
2179 fields.sort(key=fieldkeyfunc)
2178
2180
2179 # Each field will be matched with its own "getfield" function
2181 # Each field will be matched with its own "getfield" function
2180 # which will be added to the getfieldfuncs array of functions
2182 # which will be added to the getfieldfuncs array of functions
2181 getfieldfuncs = []
2183 getfieldfuncs = []
2182 _funcs = {
2184 _funcs = {
2183 b'user': lambda r: repo[r].user(),
2185 b'user': lambda r: repo[r].user(),
2184 b'branch': lambda r: repo[r].branch(),
2186 b'branch': lambda r: repo[r].branch(),
2185 b'date': lambda r: repo[r].date(),
2187 b'date': lambda r: repo[r].date(),
2186 b'description': lambda r: repo[r].description(),
2188 b'description': lambda r: repo[r].description(),
2187 b'files': lambda r: repo[r].files(),
2189 b'files': lambda r: repo[r].files(),
2188 b'parents': lambda r: repo[r].parents(),
2190 b'parents': lambda r: repo[r].parents(),
2189 b'phase': lambda r: repo[r].phase(),
2191 b'phase': lambda r: repo[r].phase(),
2190 b'substate': lambda r: repo[r].substate,
2192 b'substate': lambda r: repo[r].substate,
2191 b'summary': lambda r: repo[r].description().splitlines()[0],
2193 b'summary': lambda r: repo[r].description().splitlines()[0],
2192 b'diff': lambda r: list(
2194 b'diff': lambda r: list(
2193 repo[r].diff(opts=diffutil.diffallopts(repo.ui, {b'git': True}))
2195 repo[r].diff(opts=diffutil.diffallopts(repo.ui, {b'git': True}))
2194 ),
2196 ),
2195 }
2197 }
2196 for info in fields:
2198 for info in fields:
2197 getfield = _funcs.get(info, None)
2199 getfield = _funcs.get(info, None)
2198 if getfield is None:
2200 if getfield is None:
2199 raise error.ParseError(
2201 raise error.ParseError(
2200 # i18n: "matching" is a keyword
2202 # i18n: "matching" is a keyword
2201 _(b"unexpected field name passed to matching: %s")
2203 _(b"unexpected field name passed to matching: %s")
2202 % info
2204 % info
2203 )
2205 )
2204 getfieldfuncs.append(getfield)
2206 getfieldfuncs.append(getfield)
2205 # convert the getfield array of functions into a "getinfo" function
2207 # convert the getfield array of functions into a "getinfo" function
2206 # which returns an array of field values (or a single value if there
2208 # which returns an array of field values (or a single value if there
2207 # is only one field to match)
2209 # is only one field to match)
2208 getinfo = lambda r: [f(r) for f in getfieldfuncs]
2210 getinfo = lambda r: [f(r) for f in getfieldfuncs]
2209
2211
2210 def matches(x):
2212 def matches(x):
2211 for rev in revs:
2213 for rev in revs:
2212 target = getinfo(rev)
2214 target = getinfo(rev)
2213 match = True
2215 match = True
2214 for n, f in enumerate(getfieldfuncs):
2216 for n, f in enumerate(getfieldfuncs):
2215 if target[n] != f(x):
2217 if target[n] != f(x):
2216 match = False
2218 match = False
2217 if match:
2219 if match:
2218 return True
2220 return True
2219 return False
2221 return False
2220
2222
2221 return subset.filter(matches, condrepr=(b'<matching%r %r>', fields, revs))
2223 return subset.filter(matches, condrepr=(b'<matching%r %r>', fields, revs))
2222
2224
2223
2225
2224 @predicate(b'reverse(set)', safe=True, takeorder=True, weight=0)
2226 @predicate(b'reverse(set)', safe=True, takeorder=True, weight=0)
2225 def reverse(repo, subset, x, order):
2227 def reverse(repo, subset, x, order):
2226 """Reverse order of set.
2228 """Reverse order of set.
2227 """
2229 """
2228 l = getset(repo, subset, x, order)
2230 l = getset(repo, subset, x, order)
2229 if order == defineorder:
2231 if order == defineorder:
2230 l.reverse()
2232 l.reverse()
2231 return l
2233 return l
2232
2234
2233
2235
2234 @predicate(b'roots(set)', safe=True)
2236 @predicate(b'roots(set)', safe=True)
2235 def roots(repo, subset, x):
2237 def roots(repo, subset, x):
2236 """Changesets in set with no parent changeset in set.
2238 """Changesets in set with no parent changeset in set.
2237 """
2239 """
2238 s = getset(repo, fullreposet(repo), x)
2240 s = getset(repo, fullreposet(repo), x)
2239 parents = repo.changelog.parentrevs
2241 parents = repo.changelog.parentrevs
2240
2242
2241 def filter(r):
2243 def filter(r):
2242 for p in parents(r):
2244 for p in parents(r):
2243 if 0 <= p and p in s:
2245 if 0 <= p and p in s:
2244 return False
2246 return False
2245 return True
2247 return True
2246
2248
2247 return subset & s.filter(filter, condrepr=b'<roots>')
2249 return subset & s.filter(filter, condrepr=b'<roots>')
2248
2250
2249
2251
2250 _sortkeyfuncs = {
2252 _sortkeyfuncs = {
2251 b'rev': lambda c: c.rev(),
2253 b'rev': lambda c: c.rev(),
2252 b'branch': lambda c: c.branch(),
2254 b'branch': lambda c: c.branch(),
2253 b'desc': lambda c: c.description(),
2255 b'desc': lambda c: c.description(),
2254 b'user': lambda c: c.user(),
2256 b'user': lambda c: c.user(),
2255 b'author': lambda c: c.user(),
2257 b'author': lambda c: c.user(),
2256 b'date': lambda c: c.date()[0],
2258 b'date': lambda c: c.date()[0],
2257 }
2259 }
2258
2260
2259
2261
2260 def _getsortargs(x):
2262 def _getsortargs(x):
2261 """Parse sort options into (set, [(key, reverse)], opts)"""
2263 """Parse sort options into (set, [(key, reverse)], opts)"""
2262 args = getargsdict(x, b'sort', b'set keys topo.firstbranch')
2264 args = getargsdict(x, b'sort', b'set keys topo.firstbranch')
2263 if b'set' not in args:
2265 if b'set' not in args:
2264 # i18n: "sort" is a keyword
2266 # i18n: "sort" is a keyword
2265 raise error.ParseError(_(b'sort requires one or two arguments'))
2267 raise error.ParseError(_(b'sort requires one or two arguments'))
2266 keys = b"rev"
2268 keys = b"rev"
2267 if b'keys' in args:
2269 if b'keys' in args:
2268 # i18n: "sort" is a keyword
2270 # i18n: "sort" is a keyword
2269 keys = getstring(args[b'keys'], _(b"sort spec must be a string"))
2271 keys = getstring(args[b'keys'], _(b"sort spec must be a string"))
2270
2272
2271 keyflags = []
2273 keyflags = []
2272 for k in keys.split():
2274 for k in keys.split():
2273 fk = k
2275 fk = k
2274 reverse = k.startswith(b'-')
2276 reverse = k.startswith(b'-')
2275 if reverse:
2277 if reverse:
2276 k = k[1:]
2278 k = k[1:]
2277 if k not in _sortkeyfuncs and k != b'topo':
2279 if k not in _sortkeyfuncs and k != b'topo':
2278 raise error.ParseError(
2280 raise error.ParseError(
2279 _(b"unknown sort key %r") % pycompat.bytestr(fk)
2281 _(b"unknown sort key %r") % pycompat.bytestr(fk)
2280 )
2282 )
2281 keyflags.append((k, reverse))
2283 keyflags.append((k, reverse))
2282
2284
2283 if len(keyflags) > 1 and any(k == b'topo' for k, reverse in keyflags):
2285 if len(keyflags) > 1 and any(k == b'topo' for k, reverse in keyflags):
2284 # i18n: "topo" is a keyword
2286 # i18n: "topo" is a keyword
2285 raise error.ParseError(
2287 raise error.ParseError(
2286 _(b'topo sort order cannot be combined with other sort keys')
2288 _(b'topo sort order cannot be combined with other sort keys')
2287 )
2289 )
2288
2290
2289 opts = {}
2291 opts = {}
2290 if b'topo.firstbranch' in args:
2292 if b'topo.firstbranch' in args:
2291 if any(k == b'topo' for k, reverse in keyflags):
2293 if any(k == b'topo' for k, reverse in keyflags):
2292 opts[b'topo.firstbranch'] = args[b'topo.firstbranch']
2294 opts[b'topo.firstbranch'] = args[b'topo.firstbranch']
2293 else:
2295 else:
2294 # i18n: "topo" and "topo.firstbranch" are keywords
2296 # i18n: "topo" and "topo.firstbranch" are keywords
2295 raise error.ParseError(
2297 raise error.ParseError(
2296 _(
2298 _(
2297 b'topo.firstbranch can only be used '
2299 b'topo.firstbranch can only be used '
2298 b'when using the topo sort key'
2300 b'when using the topo sort key'
2299 )
2301 )
2300 )
2302 )
2301
2303
2302 return args[b'set'], keyflags, opts
2304 return args[b'set'], keyflags, opts
2303
2305
2304
2306
2305 @predicate(
2307 @predicate(
2306 b'sort(set[, [-]key... [, ...]])', safe=True, takeorder=True, weight=10
2308 b'sort(set[, [-]key... [, ...]])', safe=True, takeorder=True, weight=10
2307 )
2309 )
2308 def sort(repo, subset, x, order):
2310 def sort(repo, subset, x, order):
2309 """Sort set by keys. The default sort order is ascending, specify a key
2311 """Sort set by keys. The default sort order is ascending, specify a key
2310 as ``-key`` to sort in descending order.
2312 as ``-key`` to sort in descending order.
2311
2313
2312 The keys can be:
2314 The keys can be:
2313
2315
2314 - ``rev`` for the revision number,
2316 - ``rev`` for the revision number,
2315 - ``branch`` for the branch name,
2317 - ``branch`` for the branch name,
2316 - ``desc`` for the commit message (description),
2318 - ``desc`` for the commit message (description),
2317 - ``user`` for user name (``author`` can be used as an alias),
2319 - ``user`` for user name (``author`` can be used as an alias),
2318 - ``date`` for the commit date
2320 - ``date`` for the commit date
2319 - ``topo`` for a reverse topographical sort
2321 - ``topo`` for a reverse topographical sort
2320
2322
2321 The ``topo`` sort order cannot be combined with other sort keys. This sort
2323 The ``topo`` sort order cannot be combined with other sort keys. This sort
2322 takes one optional argument, ``topo.firstbranch``, which takes a revset that
2324 takes one optional argument, ``topo.firstbranch``, which takes a revset that
2323 specifies what topographical branches to prioritize in the sort.
2325 specifies what topographical branches to prioritize in the sort.
2324
2326
2325 """
2327 """
2326 s, keyflags, opts = _getsortargs(x)
2328 s, keyflags, opts = _getsortargs(x)
2327 revs = getset(repo, subset, s, order)
2329 revs = getset(repo, subset, s, order)
2328
2330
2329 if not keyflags or order != defineorder:
2331 if not keyflags or order != defineorder:
2330 return revs
2332 return revs
2331 if len(keyflags) == 1 and keyflags[0][0] == b"rev":
2333 if len(keyflags) == 1 and keyflags[0][0] == b"rev":
2332 revs.sort(reverse=keyflags[0][1])
2334 revs.sort(reverse=keyflags[0][1])
2333 return revs
2335 return revs
2334 elif keyflags[0][0] == b"topo":
2336 elif keyflags[0][0] == b"topo":
2335 firstbranch = ()
2337 firstbranch = ()
2336 if b'topo.firstbranch' in opts:
2338 if b'topo.firstbranch' in opts:
2337 firstbranch = getset(repo, subset, opts[b'topo.firstbranch'])
2339 firstbranch = getset(repo, subset, opts[b'topo.firstbranch'])
2338 revs = baseset(
2340 revs = baseset(
2339 dagop.toposort(revs, repo.changelog.parentrevs, firstbranch),
2341 dagop.toposort(revs, repo.changelog.parentrevs, firstbranch),
2340 istopo=True,
2342 istopo=True,
2341 )
2343 )
2342 if keyflags[0][1]:
2344 if keyflags[0][1]:
2343 revs.reverse()
2345 revs.reverse()
2344 return revs
2346 return revs
2345
2347
2346 # sort() is guaranteed to be stable
2348 # sort() is guaranteed to be stable
2347 ctxs = [repo[r] for r in revs]
2349 ctxs = [repo[r] for r in revs]
2348 for k, reverse in reversed(keyflags):
2350 for k, reverse in reversed(keyflags):
2349 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
2351 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
2350 return baseset([c.rev() for c in ctxs])
2352 return baseset([c.rev() for c in ctxs])
2351
2353
2352
2354
2353 @predicate(b'subrepo([pattern])')
2355 @predicate(b'subrepo([pattern])')
2354 def subrepo(repo, subset, x):
2356 def subrepo(repo, subset, x):
2355 """Changesets that add, modify or remove the given subrepo. If no subrepo
2357 """Changesets that add, modify or remove the given subrepo. If no subrepo
2356 pattern is named, any subrepo changes are returned.
2358 pattern is named, any subrepo changes are returned.
2357 """
2359 """
2358 # i18n: "subrepo" is a keyword
2360 # i18n: "subrepo" is a keyword
2359 args = getargs(x, 0, 1, _(b'subrepo takes at most one argument'))
2361 args = getargs(x, 0, 1, _(b'subrepo takes at most one argument'))
2360 pat = None
2362 pat = None
2361 if len(args) != 0:
2363 if len(args) != 0:
2362 pat = getstring(args[0], _(b"subrepo requires a pattern"))
2364 pat = getstring(args[0], _(b"subrepo requires a pattern"))
2363
2365
2364 m = matchmod.exact([b'.hgsubstate'])
2366 m = matchmod.exact([b'.hgsubstate'])
2365
2367
2366 def submatches(names):
2368 def submatches(names):
2367 k, p, m = stringutil.stringmatcher(pat)
2369 k, p, m = stringutil.stringmatcher(pat)
2368 for name in names:
2370 for name in names:
2369 if m(name):
2371 if m(name):
2370 yield name
2372 yield name
2371
2373
2372 def matches(x):
2374 def matches(x):
2373 c = repo[x]
2375 c = repo[x]
2374 s = repo.status(c.p1().node(), c.node(), match=m)
2376 s = repo.status(c.p1().node(), c.node(), match=m)
2375
2377
2376 if pat is None:
2378 if pat is None:
2377 return s.added or s.modified or s.removed
2379 return s.added or s.modified or s.removed
2378
2380
2379 if s.added:
2381 if s.added:
2380 return any(submatches(c.substate.keys()))
2382 return any(submatches(c.substate.keys()))
2381
2383
2382 if s.modified:
2384 if s.modified:
2383 subs = set(c.p1().substate.keys())
2385 subs = set(c.p1().substate.keys())
2384 subs.update(c.substate.keys())
2386 subs.update(c.substate.keys())
2385
2387
2386 for path in submatches(subs):
2388 for path in submatches(subs):
2387 if c.p1().substate.get(path) != c.substate.get(path):
2389 if c.p1().substate.get(path) != c.substate.get(path):
2388 return True
2390 return True
2389
2391
2390 if s.removed:
2392 if s.removed:
2391 return any(submatches(c.p1().substate.keys()))
2393 return any(submatches(c.p1().substate.keys()))
2392
2394
2393 return False
2395 return False
2394
2396
2395 return subset.filter(matches, condrepr=(b'<subrepo %r>', pat))
2397 return subset.filter(matches, condrepr=(b'<subrepo %r>', pat))
2396
2398
2397
2399
2398 def _mapbynodefunc(repo, s, f):
2400 def _mapbynodefunc(repo, s, f):
2399 """(repo, smartset, [node] -> [node]) -> smartset
2401 """(repo, smartset, [node] -> [node]) -> smartset
2400
2402
2401 Helper method to map a smartset to another smartset given a function only
2403 Helper method to map a smartset to another smartset given a function only
2402 talking about nodes. Handles converting between rev numbers and nodes, and
2404 talking about nodes. Handles converting between rev numbers and nodes, and
2403 filtering.
2405 filtering.
2404 """
2406 """
2405 cl = repo.unfiltered().changelog
2407 cl = repo.unfiltered().changelog
2406 torev = cl.index.get_rev
2408 torev = cl.index.get_rev
2407 tonode = cl.node
2409 tonode = cl.node
2408 result = set(torev(n) for n in f(tonode(r) for r in s))
2410 result = set(torev(n) for n in f(tonode(r) for r in s))
2409 result.discard(None)
2411 result.discard(None)
2410 return smartset.baseset(result - repo.changelog.filteredrevs)
2412 return smartset.baseset(result - repo.changelog.filteredrevs)
2411
2413
2412
2414
2413 @predicate(b'successors(set)', safe=True)
2415 @predicate(b'successors(set)', safe=True)
2414 def successors(repo, subset, x):
2416 def successors(repo, subset, x):
2415 """All successors for set, including the given set themselves"""
2417 """All successors for set, including the given set themselves"""
2416 s = getset(repo, fullreposet(repo), x)
2418 s = getset(repo, fullreposet(repo), x)
2417 f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
2419 f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
2418 d = _mapbynodefunc(repo, s, f)
2420 d = _mapbynodefunc(repo, s, f)
2419 return subset & d
2421 return subset & d
2420
2422
2421
2423
2422 def _substringmatcher(pattern, casesensitive=True):
2424 def _substringmatcher(pattern, casesensitive=True):
2423 kind, pattern, matcher = stringutil.stringmatcher(
2425 kind, pattern, matcher = stringutil.stringmatcher(
2424 pattern, casesensitive=casesensitive
2426 pattern, casesensitive=casesensitive
2425 )
2427 )
2426 if kind == b'literal':
2428 if kind == b'literal':
2427 if not casesensitive:
2429 if not casesensitive:
2428 pattern = encoding.lower(pattern)
2430 pattern = encoding.lower(pattern)
2429 matcher = lambda s: pattern in encoding.lower(s)
2431 matcher = lambda s: pattern in encoding.lower(s)
2430 else:
2432 else:
2431 matcher = lambda s: pattern in s
2433 matcher = lambda s: pattern in s
2432 return kind, pattern, matcher
2434 return kind, pattern, matcher
2433
2435
2434
2436
2435 @predicate(b'tag([name])', safe=True)
2437 @predicate(b'tag([name])', safe=True)
2436 def tag(repo, subset, x):
2438 def tag(repo, subset, x):
2437 """The specified tag by name, or all tagged revisions if no name is given.
2439 """The specified tag by name, or all tagged revisions if no name is given.
2438
2440
2439 Pattern matching is supported for `name`. See
2441 Pattern matching is supported for `name`. See
2440 :hg:`help revisions.patterns`.
2442 :hg:`help revisions.patterns`.
2441 """
2443 """
2442 # i18n: "tag" is a keyword
2444 # i18n: "tag" is a keyword
2443 args = getargs(x, 0, 1, _(b"tag takes one or no arguments"))
2445 args = getargs(x, 0, 1, _(b"tag takes one or no arguments"))
2444 cl = repo.changelog
2446 cl = repo.changelog
2445 if args:
2447 if args:
2446 pattern = getstring(
2448 pattern = getstring(
2447 args[0],
2449 args[0],
2448 # i18n: "tag" is a keyword
2450 # i18n: "tag" is a keyword
2449 _(b'the argument to tag must be a string'),
2451 _(b'the argument to tag must be a string'),
2450 )
2452 )
2451 kind, pattern, matcher = stringutil.stringmatcher(pattern)
2453 kind, pattern, matcher = stringutil.stringmatcher(pattern)
2452 if kind == b'literal':
2454 if kind == b'literal':
2453 # avoid resolving all tags
2455 # avoid resolving all tags
2454 tn = repo._tagscache.tags.get(pattern, None)
2456 tn = repo._tagscache.tags.get(pattern, None)
2455 if tn is None:
2457 if tn is None:
2456 raise error.RepoLookupError(
2458 raise error.RepoLookupError(
2457 _(b"tag '%s' does not exist") % pattern
2459 _(b"tag '%s' does not exist") % pattern
2458 )
2460 )
2459 s = {repo[tn].rev()}
2461 s = {repo[tn].rev()}
2460 else:
2462 else:
2461 s = {cl.rev(n) for t, n in repo.tagslist() if matcher(t)}
2463 s = {cl.rev(n) for t, n in repo.tagslist() if matcher(t)}
2462 else:
2464 else:
2463 s = {cl.rev(n) for t, n in repo.tagslist() if t != b'tip'}
2465 s = {cl.rev(n) for t, n in repo.tagslist() if t != b'tip'}
2464 return subset & s
2466 return subset & s
2465
2467
2466
2468
2467 @predicate(b'tagged', safe=True)
2469 @predicate(b'tagged', safe=True)
2468 def tagged(repo, subset, x):
2470 def tagged(repo, subset, x):
2469 return tag(repo, subset, x)
2471 return tag(repo, subset, x)
2470
2472
2471
2473
2472 @predicate(b'orphan()', safe=True)
2474 @predicate(b'orphan()', safe=True)
2473 def orphan(repo, subset, x):
2475 def orphan(repo, subset, x):
2474 """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)
2476 """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)
2475 """
2477 """
2476 # i18n: "orphan" is a keyword
2478 # i18n: "orphan" is a keyword
2477 getargs(x, 0, 0, _(b"orphan takes no arguments"))
2479 getargs(x, 0, 0, _(b"orphan takes no arguments"))
2478 orphan = obsmod.getrevs(repo, b'orphan')
2480 orphan = obsmod.getrevs(repo, b'orphan')
2479 return subset & orphan
2481 return subset & orphan
2480
2482
2481
2483
2482 @predicate(b'user(string)', safe=True, weight=10)
2484 @predicate(b'user(string)', safe=True, weight=10)
2483 def user(repo, subset, x):
2485 def user(repo, subset, x):
2484 """User name contains string. The match is case-insensitive.
2486 """User name contains string. The match is case-insensitive.
2485
2487
2486 Pattern matching is supported for `string`. See
2488 Pattern matching is supported for `string`. See
2487 :hg:`help revisions.patterns`.
2489 :hg:`help revisions.patterns`.
2488 """
2490 """
2489 return author(repo, subset, x)
2491 return author(repo, subset, x)
2490
2492
2491
2493
2492 @predicate(b'wdir()', safe=True, weight=0)
2494 @predicate(b'wdir()', safe=True, weight=0)
2493 def wdir(repo, subset, x):
2495 def wdir(repo, subset, x):
2494 """Working directory. (EXPERIMENTAL)"""
2496 """Working directory. (EXPERIMENTAL)"""
2495 # i18n: "wdir" is a keyword
2497 # i18n: "wdir" is a keyword
2496 getargs(x, 0, 0, _(b"wdir takes no arguments"))
2498 getargs(x, 0, 0, _(b"wdir takes no arguments"))
2497 if node.wdirrev in subset or isinstance(subset, fullreposet):
2499 if node.wdirrev in subset or isinstance(subset, fullreposet):
2498 return baseset([node.wdirrev])
2500 return baseset([node.wdirrev])
2499 return baseset()
2501 return baseset()
2500
2502
2501
2503
2502 def _orderedlist(repo, subset, x):
2504 def _orderedlist(repo, subset, x):
2503 s = getstring(x, b"internal error")
2505 s = getstring(x, b"internal error")
2504 if not s:
2506 if not s:
2505 return baseset()
2507 return baseset()
2506 # remove duplicates here. it's difficult for caller to deduplicate sets
2508 # remove duplicates here. it's difficult for caller to deduplicate sets
2507 # because different symbols can point to the same rev.
2509 # because different symbols can point to the same rev.
2508 cl = repo.changelog
2510 cl = repo.changelog
2509 ls = []
2511 ls = []
2510 seen = set()
2512 seen = set()
2511 for t in s.split(b'\0'):
2513 for t in s.split(b'\0'):
2512 try:
2514 try:
2513 # fast path for integer revision
2515 # fast path for integer revision
2514 r = int(t)
2516 r = int(t)
2515 if (b'%d' % r) != t or r not in cl:
2517 if (b'%d' % r) != t or r not in cl:
2516 raise ValueError
2518 raise ValueError
2517 revs = [r]
2519 revs = [r]
2518 except ValueError:
2520 except ValueError:
2519 revs = stringset(repo, subset, t, defineorder)
2521 revs = stringset(repo, subset, t, defineorder)
2520
2522
2521 for r in revs:
2523 for r in revs:
2522 if r in seen:
2524 if r in seen:
2523 continue
2525 continue
2524 if (
2526 if (
2525 r in subset
2527 r in subset
2526 or r in _virtualrevs
2528 or r in _virtualrevs
2527 and isinstance(subset, fullreposet)
2529 and isinstance(subset, fullreposet)
2528 ):
2530 ):
2529 ls.append(r)
2531 ls.append(r)
2530 seen.add(r)
2532 seen.add(r)
2531 return baseset(ls)
2533 return baseset(ls)
2532
2534
2533
2535
2534 # for internal use
2536 # for internal use
2535 @predicate(b'_list', safe=True, takeorder=True)
2537 @predicate(b'_list', safe=True, takeorder=True)
2536 def _list(repo, subset, x, order):
2538 def _list(repo, subset, x, order):
2537 if order == followorder:
2539 if order == followorder:
2538 # slow path to take the subset order
2540 # slow path to take the subset order
2539 return subset & _orderedlist(repo, fullreposet(repo), x)
2541 return subset & _orderedlist(repo, fullreposet(repo), x)
2540 else:
2542 else:
2541 return _orderedlist(repo, subset, x)
2543 return _orderedlist(repo, subset, x)
2542
2544
2543
2545
2544 def _orderedintlist(repo, subset, x):
2546 def _orderedintlist(repo, subset, x):
2545 s = getstring(x, b"internal error")
2547 s = getstring(x, b"internal error")
2546 if not s:
2548 if not s:
2547 return baseset()
2549 return baseset()
2548 ls = [int(r) for r in s.split(b'\0')]
2550 ls = [int(r) for r in s.split(b'\0')]
2549 s = subset
2551 s = subset
2550 return baseset([r for r in ls if r in s])
2552 return baseset([r for r in ls if r in s])
2551
2553
2552
2554
2553 # for internal use
2555 # for internal use
2554 @predicate(b'_intlist', safe=True, takeorder=True, weight=0)
2556 @predicate(b'_intlist', safe=True, takeorder=True, weight=0)
2555 def _intlist(repo, subset, x, order):
2557 def _intlist(repo, subset, x, order):
2556 if order == followorder:
2558 if order == followorder:
2557 # slow path to take the subset order
2559 # slow path to take the subset order
2558 return subset & _orderedintlist(repo, fullreposet(repo), x)
2560 return subset & _orderedintlist(repo, fullreposet(repo), x)
2559 else:
2561 else:
2560 return _orderedintlist(repo, subset, x)
2562 return _orderedintlist(repo, subset, x)
2561
2563
2562
2564
2563 def _orderedhexlist(repo, subset, x):
2565 def _orderedhexlist(repo, subset, x):
2564 s = getstring(x, b"internal error")
2566 s = getstring(x, b"internal error")
2565 if not s:
2567 if not s:
2566 return baseset()
2568 return baseset()
2567 cl = repo.changelog
2569 cl = repo.changelog
2568 ls = [cl.rev(node.bin(r)) for r in s.split(b'\0')]
2570 ls = [cl.rev(node.bin(r)) for r in s.split(b'\0')]
2569 s = subset
2571 s = subset
2570 return baseset([r for r in ls if r in s])
2572 return baseset([r for r in ls if r in s])
2571
2573
2572
2574
2573 # for internal use
2575 # for internal use
2574 @predicate(b'_hexlist', safe=True, takeorder=True)
2576 @predicate(b'_hexlist', safe=True, takeorder=True)
2575 def _hexlist(repo, subset, x, order):
2577 def _hexlist(repo, subset, x, order):
2576 if order == followorder:
2578 if order == followorder:
2577 # slow path to take the subset order
2579 # slow path to take the subset order
2578 return subset & _orderedhexlist(repo, fullreposet(repo), x)
2580 return subset & _orderedhexlist(repo, fullreposet(repo), x)
2579 else:
2581 else:
2580 return _orderedhexlist(repo, subset, x)
2582 return _orderedhexlist(repo, subset, x)
2581
2583
2582
2584
2583 methods = {
2585 methods = {
2584 b"range": rangeset,
2586 b"range": rangeset,
2585 b"rangeall": rangeall,
2587 b"rangeall": rangeall,
2586 b"rangepre": rangepre,
2588 b"rangepre": rangepre,
2587 b"rangepost": rangepost,
2589 b"rangepost": rangepost,
2588 b"dagrange": dagrange,
2590 b"dagrange": dagrange,
2589 b"string": stringset,
2591 b"string": stringset,
2590 b"symbol": stringset,
2592 b"symbol": stringset,
2591 b"and": andset,
2593 b"and": andset,
2592 b"andsmally": andsmallyset,
2594 b"andsmally": andsmallyset,
2593 b"or": orset,
2595 b"or": orset,
2594 b"not": notset,
2596 b"not": notset,
2595 b"difference": differenceset,
2597 b"difference": differenceset,
2596 b"relation": relationset,
2598 b"relation": relationset,
2597 b"relsubscript": relsubscriptset,
2599 b"relsubscript": relsubscriptset,
2598 b"subscript": subscriptset,
2600 b"subscript": subscriptset,
2599 b"list": listset,
2601 b"list": listset,
2600 b"keyvalue": keyvaluepair,
2602 b"keyvalue": keyvaluepair,
2601 b"func": func,
2603 b"func": func,
2602 b"ancestor": ancestorspec,
2604 b"ancestor": ancestorspec,
2603 b"parent": parentspec,
2605 b"parent": parentspec,
2604 b"parentpost": parentpost,
2606 b"parentpost": parentpost,
2605 b"smartset": rawsmartset,
2607 b"smartset": rawsmartset,
2606 }
2608 }
2607
2609
2608 subscriptrelations = {
2610 subscriptrelations = {
2609 b"g": generationsrel,
2611 b"g": generationsrel,
2610 b"generations": generationsrel,
2612 b"generations": generationsrel,
2611 }
2613 }
2612
2614
2613
2615
2614 def lookupfn(repo):
2616 def lookupfn(repo):
2615 return lambda symbol: scmutil.isrevsymbol(repo, symbol)
2617 return lambda symbol: scmutil.isrevsymbol(repo, symbol)
2616
2618
2617
2619
2618 def match(ui, spec, lookup=None):
2620 def match(ui, spec, lookup=None):
2619 """Create a matcher for a single revision spec"""
2621 """Create a matcher for a single revision spec"""
2620 return matchany(ui, [spec], lookup=lookup)
2622 return matchany(ui, [spec], lookup=lookup)
2621
2623
2622
2624
2623 def matchany(ui, specs, lookup=None, localalias=None):
2625 def matchany(ui, specs, lookup=None, localalias=None):
2624 """Create a matcher that will include any revisions matching one of the
2626 """Create a matcher that will include any revisions matching one of the
2625 given specs
2627 given specs
2626
2628
2627 If lookup function is not None, the parser will first attempt to handle
2629 If lookup function is not None, the parser will first attempt to handle
2628 old-style ranges, which may contain operator characters.
2630 old-style ranges, which may contain operator characters.
2629
2631
2630 If localalias is not None, it is a dict {name: definitionstring}. It takes
2632 If localalias is not None, it is a dict {name: definitionstring}. It takes
2631 precedence over [revsetalias] config section.
2633 precedence over [revsetalias] config section.
2632 """
2634 """
2633 if not specs:
2635 if not specs:
2634
2636
2635 def mfunc(repo, subset=None):
2637 def mfunc(repo, subset=None):
2636 return baseset()
2638 return baseset()
2637
2639
2638 return mfunc
2640 return mfunc
2639 if not all(specs):
2641 if not all(specs):
2640 raise error.ParseError(_(b"empty query"))
2642 raise error.ParseError(_(b"empty query"))
2641 if len(specs) == 1:
2643 if len(specs) == 1:
2642 tree = revsetlang.parse(specs[0], lookup)
2644 tree = revsetlang.parse(specs[0], lookup)
2643 else:
2645 else:
2644 tree = (
2646 tree = (
2645 b'or',
2647 b'or',
2646 (b'list',) + tuple(revsetlang.parse(s, lookup) for s in specs),
2648 (b'list',) + tuple(revsetlang.parse(s, lookup) for s in specs),
2647 )
2649 )
2648
2650
2649 aliases = []
2651 aliases = []
2650 warn = None
2652 warn = None
2651 if ui:
2653 if ui:
2652 aliases.extend(ui.configitems(b'revsetalias'))
2654 aliases.extend(ui.configitems(b'revsetalias'))
2653 warn = ui.warn
2655 warn = ui.warn
2654 if localalias:
2656 if localalias:
2655 aliases.extend(localalias.items())
2657 aliases.extend(localalias.items())
2656 if aliases:
2658 if aliases:
2657 tree = revsetlang.expandaliases(tree, aliases, warn=warn)
2659 tree = revsetlang.expandaliases(tree, aliases, warn=warn)
2658 tree = revsetlang.foldconcat(tree)
2660 tree = revsetlang.foldconcat(tree)
2659 tree = revsetlang.analyze(tree)
2661 tree = revsetlang.analyze(tree)
2660 tree = revsetlang.optimize(tree)
2662 tree = revsetlang.optimize(tree)
2661 return makematcher(tree)
2663 return makematcher(tree)
2662
2664
2663
2665
2664 def makematcher(tree):
2666 def makematcher(tree):
2665 """Create a matcher from an evaluatable tree"""
2667 """Create a matcher from an evaluatable tree"""
2666
2668
2667 def mfunc(repo, subset=None, order=None):
2669 def mfunc(repo, subset=None, order=None):
2668 if order is None:
2670 if order is None:
2669 if subset is None:
2671 if subset is None:
2670 order = defineorder # 'x'
2672 order = defineorder # 'x'
2671 else:
2673 else:
2672 order = followorder # 'subset & x'
2674 order = followorder # 'subset & x'
2673 if subset is None:
2675 if subset is None:
2674 subset = fullreposet(repo)
2676 subset = fullreposet(repo)
2675 return getset(repo, subset, tree, order)
2677 return getset(repo, subset, tree, order)
2676
2678
2677 return mfunc
2679 return mfunc
2678
2680
2679
2681
2680 def loadpredicate(ui, extname, registrarobj):
2682 def loadpredicate(ui, extname, registrarobj):
2681 """Load revset predicates from specified registrarobj
2683 """Load revset predicates from specified registrarobj
2682 """
2684 """
2683 for name, func in pycompat.iteritems(registrarobj._table):
2685 for name, func in pycompat.iteritems(registrarobj._table):
2684 symbols[name] = func
2686 symbols[name] = func
2685 if func._safe:
2687 if func._safe:
2686 safesymbols.add(name)
2688 safesymbols.add(name)
2687
2689
2688
2690
2689 # load built-in predicates explicitly to setup safesymbols
2691 # load built-in predicates explicitly to setup safesymbols
2690 loadpredicate(None, None, predicate)
2692 loadpredicate(None, None, predicate)
2691
2693
2692 # tell hggettext to extract docstrings from these functions:
2694 # tell hggettext to extract docstrings from these functions:
2693 i18nfunctions = symbols.values()
2695 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now