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