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