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