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