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