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