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