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