##// END OF EJS Templates
outgoing: use `get_push_paths` in the revset too...
marmoute -
r47695:9519312e default
parent child Browse files
Show More
@@ -1,2812 +1,2811 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 not dest:
1858 if dest:
1859 # ui.getpath() explicitly tests for None, not just a boolean
1859 # ui.getpath() explicitly tests for None, not just a boolean
1860 dest = None
1860 dests = [dest]
1861 path = repo.ui.getpath(dest, default=(b'default-push', b'default'))
1861 else:
1862 if not path:
1862 dests = []
1863 raise error.Abort(
1863 missing = set()
1864 _(b'default repository not configured!'),
1864 for path in urlutil.get_push_paths(repo, repo.ui, dests):
1865 hint=_(b"see 'hg help config.paths'"),
1866 )
1867 dest = path.pushloc or path.loc
1865 dest = path.pushloc or path.loc
1868 branches = path.branch, []
1866 branches = path.branch, []
1869
1867
1870 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1868 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1871 if revs:
1869 if revs:
1872 revs = [repo.lookup(rev) for rev in revs]
1870 revs = [repo.lookup(rev) for rev in revs]
1873 other = hg.peer(repo, {}, dest)
1871 other = hg.peer(repo, {}, dest)
1874 try:
1872 try:
1875 repo.ui.pushbuffer()
1873 repo.ui.pushbuffer()
1876 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1874 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1877 repo.ui.popbuffer()
1875 repo.ui.popbuffer()
1878 finally:
1876 finally:
1879 other.close()
1877 other.close()
1878 missing.update(outgoing.missing)
1880 cl = repo.changelog
1879 cl = repo.changelog
1881 o = {cl.rev(r) for r in outgoing.missing}
1880 o = {cl.rev(r) for r in missing}
1882 return subset & o
1881 return subset & o
1883
1882
1884
1883
1885 @predicate(b'p1([set])', safe=True)
1884 @predicate(b'p1([set])', safe=True)
1886 def p1(repo, subset, x):
1885 def p1(repo, subset, x):
1887 """First parent of changesets in set, or the working directory."""
1886 """First parent of changesets in set, or the working directory."""
1888 if x is None:
1887 if x is None:
1889 p = repo[x].p1().rev()
1888 p = repo[x].p1().rev()
1890 if p >= 0:
1889 if p >= 0:
1891 return subset & baseset([p])
1890 return subset & baseset([p])
1892 return baseset()
1891 return baseset()
1893
1892
1894 ps = set()
1893 ps = set()
1895 cl = repo.changelog
1894 cl = repo.changelog
1896 for r in getset(repo, fullreposet(repo), x):
1895 for r in getset(repo, fullreposet(repo), x):
1897 try:
1896 try:
1898 ps.add(cl.parentrevs(r)[0])
1897 ps.add(cl.parentrevs(r)[0])
1899 except error.WdirUnsupported:
1898 except error.WdirUnsupported:
1900 ps.add(repo[r].p1().rev())
1899 ps.add(repo[r].p1().rev())
1901 ps -= {nullrev}
1900 ps -= {nullrev}
1902 # XXX we should turn this into a baseset instead of a set, smartset may do
1901 # XXX we should turn this into a baseset instead of a set, smartset may do
1903 # some optimizations from the fact this is a baseset.
1902 # some optimizations from the fact this is a baseset.
1904 return subset & ps
1903 return subset & ps
1905
1904
1906
1905
1907 @predicate(b'p2([set])', safe=True)
1906 @predicate(b'p2([set])', safe=True)
1908 def p2(repo, subset, x):
1907 def p2(repo, subset, x):
1909 """Second parent of changesets in set, or the working directory."""
1908 """Second parent of changesets in set, or the working directory."""
1910 if x is None:
1909 if x is None:
1911 ps = repo[x].parents()
1910 ps = repo[x].parents()
1912 try:
1911 try:
1913 p = ps[1].rev()
1912 p = ps[1].rev()
1914 if p >= 0:
1913 if p >= 0:
1915 return subset & baseset([p])
1914 return subset & baseset([p])
1916 return baseset()
1915 return baseset()
1917 except IndexError:
1916 except IndexError:
1918 return baseset()
1917 return baseset()
1919
1918
1920 ps = set()
1919 ps = set()
1921 cl = repo.changelog
1920 cl = repo.changelog
1922 for r in getset(repo, fullreposet(repo), x):
1921 for r in getset(repo, fullreposet(repo), x):
1923 try:
1922 try:
1924 ps.add(cl.parentrevs(r)[1])
1923 ps.add(cl.parentrevs(r)[1])
1925 except error.WdirUnsupported:
1924 except error.WdirUnsupported:
1926 parents = repo[r].parents()
1925 parents = repo[r].parents()
1927 if len(parents) == 2:
1926 if len(parents) == 2:
1928 ps.add(parents[1])
1927 ps.add(parents[1])
1929 ps -= {nullrev}
1928 ps -= {nullrev}
1930 # XXX we should turn this into a baseset instead of a set, smartset may do
1929 # XXX we should turn this into a baseset instead of a set, smartset may do
1931 # some optimizations from the fact this is a baseset.
1930 # some optimizations from the fact this is a baseset.
1932 return subset & ps
1931 return subset & ps
1933
1932
1934
1933
1935 def parentpost(repo, subset, x, order):
1934 def parentpost(repo, subset, x, order):
1936 return p1(repo, subset, x)
1935 return p1(repo, subset, x)
1937
1936
1938
1937
1939 @predicate(b'parents([set])', safe=True)
1938 @predicate(b'parents([set])', safe=True)
1940 def parents(repo, subset, x):
1939 def parents(repo, subset, x):
1941 """
1940 """
1942 The set of all parents for all changesets in set, or the working directory.
1941 The set of all parents for all changesets in set, or the working directory.
1943 """
1942 """
1944 if x is None:
1943 if x is None:
1945 ps = {p.rev() for p in repo[x].parents()}
1944 ps = {p.rev() for p in repo[x].parents()}
1946 else:
1945 else:
1947 ps = set()
1946 ps = set()
1948 cl = repo.changelog
1947 cl = repo.changelog
1949 up = ps.update
1948 up = ps.update
1950 parentrevs = cl.parentrevs
1949 parentrevs = cl.parentrevs
1951 for r in getset(repo, fullreposet(repo), x):
1950 for r in getset(repo, fullreposet(repo), x):
1952 try:
1951 try:
1953 up(parentrevs(r))
1952 up(parentrevs(r))
1954 except error.WdirUnsupported:
1953 except error.WdirUnsupported:
1955 up(p.rev() for p in repo[r].parents())
1954 up(p.rev() for p in repo[r].parents())
1956 ps -= {nullrev}
1955 ps -= {nullrev}
1957 return subset & ps
1956 return subset & ps
1958
1957
1959
1958
1960 def _phase(repo, subset, *targets):
1959 def _phase(repo, subset, *targets):
1961 """helper to select all rev in <targets> phases"""
1960 """helper to select all rev in <targets> phases"""
1962 return repo._phasecache.getrevset(repo, targets, subset)
1961 return repo._phasecache.getrevset(repo, targets, subset)
1963
1962
1964
1963
1965 @predicate(b'_phase(idx)', safe=True)
1964 @predicate(b'_phase(idx)', safe=True)
1966 def phase(repo, subset, x):
1965 def phase(repo, subset, x):
1967 l = getargs(x, 1, 1, b"_phase requires one argument")
1966 l = getargs(x, 1, 1, b"_phase requires one argument")
1968 target = getinteger(l[0], b"_phase expects a number")
1967 target = getinteger(l[0], b"_phase expects a number")
1969 return _phase(repo, subset, target)
1968 return _phase(repo, subset, target)
1970
1969
1971
1970
1972 @predicate(b'draft()', safe=True)
1971 @predicate(b'draft()', safe=True)
1973 def draft(repo, subset, x):
1972 def draft(repo, subset, x):
1974 """Changeset in draft phase."""
1973 """Changeset in draft phase."""
1975 # i18n: "draft" is a keyword
1974 # i18n: "draft" is a keyword
1976 getargs(x, 0, 0, _(b"draft takes no arguments"))
1975 getargs(x, 0, 0, _(b"draft takes no arguments"))
1977 target = phases.draft
1976 target = phases.draft
1978 return _phase(repo, subset, target)
1977 return _phase(repo, subset, target)
1979
1978
1980
1979
1981 @predicate(b'secret()', safe=True)
1980 @predicate(b'secret()', safe=True)
1982 def secret(repo, subset, x):
1981 def secret(repo, subset, x):
1983 """Changeset in secret phase."""
1982 """Changeset in secret phase."""
1984 # i18n: "secret" is a keyword
1983 # i18n: "secret" is a keyword
1985 getargs(x, 0, 0, _(b"secret takes no arguments"))
1984 getargs(x, 0, 0, _(b"secret takes no arguments"))
1986 target = phases.secret
1985 target = phases.secret
1987 return _phase(repo, subset, target)
1986 return _phase(repo, subset, target)
1988
1987
1989
1988
1990 @predicate(b'stack([revs])', safe=True)
1989 @predicate(b'stack([revs])', safe=True)
1991 def stack(repo, subset, x):
1990 def stack(repo, subset, x):
1992 """Experimental revset for the stack of changesets or working directory
1991 """Experimental revset for the stack of changesets or working directory
1993 parent. (EXPERIMENTAL)
1992 parent. (EXPERIMENTAL)
1994 """
1993 """
1995 if x is None:
1994 if x is None:
1996 stacks = stackmod.getstack(repo)
1995 stacks = stackmod.getstack(repo)
1997 else:
1996 else:
1998 stacks = smartset.baseset([])
1997 stacks = smartset.baseset([])
1999 for revision in getset(repo, fullreposet(repo), x):
1998 for revision in getset(repo, fullreposet(repo), x):
2000 currentstack = stackmod.getstack(repo, revision)
1999 currentstack = stackmod.getstack(repo, revision)
2001 stacks = stacks + currentstack
2000 stacks = stacks + currentstack
2002
2001
2003 return subset & stacks
2002 return subset & stacks
2004
2003
2005
2004
2006 def parentspec(repo, subset, x, n, order):
2005 def parentspec(repo, subset, x, n, order):
2007 """``set^0``
2006 """``set^0``
2008 The set.
2007 The set.
2009 ``set^1`` (or ``set^``), ``set^2``
2008 ``set^1`` (or ``set^``), ``set^2``
2010 First or second parent, respectively, of all changesets in set.
2009 First or second parent, respectively, of all changesets in set.
2011 """
2010 """
2012 try:
2011 try:
2013 n = int(n[1])
2012 n = int(n[1])
2014 if n not in (0, 1, 2):
2013 if n not in (0, 1, 2):
2015 raise ValueError
2014 raise ValueError
2016 except (TypeError, ValueError):
2015 except (TypeError, ValueError):
2017 raise error.ParseError(_(b"^ expects a number 0, 1, or 2"))
2016 raise error.ParseError(_(b"^ expects a number 0, 1, or 2"))
2018 ps = set()
2017 ps = set()
2019 cl = repo.changelog
2018 cl = repo.changelog
2020 for r in getset(repo, fullreposet(repo), x):
2019 for r in getset(repo, fullreposet(repo), x):
2021 if n == 0:
2020 if n == 0:
2022 ps.add(r)
2021 ps.add(r)
2023 elif n == 1:
2022 elif n == 1:
2024 try:
2023 try:
2025 ps.add(cl.parentrevs(r)[0])
2024 ps.add(cl.parentrevs(r)[0])
2026 except error.WdirUnsupported:
2025 except error.WdirUnsupported:
2027 ps.add(repo[r].p1().rev())
2026 ps.add(repo[r].p1().rev())
2028 else:
2027 else:
2029 try:
2028 try:
2030 parents = cl.parentrevs(r)
2029 parents = cl.parentrevs(r)
2031 if parents[1] != nullrev:
2030 if parents[1] != nullrev:
2032 ps.add(parents[1])
2031 ps.add(parents[1])
2033 except error.WdirUnsupported:
2032 except error.WdirUnsupported:
2034 parents = repo[r].parents()
2033 parents = repo[r].parents()
2035 if len(parents) == 2:
2034 if len(parents) == 2:
2036 ps.add(parents[1].rev())
2035 ps.add(parents[1].rev())
2037 return subset & ps
2036 return subset & ps
2038
2037
2039
2038
2040 @predicate(b'present(set)', safe=True, takeorder=True)
2039 @predicate(b'present(set)', safe=True, takeorder=True)
2041 def present(repo, subset, x, order):
2040 def present(repo, subset, x, order):
2042 """An empty set, if any revision in set isn't found; otherwise,
2041 """An empty set, if any revision in set isn't found; otherwise,
2043 all revisions in set.
2042 all revisions in set.
2044
2043
2045 If any of specified revisions is not present in the local repository,
2044 If any of specified revisions is not present in the local repository,
2046 the query is normally aborted. But this predicate allows the query
2045 the query is normally aborted. But this predicate allows the query
2047 to continue even in such cases.
2046 to continue even in such cases.
2048 """
2047 """
2049 try:
2048 try:
2050 return getset(repo, subset, x, order)
2049 return getset(repo, subset, x, order)
2051 except error.RepoLookupError:
2050 except error.RepoLookupError:
2052 return baseset()
2051 return baseset()
2053
2052
2054
2053
2055 # for internal use
2054 # for internal use
2056 @predicate(b'_notpublic', safe=True)
2055 @predicate(b'_notpublic', safe=True)
2057 def _notpublic(repo, subset, x):
2056 def _notpublic(repo, subset, x):
2058 getargs(x, 0, 0, b"_notpublic takes no arguments")
2057 getargs(x, 0, 0, b"_notpublic takes no arguments")
2059 return _phase(repo, subset, phases.draft, phases.secret)
2058 return _phase(repo, subset, phases.draft, phases.secret)
2060
2059
2061
2060
2062 # for internal use
2061 # for internal use
2063 @predicate(b'_phaseandancestors(phasename, set)', safe=True)
2062 @predicate(b'_phaseandancestors(phasename, set)', safe=True)
2064 def _phaseandancestors(repo, subset, x):
2063 def _phaseandancestors(repo, subset, x):
2065 # equivalent to (phasename() & ancestors(set)) but more efficient
2064 # equivalent to (phasename() & ancestors(set)) but more efficient
2066 # phasename could be one of 'draft', 'secret', or '_notpublic'
2065 # phasename could be one of 'draft', 'secret', or '_notpublic'
2067 args = getargs(x, 2, 2, b"_phaseandancestors requires two arguments")
2066 args = getargs(x, 2, 2, b"_phaseandancestors requires two arguments")
2068 phasename = getsymbol(args[0])
2067 phasename = getsymbol(args[0])
2069 s = getset(repo, fullreposet(repo), args[1])
2068 s = getset(repo, fullreposet(repo), args[1])
2070
2069
2071 draft = phases.draft
2070 draft = phases.draft
2072 secret = phases.secret
2071 secret = phases.secret
2073 phasenamemap = {
2072 phasenamemap = {
2074 b'_notpublic': draft,
2073 b'_notpublic': draft,
2075 b'draft': draft, # follow secret's ancestors
2074 b'draft': draft, # follow secret's ancestors
2076 b'secret': secret,
2075 b'secret': secret,
2077 }
2076 }
2078 if phasename not in phasenamemap:
2077 if phasename not in phasenamemap:
2079 raise error.ParseError(b'%r is not a valid phasename' % phasename)
2078 raise error.ParseError(b'%r is not a valid phasename' % phasename)
2080
2079
2081 minimalphase = phasenamemap[phasename]
2080 minimalphase = phasenamemap[phasename]
2082 getphase = repo._phasecache.phase
2081 getphase = repo._phasecache.phase
2083
2082
2084 def cutfunc(rev):
2083 def cutfunc(rev):
2085 return getphase(repo, rev) < minimalphase
2084 return getphase(repo, rev) < minimalphase
2086
2085
2087 revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
2086 revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
2088
2087
2089 if phasename == b'draft': # need to remove secret changesets
2088 if phasename == b'draft': # need to remove secret changesets
2090 revs = revs.filter(lambda r: getphase(repo, r) == draft)
2089 revs = revs.filter(lambda r: getphase(repo, r) == draft)
2091 return subset & revs
2090 return subset & revs
2092
2091
2093
2092
2094 @predicate(b'public()', safe=True)
2093 @predicate(b'public()', safe=True)
2095 def public(repo, subset, x):
2094 def public(repo, subset, x):
2096 """Changeset in public phase."""
2095 """Changeset in public phase."""
2097 # i18n: "public" is a keyword
2096 # i18n: "public" is a keyword
2098 getargs(x, 0, 0, _(b"public takes no arguments"))
2097 getargs(x, 0, 0, _(b"public takes no arguments"))
2099 return _phase(repo, subset, phases.public)
2098 return _phase(repo, subset, phases.public)
2100
2099
2101
2100
2102 @predicate(b'remote([id [,path]])', safe=False)
2101 @predicate(b'remote([id [,path]])', safe=False)
2103 def remote(repo, subset, x):
2102 def remote(repo, subset, x):
2104 """Local revision that corresponds to the given identifier in a
2103 """Local revision that corresponds to the given identifier in a
2105 remote repository, if present. Here, the '.' identifier is a
2104 remote repository, if present. Here, the '.' identifier is a
2106 synonym for the current local branch.
2105 synonym for the current local branch.
2107 """
2106 """
2108
2107
2109 from . import hg # avoid start-up nasties
2108 from . import hg # avoid start-up nasties
2110
2109
2111 # i18n: "remote" is a keyword
2110 # i18n: "remote" is a keyword
2112 l = getargs(x, 0, 2, _(b"remote takes zero, one, or two arguments"))
2111 l = getargs(x, 0, 2, _(b"remote takes zero, one, or two arguments"))
2113
2112
2114 q = b'.'
2113 q = b'.'
2115 if len(l) > 0:
2114 if len(l) > 0:
2116 # i18n: "remote" is a keyword
2115 # i18n: "remote" is a keyword
2117 q = getstring(l[0], _(b"remote requires a string id"))
2116 q = getstring(l[0], _(b"remote requires a string id"))
2118 if q == b'.':
2117 if q == b'.':
2119 q = repo[b'.'].branch()
2118 q = repo[b'.'].branch()
2120
2119
2121 dest = b''
2120 dest = b''
2122 if len(l) > 1:
2121 if len(l) > 1:
2123 # i18n: "remote" is a keyword
2122 # i18n: "remote" is a keyword
2124 dest = getstring(l[1], _(b"remote requires a repository path"))
2123 dest = getstring(l[1], _(b"remote requires a repository path"))
2125 dest = repo.ui.expandpath(dest or b'default')
2124 dest = repo.ui.expandpath(dest or b'default')
2126 dest, branches = urlutil.parseurl(dest)
2125 dest, branches = urlutil.parseurl(dest)
2127
2126
2128 other = hg.peer(repo, {}, dest)
2127 other = hg.peer(repo, {}, dest)
2129 n = other.lookup(q)
2128 n = other.lookup(q)
2130 if n in repo:
2129 if n in repo:
2131 r = repo[n].rev()
2130 r = repo[n].rev()
2132 if r in subset:
2131 if r in subset:
2133 return baseset([r])
2132 return baseset([r])
2134 return baseset()
2133 return baseset()
2135
2134
2136
2135
2137 @predicate(b'removes(pattern)', safe=True, weight=30)
2136 @predicate(b'removes(pattern)', safe=True, weight=30)
2138 def removes(repo, subset, x):
2137 def removes(repo, subset, x):
2139 """Changesets which remove files matching pattern.
2138 """Changesets which remove files matching pattern.
2140
2139
2141 The pattern without explicit kind like ``glob:`` is expected to be
2140 The pattern without explicit kind like ``glob:`` is expected to be
2142 relative to the current directory and match against a file or a
2141 relative to the current directory and match against a file or a
2143 directory.
2142 directory.
2144 """
2143 """
2145 # i18n: "removes" is a keyword
2144 # i18n: "removes" is a keyword
2146 pat = getstring(x, _(b"removes requires a pattern"))
2145 pat = getstring(x, _(b"removes requires a pattern"))
2147 return checkstatus(repo, subset, pat, 'removed')
2146 return checkstatus(repo, subset, pat, 'removed')
2148
2147
2149
2148
2150 @predicate(b'rev(number)', safe=True)
2149 @predicate(b'rev(number)', safe=True)
2151 def rev(repo, subset, x):
2150 def rev(repo, subset, x):
2152 """Revision with the given numeric identifier."""
2151 """Revision with the given numeric identifier."""
2153 try:
2152 try:
2154 return _rev(repo, subset, x)
2153 return _rev(repo, subset, x)
2155 except error.RepoLookupError:
2154 except error.RepoLookupError:
2156 return baseset()
2155 return baseset()
2157
2156
2158
2157
2159 @predicate(b'_rev(number)', safe=True)
2158 @predicate(b'_rev(number)', safe=True)
2160 def _rev(repo, subset, x):
2159 def _rev(repo, subset, x):
2161 # internal version of "rev(x)" that raise error if "x" is invalid
2160 # internal version of "rev(x)" that raise error if "x" is invalid
2162 # i18n: "rev" is a keyword
2161 # i18n: "rev" is a keyword
2163 l = getargs(x, 1, 1, _(b"rev requires one argument"))
2162 l = getargs(x, 1, 1, _(b"rev requires one argument"))
2164 try:
2163 try:
2165 # i18n: "rev" is a keyword
2164 # i18n: "rev" is a keyword
2166 l = int(getstring(l[0], _(b"rev requires a number")))
2165 l = int(getstring(l[0], _(b"rev requires a number")))
2167 except (TypeError, ValueError):
2166 except (TypeError, ValueError):
2168 # i18n: "rev" is a keyword
2167 # i18n: "rev" is a keyword
2169 raise error.ParseError(_(b"rev expects a number"))
2168 raise error.ParseError(_(b"rev expects a number"))
2170 if l not in _virtualrevs:
2169 if l not in _virtualrevs:
2171 try:
2170 try:
2172 repo.changelog.node(l) # check that the rev exists
2171 repo.changelog.node(l) # check that the rev exists
2173 except IndexError:
2172 except IndexError:
2174 raise error.RepoLookupError(_(b"unknown revision '%d'") % l)
2173 raise error.RepoLookupError(_(b"unknown revision '%d'") % l)
2175 return subset & baseset([l])
2174 return subset & baseset([l])
2176
2175
2177
2176
2178 @predicate(b'revset(set)', safe=True, takeorder=True)
2177 @predicate(b'revset(set)', safe=True, takeorder=True)
2179 def revsetpredicate(repo, subset, x, order):
2178 def revsetpredicate(repo, subset, x, order):
2180 """Strictly interpret the content as a revset.
2179 """Strictly interpret the content as a revset.
2181
2180
2182 The content of this special predicate will be strictly interpreted as a
2181 The content of this special predicate will be strictly interpreted as a
2183 revset. For example, ``revset(id(0))`` will be interpreted as "id(0)"
2182 revset. For example, ``revset(id(0))`` will be interpreted as "id(0)"
2184 without possible ambiguity with a "id(0)" bookmark or tag.
2183 without possible ambiguity with a "id(0)" bookmark or tag.
2185 """
2184 """
2186 return getset(repo, subset, x, order)
2185 return getset(repo, subset, x, order)
2187
2186
2188
2187
2189 @predicate(b'matching(revision [, field])', safe=True)
2188 @predicate(b'matching(revision [, field])', safe=True)
2190 def matching(repo, subset, x):
2189 def matching(repo, subset, x):
2191 """Changesets in which a given set of fields match the set of fields in the
2190 """Changesets in which a given set of fields match the set of fields in the
2192 selected revision or set.
2191 selected revision or set.
2193
2192
2194 To match more than one field pass the list of fields to match separated
2193 To match more than one field pass the list of fields to match separated
2195 by spaces (e.g. ``author description``).
2194 by spaces (e.g. ``author description``).
2196
2195
2197 Valid fields are most regular revision fields and some special fields.
2196 Valid fields are most regular revision fields and some special fields.
2198
2197
2199 Regular revision fields are ``description``, ``author``, ``branch``,
2198 Regular revision fields are ``description``, ``author``, ``branch``,
2200 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
2199 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
2201 and ``diff``.
2200 and ``diff``.
2202 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
2201 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
2203 contents of the revision. Two revisions matching their ``diff`` will
2202 contents of the revision. Two revisions matching their ``diff`` will
2204 also match their ``files``.
2203 also match their ``files``.
2205
2204
2206 Special fields are ``summary`` and ``metadata``:
2205 Special fields are ``summary`` and ``metadata``:
2207 ``summary`` matches the first line of the description.
2206 ``summary`` matches the first line of the description.
2208 ``metadata`` is equivalent to matching ``description user date``
2207 ``metadata`` is equivalent to matching ``description user date``
2209 (i.e. it matches the main metadata fields).
2208 (i.e. it matches the main metadata fields).
2210
2209
2211 ``metadata`` is the default field which is used when no fields are
2210 ``metadata`` is the default field which is used when no fields are
2212 specified. You can match more than one field at a time.
2211 specified. You can match more than one field at a time.
2213 """
2212 """
2214 # i18n: "matching" is a keyword
2213 # i18n: "matching" is a keyword
2215 l = getargs(x, 1, 2, _(b"matching takes 1 or 2 arguments"))
2214 l = getargs(x, 1, 2, _(b"matching takes 1 or 2 arguments"))
2216
2215
2217 revs = getset(repo, fullreposet(repo), l[0])
2216 revs = getset(repo, fullreposet(repo), l[0])
2218
2217
2219 fieldlist = [b'metadata']
2218 fieldlist = [b'metadata']
2220 if len(l) > 1:
2219 if len(l) > 1:
2221 fieldlist = getstring(
2220 fieldlist = getstring(
2222 l[1],
2221 l[1],
2223 # i18n: "matching" is a keyword
2222 # i18n: "matching" is a keyword
2224 _(b"matching requires a string as its second argument"),
2223 _(b"matching requires a string as its second argument"),
2225 ).split()
2224 ).split()
2226
2225
2227 # Make sure that there are no repeated fields,
2226 # Make sure that there are no repeated fields,
2228 # expand the 'special' 'metadata' field type
2227 # expand the 'special' 'metadata' field type
2229 # and check the 'files' whenever we check the 'diff'
2228 # and check the 'files' whenever we check the 'diff'
2230 fields = []
2229 fields = []
2231 for field in fieldlist:
2230 for field in fieldlist:
2232 if field == b'metadata':
2231 if field == b'metadata':
2233 fields += [b'user', b'description', b'date']
2232 fields += [b'user', b'description', b'date']
2234 elif field == b'diff':
2233 elif field == b'diff':
2235 # a revision matching the diff must also match the files
2234 # a revision matching the diff must also match the files
2236 # since matching the diff is very costly, make sure to
2235 # since matching the diff is very costly, make sure to
2237 # also match the files first
2236 # also match the files first
2238 fields += [b'files', b'diff']
2237 fields += [b'files', b'diff']
2239 else:
2238 else:
2240 if field == b'author':
2239 if field == b'author':
2241 field = b'user'
2240 field = b'user'
2242 fields.append(field)
2241 fields.append(field)
2243 fields = set(fields)
2242 fields = set(fields)
2244 if b'summary' in fields and b'description' in fields:
2243 if b'summary' in fields and b'description' in fields:
2245 # If a revision matches its description it also matches its summary
2244 # If a revision matches its description it also matches its summary
2246 fields.discard(b'summary')
2245 fields.discard(b'summary')
2247
2246
2248 # We may want to match more than one field
2247 # We may want to match more than one field
2249 # Not all fields take the same amount of time to be matched
2248 # Not all fields take the same amount of time to be matched
2250 # Sort the selected fields in order of increasing matching cost
2249 # Sort the selected fields in order of increasing matching cost
2251 fieldorder = [
2250 fieldorder = [
2252 b'phase',
2251 b'phase',
2253 b'parents',
2252 b'parents',
2254 b'user',
2253 b'user',
2255 b'date',
2254 b'date',
2256 b'branch',
2255 b'branch',
2257 b'summary',
2256 b'summary',
2258 b'files',
2257 b'files',
2259 b'description',
2258 b'description',
2260 b'substate',
2259 b'substate',
2261 b'diff',
2260 b'diff',
2262 ]
2261 ]
2263
2262
2264 def fieldkeyfunc(f):
2263 def fieldkeyfunc(f):
2265 try:
2264 try:
2266 return fieldorder.index(f)
2265 return fieldorder.index(f)
2267 except ValueError:
2266 except ValueError:
2268 # assume an unknown field is very costly
2267 # assume an unknown field is very costly
2269 return len(fieldorder)
2268 return len(fieldorder)
2270
2269
2271 fields = list(fields)
2270 fields = list(fields)
2272 fields.sort(key=fieldkeyfunc)
2271 fields.sort(key=fieldkeyfunc)
2273
2272
2274 # Each field will be matched with its own "getfield" function
2273 # Each field will be matched with its own "getfield" function
2275 # which will be added to the getfieldfuncs array of functions
2274 # which will be added to the getfieldfuncs array of functions
2276 getfieldfuncs = []
2275 getfieldfuncs = []
2277 _funcs = {
2276 _funcs = {
2278 b'user': lambda r: repo[r].user(),
2277 b'user': lambda r: repo[r].user(),
2279 b'branch': lambda r: repo[r].branch(),
2278 b'branch': lambda r: repo[r].branch(),
2280 b'date': lambda r: repo[r].date(),
2279 b'date': lambda r: repo[r].date(),
2281 b'description': lambda r: repo[r].description(),
2280 b'description': lambda r: repo[r].description(),
2282 b'files': lambda r: repo[r].files(),
2281 b'files': lambda r: repo[r].files(),
2283 b'parents': lambda r: repo[r].parents(),
2282 b'parents': lambda r: repo[r].parents(),
2284 b'phase': lambda r: repo[r].phase(),
2283 b'phase': lambda r: repo[r].phase(),
2285 b'substate': lambda r: repo[r].substate,
2284 b'substate': lambda r: repo[r].substate,
2286 b'summary': lambda r: repo[r].description().splitlines()[0],
2285 b'summary': lambda r: repo[r].description().splitlines()[0],
2287 b'diff': lambda r: list(
2286 b'diff': lambda r: list(
2288 repo[r].diff(opts=diffutil.diffallopts(repo.ui, {b'git': True}))
2287 repo[r].diff(opts=diffutil.diffallopts(repo.ui, {b'git': True}))
2289 ),
2288 ),
2290 }
2289 }
2291 for info in fields:
2290 for info in fields:
2292 getfield = _funcs.get(info, None)
2291 getfield = _funcs.get(info, None)
2293 if getfield is None:
2292 if getfield is None:
2294 raise error.ParseError(
2293 raise error.ParseError(
2295 # i18n: "matching" is a keyword
2294 # i18n: "matching" is a keyword
2296 _(b"unexpected field name passed to matching: %s")
2295 _(b"unexpected field name passed to matching: %s")
2297 % info
2296 % info
2298 )
2297 )
2299 getfieldfuncs.append(getfield)
2298 getfieldfuncs.append(getfield)
2300 # convert the getfield array of functions into a "getinfo" function
2299 # convert the getfield array of functions into a "getinfo" function
2301 # which returns an array of field values (or a single value if there
2300 # which returns an array of field values (or a single value if there
2302 # is only one field to match)
2301 # is only one field to match)
2303 getinfo = lambda r: [f(r) for f in getfieldfuncs]
2302 getinfo = lambda r: [f(r) for f in getfieldfuncs]
2304
2303
2305 def matches(x):
2304 def matches(x):
2306 for rev in revs:
2305 for rev in revs:
2307 target = getinfo(rev)
2306 target = getinfo(rev)
2308 match = True
2307 match = True
2309 for n, f in enumerate(getfieldfuncs):
2308 for n, f in enumerate(getfieldfuncs):
2310 if target[n] != f(x):
2309 if target[n] != f(x):
2311 match = False
2310 match = False
2312 if match:
2311 if match:
2313 return True
2312 return True
2314 return False
2313 return False
2315
2314
2316 return subset.filter(matches, condrepr=(b'<matching%r %r>', fields, revs))
2315 return subset.filter(matches, condrepr=(b'<matching%r %r>', fields, revs))
2317
2316
2318
2317
2319 @predicate(b'reverse(set)', safe=True, takeorder=True, weight=0)
2318 @predicate(b'reverse(set)', safe=True, takeorder=True, weight=0)
2320 def reverse(repo, subset, x, order):
2319 def reverse(repo, subset, x, order):
2321 """Reverse order of set."""
2320 """Reverse order of set."""
2322 l = getset(repo, subset, x, order)
2321 l = getset(repo, subset, x, order)
2323 if order == defineorder:
2322 if order == defineorder:
2324 l.reverse()
2323 l.reverse()
2325 return l
2324 return l
2326
2325
2327
2326
2328 @predicate(b'roots(set)', safe=True)
2327 @predicate(b'roots(set)', safe=True)
2329 def roots(repo, subset, x):
2328 def roots(repo, subset, x):
2330 """Changesets in set with no parent changeset in set."""
2329 """Changesets in set with no parent changeset in set."""
2331 s = getset(repo, fullreposet(repo), x)
2330 s = getset(repo, fullreposet(repo), x)
2332 parents = repo.changelog.parentrevs
2331 parents = repo.changelog.parentrevs
2333
2332
2334 def filter(r):
2333 def filter(r):
2335 for p in parents(r):
2334 for p in parents(r):
2336 if 0 <= p and p in s:
2335 if 0 <= p and p in s:
2337 return False
2336 return False
2338 return True
2337 return True
2339
2338
2340 return subset & s.filter(filter, condrepr=b'<roots>')
2339 return subset & s.filter(filter, condrepr=b'<roots>')
2341
2340
2342
2341
2343 _sortkeyfuncs = {
2342 _sortkeyfuncs = {
2344 b'rev': scmutil.intrev,
2343 b'rev': scmutil.intrev,
2345 b'branch': lambda c: c.branch(),
2344 b'branch': lambda c: c.branch(),
2346 b'desc': lambda c: c.description(),
2345 b'desc': lambda c: c.description(),
2347 b'user': lambda c: c.user(),
2346 b'user': lambda c: c.user(),
2348 b'author': lambda c: c.user(),
2347 b'author': lambda c: c.user(),
2349 b'date': lambda c: c.date()[0],
2348 b'date': lambda c: c.date()[0],
2350 b'node': scmutil.binnode,
2349 b'node': scmutil.binnode,
2351 }
2350 }
2352
2351
2353
2352
2354 def _getsortargs(x):
2353 def _getsortargs(x):
2355 """Parse sort options into (set, [(key, reverse)], opts)"""
2354 """Parse sort options into (set, [(key, reverse)], opts)"""
2356 args = getargsdict(x, b'sort', b'set keys topo.firstbranch')
2355 args = getargsdict(x, b'sort', b'set keys topo.firstbranch')
2357 if b'set' not in args:
2356 if b'set' not in args:
2358 # i18n: "sort" is a keyword
2357 # i18n: "sort" is a keyword
2359 raise error.ParseError(_(b'sort requires one or two arguments'))
2358 raise error.ParseError(_(b'sort requires one or two arguments'))
2360 keys = b"rev"
2359 keys = b"rev"
2361 if b'keys' in args:
2360 if b'keys' in args:
2362 # i18n: "sort" is a keyword
2361 # i18n: "sort" is a keyword
2363 keys = getstring(args[b'keys'], _(b"sort spec must be a string"))
2362 keys = getstring(args[b'keys'], _(b"sort spec must be a string"))
2364
2363
2365 keyflags = []
2364 keyflags = []
2366 for k in keys.split():
2365 for k in keys.split():
2367 fk = k
2366 fk = k
2368 reverse = k.startswith(b'-')
2367 reverse = k.startswith(b'-')
2369 if reverse:
2368 if reverse:
2370 k = k[1:]
2369 k = k[1:]
2371 if k not in _sortkeyfuncs and k != b'topo':
2370 if k not in _sortkeyfuncs and k != b'topo':
2372 raise error.ParseError(
2371 raise error.ParseError(
2373 _(b"unknown sort key %r") % pycompat.bytestr(fk)
2372 _(b"unknown sort key %r") % pycompat.bytestr(fk)
2374 )
2373 )
2375 keyflags.append((k, reverse))
2374 keyflags.append((k, reverse))
2376
2375
2377 if len(keyflags) > 1 and any(k == b'topo' for k, reverse in keyflags):
2376 if len(keyflags) > 1 and any(k == b'topo' for k, reverse in keyflags):
2378 # i18n: "topo" is a keyword
2377 # i18n: "topo" is a keyword
2379 raise error.ParseError(
2378 raise error.ParseError(
2380 _(b'topo sort order cannot be combined with other sort keys')
2379 _(b'topo sort order cannot be combined with other sort keys')
2381 )
2380 )
2382
2381
2383 opts = {}
2382 opts = {}
2384 if b'topo.firstbranch' in args:
2383 if b'topo.firstbranch' in args:
2385 if any(k == b'topo' for k, reverse in keyflags):
2384 if any(k == b'topo' for k, reverse in keyflags):
2386 opts[b'topo.firstbranch'] = args[b'topo.firstbranch']
2385 opts[b'topo.firstbranch'] = args[b'topo.firstbranch']
2387 else:
2386 else:
2388 # i18n: "topo" and "topo.firstbranch" are keywords
2387 # i18n: "topo" and "topo.firstbranch" are keywords
2389 raise error.ParseError(
2388 raise error.ParseError(
2390 _(
2389 _(
2391 b'topo.firstbranch can only be used '
2390 b'topo.firstbranch can only be used '
2392 b'when using the topo sort key'
2391 b'when using the topo sort key'
2393 )
2392 )
2394 )
2393 )
2395
2394
2396 return args[b'set'], keyflags, opts
2395 return args[b'set'], keyflags, opts
2397
2396
2398
2397
2399 @predicate(
2398 @predicate(
2400 b'sort(set[, [-]key... [, ...]])', safe=True, takeorder=True, weight=10
2399 b'sort(set[, [-]key... [, ...]])', safe=True, takeorder=True, weight=10
2401 )
2400 )
2402 def sort(repo, subset, x, order):
2401 def sort(repo, subset, x, order):
2403 """Sort set by keys. The default sort order is ascending, specify a key
2402 """Sort set by keys. The default sort order is ascending, specify a key
2404 as ``-key`` to sort in descending order.
2403 as ``-key`` to sort in descending order.
2405
2404
2406 The keys can be:
2405 The keys can be:
2407
2406
2408 - ``rev`` for the revision number,
2407 - ``rev`` for the revision number,
2409 - ``branch`` for the branch name,
2408 - ``branch`` for the branch name,
2410 - ``desc`` for the commit message (description),
2409 - ``desc`` for the commit message (description),
2411 - ``user`` for user name (``author`` can be used as an alias),
2410 - ``user`` for user name (``author`` can be used as an alias),
2412 - ``date`` for the commit date
2411 - ``date`` for the commit date
2413 - ``topo`` for a reverse topographical sort
2412 - ``topo`` for a reverse topographical sort
2414 - ``node`` the nodeid of the revision
2413 - ``node`` the nodeid of the revision
2415
2414
2416 The ``topo`` sort order cannot be combined with other sort keys. This sort
2415 The ``topo`` sort order cannot be combined with other sort keys. This sort
2417 takes one optional argument, ``topo.firstbranch``, which takes a revset that
2416 takes one optional argument, ``topo.firstbranch``, which takes a revset that
2418 specifies what topographical branches to prioritize in the sort.
2417 specifies what topographical branches to prioritize in the sort.
2419
2418
2420 """
2419 """
2421 s, keyflags, opts = _getsortargs(x)
2420 s, keyflags, opts = _getsortargs(x)
2422 revs = getset(repo, subset, s, order)
2421 revs = getset(repo, subset, s, order)
2423
2422
2424 if not keyflags or order != defineorder:
2423 if not keyflags or order != defineorder:
2425 return revs
2424 return revs
2426 if len(keyflags) == 1 and keyflags[0][0] == b"rev":
2425 if len(keyflags) == 1 and keyflags[0][0] == b"rev":
2427 revs.sort(reverse=keyflags[0][1])
2426 revs.sort(reverse=keyflags[0][1])
2428 return revs
2427 return revs
2429 elif keyflags[0][0] == b"topo":
2428 elif keyflags[0][0] == b"topo":
2430 firstbranch = ()
2429 firstbranch = ()
2431 if b'topo.firstbranch' in opts:
2430 if b'topo.firstbranch' in opts:
2432 firstbranch = getset(repo, subset, opts[b'topo.firstbranch'])
2431 firstbranch = getset(repo, subset, opts[b'topo.firstbranch'])
2433 revs = baseset(
2432 revs = baseset(
2434 dagop.toposort(revs, repo.changelog.parentrevs, firstbranch),
2433 dagop.toposort(revs, repo.changelog.parentrevs, firstbranch),
2435 istopo=True,
2434 istopo=True,
2436 )
2435 )
2437 if keyflags[0][1]:
2436 if keyflags[0][1]:
2438 revs.reverse()
2437 revs.reverse()
2439 return revs
2438 return revs
2440
2439
2441 # sort() is guaranteed to be stable
2440 # sort() is guaranteed to be stable
2442 ctxs = [repo[r] for r in revs]
2441 ctxs = [repo[r] for r in revs]
2443 for k, reverse in reversed(keyflags):
2442 for k, reverse in reversed(keyflags):
2444 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
2443 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
2445 return baseset([c.rev() for c in ctxs])
2444 return baseset([c.rev() for c in ctxs])
2446
2445
2447
2446
2448 @predicate(b'subrepo([pattern])')
2447 @predicate(b'subrepo([pattern])')
2449 def subrepo(repo, subset, x):
2448 def subrepo(repo, subset, x):
2450 """Changesets that add, modify or remove the given subrepo. If no subrepo
2449 """Changesets that add, modify or remove the given subrepo. If no subrepo
2451 pattern is named, any subrepo changes are returned.
2450 pattern is named, any subrepo changes are returned.
2452 """
2451 """
2453 # i18n: "subrepo" is a keyword
2452 # i18n: "subrepo" is a keyword
2454 args = getargs(x, 0, 1, _(b'subrepo takes at most one argument'))
2453 args = getargs(x, 0, 1, _(b'subrepo takes at most one argument'))
2455 pat = None
2454 pat = None
2456 if len(args) != 0:
2455 if len(args) != 0:
2457 pat = getstring(args[0], _(b"subrepo requires a pattern"))
2456 pat = getstring(args[0], _(b"subrepo requires a pattern"))
2458
2457
2459 m = matchmod.exact([b'.hgsubstate'])
2458 m = matchmod.exact([b'.hgsubstate'])
2460
2459
2461 def submatches(names):
2460 def submatches(names):
2462 k, p, m = stringutil.stringmatcher(pat)
2461 k, p, m = stringutil.stringmatcher(pat)
2463 for name in names:
2462 for name in names:
2464 if m(name):
2463 if m(name):
2465 yield name
2464 yield name
2466
2465
2467 def matches(x):
2466 def matches(x):
2468 c = repo[x]
2467 c = repo[x]
2469 s = repo.status(c.p1().node(), c.node(), match=m)
2468 s = repo.status(c.p1().node(), c.node(), match=m)
2470
2469
2471 if pat is None:
2470 if pat is None:
2472 return s.added or s.modified or s.removed
2471 return s.added or s.modified or s.removed
2473
2472
2474 if s.added:
2473 if s.added:
2475 return any(submatches(c.substate.keys()))
2474 return any(submatches(c.substate.keys()))
2476
2475
2477 if s.modified:
2476 if s.modified:
2478 subs = set(c.p1().substate.keys())
2477 subs = set(c.p1().substate.keys())
2479 subs.update(c.substate.keys())
2478 subs.update(c.substate.keys())
2480
2479
2481 for path in submatches(subs):
2480 for path in submatches(subs):
2482 if c.p1().substate.get(path) != c.substate.get(path):
2481 if c.p1().substate.get(path) != c.substate.get(path):
2483 return True
2482 return True
2484
2483
2485 if s.removed:
2484 if s.removed:
2486 return any(submatches(c.p1().substate.keys()))
2485 return any(submatches(c.p1().substate.keys()))
2487
2486
2488 return False
2487 return False
2489
2488
2490 return subset.filter(matches, condrepr=(b'<subrepo %r>', pat))
2489 return subset.filter(matches, condrepr=(b'<subrepo %r>', pat))
2491
2490
2492
2491
2493 def _mapbynodefunc(repo, s, f):
2492 def _mapbynodefunc(repo, s, f):
2494 """(repo, smartset, [node] -> [node]) -> smartset
2493 """(repo, smartset, [node] -> [node]) -> smartset
2495
2494
2496 Helper method to map a smartset to another smartset given a function only
2495 Helper method to map a smartset to another smartset given a function only
2497 talking about nodes. Handles converting between rev numbers and nodes, and
2496 talking about nodes. Handles converting between rev numbers and nodes, and
2498 filtering.
2497 filtering.
2499 """
2498 """
2500 cl = repo.unfiltered().changelog
2499 cl = repo.unfiltered().changelog
2501 torev = cl.index.get_rev
2500 torev = cl.index.get_rev
2502 tonode = cl.node
2501 tonode = cl.node
2503 result = {torev(n) for n in f(tonode(r) for r in s)}
2502 result = {torev(n) for n in f(tonode(r) for r in s)}
2504 result.discard(None)
2503 result.discard(None)
2505 return smartset.baseset(result - repo.changelog.filteredrevs)
2504 return smartset.baseset(result - repo.changelog.filteredrevs)
2506
2505
2507
2506
2508 @predicate(b'successors(set)', safe=True)
2507 @predicate(b'successors(set)', safe=True)
2509 def successors(repo, subset, x):
2508 def successors(repo, subset, x):
2510 """All successors for set, including the given set themselves.
2509 """All successors for set, including the given set themselves.
2511 (EXPERIMENTAL)"""
2510 (EXPERIMENTAL)"""
2512 s = getset(repo, fullreposet(repo), x)
2511 s = getset(repo, fullreposet(repo), x)
2513 f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
2512 f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
2514 d = _mapbynodefunc(repo, s, f)
2513 d = _mapbynodefunc(repo, s, f)
2515 return subset & d
2514 return subset & d
2516
2515
2517
2516
2518 def _substringmatcher(pattern, casesensitive=True):
2517 def _substringmatcher(pattern, casesensitive=True):
2519 kind, pattern, matcher = stringutil.stringmatcher(
2518 kind, pattern, matcher = stringutil.stringmatcher(
2520 pattern, casesensitive=casesensitive
2519 pattern, casesensitive=casesensitive
2521 )
2520 )
2522 if kind == b'literal':
2521 if kind == b'literal':
2523 if not casesensitive:
2522 if not casesensitive:
2524 pattern = encoding.lower(pattern)
2523 pattern = encoding.lower(pattern)
2525 matcher = lambda s: pattern in encoding.lower(s)
2524 matcher = lambda s: pattern in encoding.lower(s)
2526 else:
2525 else:
2527 matcher = lambda s: pattern in s
2526 matcher = lambda s: pattern in s
2528 return kind, pattern, matcher
2527 return kind, pattern, matcher
2529
2528
2530
2529
2531 @predicate(b'tag([name])', safe=True)
2530 @predicate(b'tag([name])', safe=True)
2532 def tag(repo, subset, x):
2531 def tag(repo, subset, x):
2533 """The specified tag by name, or all tagged revisions if no name is given.
2532 """The specified tag by name, or all tagged revisions if no name is given.
2534
2533
2535 Pattern matching is supported for `name`. See
2534 Pattern matching is supported for `name`. See
2536 :hg:`help revisions.patterns`.
2535 :hg:`help revisions.patterns`.
2537 """
2536 """
2538 # i18n: "tag" is a keyword
2537 # i18n: "tag" is a keyword
2539 args = getargs(x, 0, 1, _(b"tag takes one or no arguments"))
2538 args = getargs(x, 0, 1, _(b"tag takes one or no arguments"))
2540 cl = repo.changelog
2539 cl = repo.changelog
2541 if args:
2540 if args:
2542 pattern = getstring(
2541 pattern = getstring(
2543 args[0],
2542 args[0],
2544 # i18n: "tag" is a keyword
2543 # i18n: "tag" is a keyword
2545 _(b'the argument to tag must be a string'),
2544 _(b'the argument to tag must be a string'),
2546 )
2545 )
2547 kind, pattern, matcher = stringutil.stringmatcher(pattern)
2546 kind, pattern, matcher = stringutil.stringmatcher(pattern)
2548 if kind == b'literal':
2547 if kind == b'literal':
2549 # avoid resolving all tags
2548 # avoid resolving all tags
2550 tn = repo._tagscache.tags.get(pattern, None)
2549 tn = repo._tagscache.tags.get(pattern, None)
2551 if tn is None:
2550 if tn is None:
2552 raise error.RepoLookupError(
2551 raise error.RepoLookupError(
2553 _(b"tag '%s' does not exist") % pattern
2552 _(b"tag '%s' does not exist") % pattern
2554 )
2553 )
2555 s = {repo[tn].rev()}
2554 s = {repo[tn].rev()}
2556 else:
2555 else:
2557 s = {cl.rev(n) for t, n in repo.tagslist() if matcher(t)}
2556 s = {cl.rev(n) for t, n in repo.tagslist() if matcher(t)}
2558 else:
2557 else:
2559 s = {cl.rev(n) for t, n in repo.tagslist() if t != b'tip'}
2558 s = {cl.rev(n) for t, n in repo.tagslist() if t != b'tip'}
2560 return subset & s
2559 return subset & s
2561
2560
2562
2561
2563 @predicate(b'tagged', safe=True)
2562 @predicate(b'tagged', safe=True)
2564 def tagged(repo, subset, x):
2563 def tagged(repo, subset, x):
2565 return tag(repo, subset, x)
2564 return tag(repo, subset, x)
2566
2565
2567
2566
2568 @predicate(b'orphan()', safe=True)
2567 @predicate(b'orphan()', safe=True)
2569 def orphan(repo, subset, x):
2568 def orphan(repo, subset, x):
2570 """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)"""
2569 """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)"""
2571 # i18n: "orphan" is a keyword
2570 # i18n: "orphan" is a keyword
2572 getargs(x, 0, 0, _(b"orphan takes no arguments"))
2571 getargs(x, 0, 0, _(b"orphan takes no arguments"))
2573 orphan = obsmod.getrevs(repo, b'orphan')
2572 orphan = obsmod.getrevs(repo, b'orphan')
2574 return subset & orphan
2573 return subset & orphan
2575
2574
2576
2575
2577 @predicate(b'unstable()', safe=True)
2576 @predicate(b'unstable()', safe=True)
2578 def unstable(repo, subset, x):
2577 def unstable(repo, subset, x):
2579 """Changesets with instabilities. (EXPERIMENTAL)"""
2578 """Changesets with instabilities. (EXPERIMENTAL)"""
2580 # i18n: "unstable" is a keyword
2579 # i18n: "unstable" is a keyword
2581 getargs(x, 0, 0, b'unstable takes no arguments')
2580 getargs(x, 0, 0, b'unstable takes no arguments')
2582 _unstable = set()
2581 _unstable = set()
2583 _unstable.update(obsmod.getrevs(repo, b'orphan'))
2582 _unstable.update(obsmod.getrevs(repo, b'orphan'))
2584 _unstable.update(obsmod.getrevs(repo, b'phasedivergent'))
2583 _unstable.update(obsmod.getrevs(repo, b'phasedivergent'))
2585 _unstable.update(obsmod.getrevs(repo, b'contentdivergent'))
2584 _unstable.update(obsmod.getrevs(repo, b'contentdivergent'))
2586 return subset & baseset(_unstable)
2585 return subset & baseset(_unstable)
2587
2586
2588
2587
2589 @predicate(b'user(string)', safe=True, weight=10)
2588 @predicate(b'user(string)', safe=True, weight=10)
2590 def user(repo, subset, x):
2589 def user(repo, subset, x):
2591 """User name contains string. The match is case-insensitive.
2590 """User name contains string. The match is case-insensitive.
2592
2591
2593 Pattern matching is supported for `string`. See
2592 Pattern matching is supported for `string`. See
2594 :hg:`help revisions.patterns`.
2593 :hg:`help revisions.patterns`.
2595 """
2594 """
2596 return author(repo, subset, x)
2595 return author(repo, subset, x)
2597
2596
2598
2597
2599 @predicate(b'wdir()', safe=True, weight=0)
2598 @predicate(b'wdir()', safe=True, weight=0)
2600 def wdir(repo, subset, x):
2599 def wdir(repo, subset, x):
2601 """Working directory. (EXPERIMENTAL)"""
2600 """Working directory. (EXPERIMENTAL)"""
2602 # i18n: "wdir" is a keyword
2601 # i18n: "wdir" is a keyword
2603 getargs(x, 0, 0, _(b"wdir takes no arguments"))
2602 getargs(x, 0, 0, _(b"wdir takes no arguments"))
2604 if wdirrev in subset or isinstance(subset, fullreposet):
2603 if wdirrev in subset or isinstance(subset, fullreposet):
2605 return baseset([wdirrev])
2604 return baseset([wdirrev])
2606 return baseset()
2605 return baseset()
2607
2606
2608
2607
2609 def _orderedlist(repo, subset, x):
2608 def _orderedlist(repo, subset, x):
2610 s = getstring(x, b"internal error")
2609 s = getstring(x, b"internal error")
2611 if not s:
2610 if not s:
2612 return baseset()
2611 return baseset()
2613 # remove duplicates here. it's difficult for caller to deduplicate sets
2612 # remove duplicates here. it's difficult for caller to deduplicate sets
2614 # because different symbols can point to the same rev.
2613 # because different symbols can point to the same rev.
2615 cl = repo.changelog
2614 cl = repo.changelog
2616 ls = []
2615 ls = []
2617 seen = set()
2616 seen = set()
2618 for t in s.split(b'\0'):
2617 for t in s.split(b'\0'):
2619 try:
2618 try:
2620 # fast path for integer revision
2619 # fast path for integer revision
2621 r = int(t)
2620 r = int(t)
2622 if (b'%d' % r) != t or r not in cl:
2621 if (b'%d' % r) != t or r not in cl:
2623 raise ValueError
2622 raise ValueError
2624 revs = [r]
2623 revs = [r]
2625 except ValueError:
2624 except ValueError:
2626 revs = stringset(repo, subset, t, defineorder)
2625 revs = stringset(repo, subset, t, defineorder)
2627
2626
2628 for r in revs:
2627 for r in revs:
2629 if r in seen:
2628 if r in seen:
2630 continue
2629 continue
2631 if (
2630 if (
2632 r in subset
2631 r in subset
2633 or r in _virtualrevs
2632 or r in _virtualrevs
2634 and isinstance(subset, fullreposet)
2633 and isinstance(subset, fullreposet)
2635 ):
2634 ):
2636 ls.append(r)
2635 ls.append(r)
2637 seen.add(r)
2636 seen.add(r)
2638 return baseset(ls)
2637 return baseset(ls)
2639
2638
2640
2639
2641 # for internal use
2640 # for internal use
2642 @predicate(b'_list', safe=True, takeorder=True)
2641 @predicate(b'_list', safe=True, takeorder=True)
2643 def _list(repo, subset, x, order):
2642 def _list(repo, subset, x, order):
2644 if order == followorder:
2643 if order == followorder:
2645 # slow path to take the subset order
2644 # slow path to take the subset order
2646 return subset & _orderedlist(repo, fullreposet(repo), x)
2645 return subset & _orderedlist(repo, fullreposet(repo), x)
2647 else:
2646 else:
2648 return _orderedlist(repo, subset, x)
2647 return _orderedlist(repo, subset, x)
2649
2648
2650
2649
2651 def _orderedintlist(repo, subset, x):
2650 def _orderedintlist(repo, subset, x):
2652 s = getstring(x, b"internal error")
2651 s = getstring(x, b"internal error")
2653 if not s:
2652 if not s:
2654 return baseset()
2653 return baseset()
2655 ls = [int(r) for r in s.split(b'\0')]
2654 ls = [int(r) for r in s.split(b'\0')]
2656 s = subset
2655 s = subset
2657 return baseset([r for r in ls if r in s])
2656 return baseset([r for r in ls if r in s])
2658
2657
2659
2658
2660 # for internal use
2659 # for internal use
2661 @predicate(b'_intlist', safe=True, takeorder=True, weight=0)
2660 @predicate(b'_intlist', safe=True, takeorder=True, weight=0)
2662 def _intlist(repo, subset, x, order):
2661 def _intlist(repo, subset, x, order):
2663 if order == followorder:
2662 if order == followorder:
2664 # slow path to take the subset order
2663 # slow path to take the subset order
2665 return subset & _orderedintlist(repo, fullreposet(repo), x)
2664 return subset & _orderedintlist(repo, fullreposet(repo), x)
2666 else:
2665 else:
2667 return _orderedintlist(repo, subset, x)
2666 return _orderedintlist(repo, subset, x)
2668
2667
2669
2668
2670 def _orderedhexlist(repo, subset, x):
2669 def _orderedhexlist(repo, subset, x):
2671 s = getstring(x, b"internal error")
2670 s = getstring(x, b"internal error")
2672 if not s:
2671 if not s:
2673 return baseset()
2672 return baseset()
2674 cl = repo.changelog
2673 cl = repo.changelog
2675 ls = [cl.rev(bin(r)) for r in s.split(b'\0')]
2674 ls = [cl.rev(bin(r)) for r in s.split(b'\0')]
2676 s = subset
2675 s = subset
2677 return baseset([r for r in ls if r in s])
2676 return baseset([r for r in ls if r in s])
2678
2677
2679
2678
2680 # for internal use
2679 # for internal use
2681 @predicate(b'_hexlist', safe=True, takeorder=True)
2680 @predicate(b'_hexlist', safe=True, takeorder=True)
2682 def _hexlist(repo, subset, x, order):
2681 def _hexlist(repo, subset, x, order):
2683 if order == followorder:
2682 if order == followorder:
2684 # slow path to take the subset order
2683 # slow path to take the subset order
2685 return subset & _orderedhexlist(repo, fullreposet(repo), x)
2684 return subset & _orderedhexlist(repo, fullreposet(repo), x)
2686 else:
2685 else:
2687 return _orderedhexlist(repo, subset, x)
2686 return _orderedhexlist(repo, subset, x)
2688
2687
2689
2688
2690 methods = {
2689 methods = {
2691 b"range": rangeset,
2690 b"range": rangeset,
2692 b"rangeall": rangeall,
2691 b"rangeall": rangeall,
2693 b"rangepre": rangepre,
2692 b"rangepre": rangepre,
2694 b"rangepost": rangepost,
2693 b"rangepost": rangepost,
2695 b"dagrange": dagrange,
2694 b"dagrange": dagrange,
2696 b"string": stringset,
2695 b"string": stringset,
2697 b"symbol": stringset,
2696 b"symbol": stringset,
2698 b"and": andset,
2697 b"and": andset,
2699 b"andsmally": andsmallyset,
2698 b"andsmally": andsmallyset,
2700 b"or": orset,
2699 b"or": orset,
2701 b"not": notset,
2700 b"not": notset,
2702 b"difference": differenceset,
2701 b"difference": differenceset,
2703 b"relation": relationset,
2702 b"relation": relationset,
2704 b"relsubscript": relsubscriptset,
2703 b"relsubscript": relsubscriptset,
2705 b"subscript": subscriptset,
2704 b"subscript": subscriptset,
2706 b"list": listset,
2705 b"list": listset,
2707 b"keyvalue": keyvaluepair,
2706 b"keyvalue": keyvaluepair,
2708 b"func": func,
2707 b"func": func,
2709 b"ancestor": ancestorspec,
2708 b"ancestor": ancestorspec,
2710 b"parent": parentspec,
2709 b"parent": parentspec,
2711 b"parentpost": parentpost,
2710 b"parentpost": parentpost,
2712 b"smartset": rawsmartset,
2711 b"smartset": rawsmartset,
2713 }
2712 }
2714
2713
2715 relations = {
2714 relations = {
2716 b"g": generationsrel,
2715 b"g": generationsrel,
2717 b"generations": generationsrel,
2716 b"generations": generationsrel,
2718 }
2717 }
2719
2718
2720 subscriptrelations = {
2719 subscriptrelations = {
2721 b"g": generationssubrel,
2720 b"g": generationssubrel,
2722 b"generations": generationssubrel,
2721 b"generations": generationssubrel,
2723 }
2722 }
2724
2723
2725
2724
2726 def lookupfn(repo):
2725 def lookupfn(repo):
2727 def fn(symbol):
2726 def fn(symbol):
2728 try:
2727 try:
2729 return scmutil.isrevsymbol(repo, symbol)
2728 return scmutil.isrevsymbol(repo, symbol)
2730 except error.AmbiguousPrefixLookupError:
2729 except error.AmbiguousPrefixLookupError:
2731 raise error.InputError(
2730 raise error.InputError(
2732 b'ambiguous revision identifier: %s' % symbol
2731 b'ambiguous revision identifier: %s' % symbol
2733 )
2732 )
2734
2733
2735 return fn
2734 return fn
2736
2735
2737
2736
2738 def match(ui, spec, lookup=None):
2737 def match(ui, spec, lookup=None):
2739 """Create a matcher for a single revision spec"""
2738 """Create a matcher for a single revision spec"""
2740 return matchany(ui, [spec], lookup=lookup)
2739 return matchany(ui, [spec], lookup=lookup)
2741
2740
2742
2741
2743 def matchany(ui, specs, lookup=None, localalias=None):
2742 def matchany(ui, specs, lookup=None, localalias=None):
2744 """Create a matcher that will include any revisions matching one of the
2743 """Create a matcher that will include any revisions matching one of the
2745 given specs
2744 given specs
2746
2745
2747 If lookup function is not None, the parser will first attempt to handle
2746 If lookup function is not None, the parser will first attempt to handle
2748 old-style ranges, which may contain operator characters.
2747 old-style ranges, which may contain operator characters.
2749
2748
2750 If localalias is not None, it is a dict {name: definitionstring}. It takes
2749 If localalias is not None, it is a dict {name: definitionstring}. It takes
2751 precedence over [revsetalias] config section.
2750 precedence over [revsetalias] config section.
2752 """
2751 """
2753 if not specs:
2752 if not specs:
2754
2753
2755 def mfunc(repo, subset=None):
2754 def mfunc(repo, subset=None):
2756 return baseset()
2755 return baseset()
2757
2756
2758 return mfunc
2757 return mfunc
2759 if not all(specs):
2758 if not all(specs):
2760 raise error.ParseError(_(b"empty query"))
2759 raise error.ParseError(_(b"empty query"))
2761 if len(specs) == 1:
2760 if len(specs) == 1:
2762 tree = revsetlang.parse(specs[0], lookup)
2761 tree = revsetlang.parse(specs[0], lookup)
2763 else:
2762 else:
2764 tree = (
2763 tree = (
2765 b'or',
2764 b'or',
2766 (b'list',) + tuple(revsetlang.parse(s, lookup) for s in specs),
2765 (b'list',) + tuple(revsetlang.parse(s, lookup) for s in specs),
2767 )
2766 )
2768
2767
2769 aliases = []
2768 aliases = []
2770 warn = None
2769 warn = None
2771 if ui:
2770 if ui:
2772 aliases.extend(ui.configitems(b'revsetalias'))
2771 aliases.extend(ui.configitems(b'revsetalias'))
2773 warn = ui.warn
2772 warn = ui.warn
2774 if localalias:
2773 if localalias:
2775 aliases.extend(localalias.items())
2774 aliases.extend(localalias.items())
2776 if aliases:
2775 if aliases:
2777 tree = revsetlang.expandaliases(tree, aliases, warn=warn)
2776 tree = revsetlang.expandaliases(tree, aliases, warn=warn)
2778 tree = revsetlang.foldconcat(tree)
2777 tree = revsetlang.foldconcat(tree)
2779 tree = revsetlang.analyze(tree)
2778 tree = revsetlang.analyze(tree)
2780 tree = revsetlang.optimize(tree)
2779 tree = revsetlang.optimize(tree)
2781 return makematcher(tree)
2780 return makematcher(tree)
2782
2781
2783
2782
2784 def makematcher(tree):
2783 def makematcher(tree):
2785 """Create a matcher from an evaluatable tree"""
2784 """Create a matcher from an evaluatable tree"""
2786
2785
2787 def mfunc(repo, subset=None, order=None):
2786 def mfunc(repo, subset=None, order=None):
2788 if order is None:
2787 if order is None:
2789 if subset is None:
2788 if subset is None:
2790 order = defineorder # 'x'
2789 order = defineorder # 'x'
2791 else:
2790 else:
2792 order = followorder # 'subset & x'
2791 order = followorder # 'subset & x'
2793 if subset is None:
2792 if subset is None:
2794 subset = fullreposet(repo)
2793 subset = fullreposet(repo)
2795 return getset(repo, subset, tree, order)
2794 return getset(repo, subset, tree, order)
2796
2795
2797 return mfunc
2796 return mfunc
2798
2797
2799
2798
2800 def loadpredicate(ui, extname, registrarobj):
2799 def loadpredicate(ui, extname, registrarobj):
2801 """Load revset predicates from specified registrarobj"""
2800 """Load revset predicates from specified registrarobj"""
2802 for name, func in pycompat.iteritems(registrarobj._table):
2801 for name, func in pycompat.iteritems(registrarobj._table):
2803 symbols[name] = func
2802 symbols[name] = func
2804 if func._safe:
2803 if func._safe:
2805 safesymbols.add(name)
2804 safesymbols.add(name)
2806
2805
2807
2806
2808 # load built-in predicates explicitly to setup safesymbols
2807 # load built-in predicates explicitly to setup safesymbols
2809 loadpredicate(None, None, predicate)
2808 loadpredicate(None, None, predicate)
2810
2809
2811 # tell hggettext to extract docstrings from these functions:
2810 # tell hggettext to extract docstrings from these functions:
2812 i18nfunctions = symbols.values()
2811 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now