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