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