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