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