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