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