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