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