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