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