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