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