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