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