##// END OF EJS Templates
revset: inline _tokenizealias() into _parsealias()...
Yuya Nishihara -
r29072:f86fa716 default
parent child Browse files
Show More
@@ -1,3391 +1,3385 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 heapq
10 import heapq
11 import re
11 import re
12
12
13 from .i18n import _
13 from .i18n import _
14 from . import (
14 from . import (
15 destutil,
15 destutil,
16 encoding,
16 encoding,
17 error,
17 error,
18 hbisect,
18 hbisect,
19 match as matchmod,
19 match as matchmod,
20 node,
20 node,
21 obsolete as obsmod,
21 obsolete as obsmod,
22 parser,
22 parser,
23 pathutil,
23 pathutil,
24 phases,
24 phases,
25 registrar,
25 registrar,
26 repoview,
26 repoview,
27 util,
27 util,
28 )
28 )
29
29
30 def _revancestors(repo, revs, followfirst):
30 def _revancestors(repo, revs, followfirst):
31 """Like revlog.ancestors(), but supports followfirst."""
31 """Like revlog.ancestors(), but supports followfirst."""
32 if followfirst:
32 if followfirst:
33 cut = 1
33 cut = 1
34 else:
34 else:
35 cut = None
35 cut = None
36 cl = repo.changelog
36 cl = repo.changelog
37
37
38 def iterate():
38 def iterate():
39 revs.sort(reverse=True)
39 revs.sort(reverse=True)
40 irevs = iter(revs)
40 irevs = iter(revs)
41 h = []
41 h = []
42
42
43 inputrev = next(irevs, None)
43 inputrev = next(irevs, None)
44 if inputrev is not None:
44 if inputrev is not None:
45 heapq.heappush(h, -inputrev)
45 heapq.heappush(h, -inputrev)
46
46
47 seen = set()
47 seen = set()
48 while h:
48 while h:
49 current = -heapq.heappop(h)
49 current = -heapq.heappop(h)
50 if current == inputrev:
50 if current == inputrev:
51 inputrev = next(irevs, None)
51 inputrev = next(irevs, None)
52 if inputrev is not None:
52 if inputrev is not None:
53 heapq.heappush(h, -inputrev)
53 heapq.heappush(h, -inputrev)
54 if current not in seen:
54 if current not in seen:
55 seen.add(current)
55 seen.add(current)
56 yield current
56 yield current
57 for parent in cl.parentrevs(current)[:cut]:
57 for parent in cl.parentrevs(current)[:cut]:
58 if parent != node.nullrev:
58 if parent != node.nullrev:
59 heapq.heappush(h, -parent)
59 heapq.heappush(h, -parent)
60
60
61 return generatorset(iterate(), iterasc=False)
61 return generatorset(iterate(), iterasc=False)
62
62
63 def _revdescendants(repo, revs, followfirst):
63 def _revdescendants(repo, revs, followfirst):
64 """Like revlog.descendants() but supports followfirst."""
64 """Like revlog.descendants() but supports followfirst."""
65 if followfirst:
65 if followfirst:
66 cut = 1
66 cut = 1
67 else:
67 else:
68 cut = None
68 cut = None
69
69
70 def iterate():
70 def iterate():
71 cl = repo.changelog
71 cl = repo.changelog
72 # XXX this should be 'parentset.min()' assuming 'parentset' is a
72 # XXX this should be 'parentset.min()' assuming 'parentset' is a
73 # smartset (and if it is not, it should.)
73 # smartset (and if it is not, it should.)
74 first = min(revs)
74 first = min(revs)
75 nullrev = node.nullrev
75 nullrev = node.nullrev
76 if first == nullrev:
76 if first == nullrev:
77 # Are there nodes with a null first parent and a non-null
77 # Are there nodes with a null first parent and a non-null
78 # second one? Maybe. Do we care? Probably not.
78 # second one? Maybe. Do we care? Probably not.
79 for i in cl:
79 for i in cl:
80 yield i
80 yield i
81 else:
81 else:
82 seen = set(revs)
82 seen = set(revs)
83 for i in cl.revs(first + 1):
83 for i in cl.revs(first + 1):
84 for x in cl.parentrevs(i)[:cut]:
84 for x in cl.parentrevs(i)[:cut]:
85 if x != nullrev and x in seen:
85 if x != nullrev and x in seen:
86 seen.add(i)
86 seen.add(i)
87 yield i
87 yield i
88 break
88 break
89
89
90 return generatorset(iterate(), iterasc=True)
90 return generatorset(iterate(), iterasc=True)
91
91
92 def _reachablerootspure(repo, minroot, roots, heads, includepath):
92 def _reachablerootspure(repo, minroot, roots, heads, includepath):
93 """return (heads(::<roots> and ::<heads>))
93 """return (heads(::<roots> and ::<heads>))
94
94
95 If includepath is True, return (<roots>::<heads>)."""
95 If includepath is True, return (<roots>::<heads>)."""
96 if not roots:
96 if not roots:
97 return []
97 return []
98 parentrevs = repo.changelog.parentrevs
98 parentrevs = repo.changelog.parentrevs
99 roots = set(roots)
99 roots = set(roots)
100 visit = list(heads)
100 visit = list(heads)
101 reachable = set()
101 reachable = set()
102 seen = {}
102 seen = {}
103 # prefetch all the things! (because python is slow)
103 # prefetch all the things! (because python is slow)
104 reached = reachable.add
104 reached = reachable.add
105 dovisit = visit.append
105 dovisit = visit.append
106 nextvisit = visit.pop
106 nextvisit = visit.pop
107 # open-code the post-order traversal due to the tiny size of
107 # open-code the post-order traversal due to the tiny size of
108 # sys.getrecursionlimit()
108 # sys.getrecursionlimit()
109 while visit:
109 while visit:
110 rev = nextvisit()
110 rev = nextvisit()
111 if rev in roots:
111 if rev in roots:
112 reached(rev)
112 reached(rev)
113 if not includepath:
113 if not includepath:
114 continue
114 continue
115 parents = parentrevs(rev)
115 parents = parentrevs(rev)
116 seen[rev] = parents
116 seen[rev] = parents
117 for parent in parents:
117 for parent in parents:
118 if parent >= minroot and parent not in seen:
118 if parent >= minroot and parent not in seen:
119 dovisit(parent)
119 dovisit(parent)
120 if not reachable:
120 if not reachable:
121 return baseset()
121 return baseset()
122 if not includepath:
122 if not includepath:
123 return reachable
123 return reachable
124 for rev in sorted(seen):
124 for rev in sorted(seen):
125 for parent in seen[rev]:
125 for parent in seen[rev]:
126 if parent in reachable:
126 if parent in reachable:
127 reached(rev)
127 reached(rev)
128 return reachable
128 return reachable
129
129
130 def reachableroots(repo, roots, heads, includepath=False):
130 def reachableroots(repo, roots, heads, includepath=False):
131 """return (heads(::<roots> and ::<heads>))
131 """return (heads(::<roots> and ::<heads>))
132
132
133 If includepath is True, return (<roots>::<heads>)."""
133 If includepath is True, return (<roots>::<heads>)."""
134 if not roots:
134 if not roots:
135 return baseset()
135 return baseset()
136 minroot = roots.min()
136 minroot = roots.min()
137 roots = list(roots)
137 roots = list(roots)
138 heads = list(heads)
138 heads = list(heads)
139 try:
139 try:
140 revs = repo.changelog.reachableroots(minroot, heads, roots, includepath)
140 revs = repo.changelog.reachableroots(minroot, heads, roots, includepath)
141 except AttributeError:
141 except AttributeError:
142 revs = _reachablerootspure(repo, minroot, roots, heads, includepath)
142 revs = _reachablerootspure(repo, minroot, roots, heads, includepath)
143 revs = baseset(revs)
143 revs = baseset(revs)
144 revs.sort()
144 revs.sort()
145 return revs
145 return revs
146
146
147 elements = {
147 elements = {
148 # token-type: binding-strength, primary, prefix, infix, suffix
148 # token-type: binding-strength, primary, prefix, infix, suffix
149 "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None),
149 "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None),
150 "##": (20, None, None, ("_concat", 20), None),
150 "##": (20, None, None, ("_concat", 20), None),
151 "~": (18, None, None, ("ancestor", 18), None),
151 "~": (18, None, None, ("ancestor", 18), None),
152 "^": (18, None, None, ("parent", 18), ("parentpost", 18)),
152 "^": (18, None, None, ("parent", 18), ("parentpost", 18)),
153 "-": (5, None, ("negate", 19), ("minus", 5), None),
153 "-": (5, None, ("negate", 19), ("minus", 5), None),
154 "::": (17, None, ("dagrangepre", 17), ("dagrange", 17),
154 "::": (17, None, ("dagrangepre", 17), ("dagrange", 17),
155 ("dagrangepost", 17)),
155 ("dagrangepost", 17)),
156 "..": (17, None, ("dagrangepre", 17), ("dagrange", 17),
156 "..": (17, None, ("dagrangepre", 17), ("dagrange", 17),
157 ("dagrangepost", 17)),
157 ("dagrangepost", 17)),
158 ":": (15, "rangeall", ("rangepre", 15), ("range", 15), ("rangepost", 15)),
158 ":": (15, "rangeall", ("rangepre", 15), ("range", 15), ("rangepost", 15)),
159 "not": (10, None, ("not", 10), None, None),
159 "not": (10, None, ("not", 10), None, None),
160 "!": (10, None, ("not", 10), None, None),
160 "!": (10, None, ("not", 10), None, None),
161 "and": (5, None, None, ("and", 5), None),
161 "and": (5, None, None, ("and", 5), None),
162 "&": (5, None, None, ("and", 5), None),
162 "&": (5, None, None, ("and", 5), None),
163 "%": (5, None, None, ("only", 5), ("onlypost", 5)),
163 "%": (5, None, None, ("only", 5), ("onlypost", 5)),
164 "or": (4, None, None, ("or", 4), None),
164 "or": (4, None, None, ("or", 4), None),
165 "|": (4, None, None, ("or", 4), None),
165 "|": (4, None, None, ("or", 4), None),
166 "+": (4, None, None, ("or", 4), None),
166 "+": (4, None, None, ("or", 4), None),
167 "=": (3, None, None, ("keyvalue", 3), None),
167 "=": (3, None, None, ("keyvalue", 3), None),
168 ",": (2, None, None, ("list", 2), None),
168 ",": (2, None, None, ("list", 2), None),
169 ")": (0, None, None, None, None),
169 ")": (0, None, None, None, None),
170 "symbol": (0, "symbol", None, None, None),
170 "symbol": (0, "symbol", None, None, None),
171 "string": (0, "string", None, None, None),
171 "string": (0, "string", None, None, None),
172 "end": (0, None, None, None, None),
172 "end": (0, None, None, None, None),
173 }
173 }
174
174
175 keywords = set(['and', 'or', 'not'])
175 keywords = set(['and', 'or', 'not'])
176
176
177 # default set of valid characters for the initial letter of symbols
177 # default set of valid characters for the initial letter of symbols
178 _syminitletters = set(c for c in [chr(i) for i in xrange(256)]
178 _syminitletters = set(c for c in [chr(i) for i in xrange(256)]
179 if c.isalnum() or c in '._@' or ord(c) > 127)
179 if c.isalnum() or c in '._@' or ord(c) > 127)
180
180
181 # default set of valid characters for non-initial letters of symbols
181 # default set of valid characters for non-initial letters of symbols
182 _symletters = set(c for c in [chr(i) for i in xrange(256)]
182 _symletters = set(c for c in [chr(i) for i in xrange(256)]
183 if c.isalnum() or c in '-._/@' or ord(c) > 127)
183 if c.isalnum() or c in '-._/@' or ord(c) > 127)
184
184
185 def tokenize(program, lookup=None, syminitletters=None, symletters=None):
185 def tokenize(program, lookup=None, syminitletters=None, symletters=None):
186 '''
186 '''
187 Parse a revset statement into a stream of tokens
187 Parse a revset statement into a stream of tokens
188
188
189 ``syminitletters`` is the set of valid characters for the initial
189 ``syminitletters`` is the set of valid characters for the initial
190 letter of symbols.
190 letter of symbols.
191
191
192 By default, character ``c`` is recognized as valid for initial
192 By default, character ``c`` is recognized as valid for initial
193 letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.
193 letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.
194
194
195 ``symletters`` is the set of valid characters for non-initial
195 ``symletters`` is the set of valid characters for non-initial
196 letters of symbols.
196 letters of symbols.
197
197
198 By default, character ``c`` is recognized as valid for non-initial
198 By default, character ``c`` is recognized as valid for non-initial
199 letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
199 letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
200
200
201 Check that @ is a valid unquoted token character (issue3686):
201 Check that @ is a valid unquoted token character (issue3686):
202 >>> list(tokenize("@::"))
202 >>> list(tokenize("@::"))
203 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
203 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
204
204
205 '''
205 '''
206 if syminitletters is None:
206 if syminitletters is None:
207 syminitletters = _syminitletters
207 syminitletters = _syminitletters
208 if symletters is None:
208 if symletters is None:
209 symletters = _symletters
209 symletters = _symletters
210
210
211 if program and lookup:
211 if program and lookup:
212 # attempt to parse old-style ranges first to deal with
212 # attempt to parse old-style ranges first to deal with
213 # things like old-tag which contain query metacharacters
213 # things like old-tag which contain query metacharacters
214 parts = program.split(':', 1)
214 parts = program.split(':', 1)
215 if all(lookup(sym) for sym in parts if sym):
215 if all(lookup(sym) for sym in parts if sym):
216 if parts[0]:
216 if parts[0]:
217 yield ('symbol', parts[0], 0)
217 yield ('symbol', parts[0], 0)
218 if len(parts) > 1:
218 if len(parts) > 1:
219 s = len(parts[0])
219 s = len(parts[0])
220 yield (':', None, s)
220 yield (':', None, s)
221 if parts[1]:
221 if parts[1]:
222 yield ('symbol', parts[1], s + 1)
222 yield ('symbol', parts[1], s + 1)
223 yield ('end', None, len(program))
223 yield ('end', None, len(program))
224 return
224 return
225
225
226 pos, l = 0, len(program)
226 pos, l = 0, len(program)
227 while pos < l:
227 while pos < l:
228 c = program[pos]
228 c = program[pos]
229 if c.isspace(): # skip inter-token whitespace
229 if c.isspace(): # skip inter-token whitespace
230 pass
230 pass
231 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
231 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
232 yield ('::', None, pos)
232 yield ('::', None, pos)
233 pos += 1 # skip ahead
233 pos += 1 # skip ahead
234 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
234 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
235 yield ('..', None, pos)
235 yield ('..', None, pos)
236 pos += 1 # skip ahead
236 pos += 1 # skip ahead
237 elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
237 elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
238 yield ('##', None, pos)
238 yield ('##', None, pos)
239 pos += 1 # skip ahead
239 pos += 1 # skip ahead
240 elif c in "():=,-|&+!~^%": # handle simple operators
240 elif c in "():=,-|&+!~^%": # handle simple operators
241 yield (c, None, pos)
241 yield (c, None, pos)
242 elif (c in '"\'' or c == 'r' and
242 elif (c in '"\'' or c == 'r' and
243 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
243 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
244 if c == 'r':
244 if c == 'r':
245 pos += 1
245 pos += 1
246 c = program[pos]
246 c = program[pos]
247 decode = lambda x: x
247 decode = lambda x: x
248 else:
248 else:
249 decode = parser.unescapestr
249 decode = parser.unescapestr
250 pos += 1
250 pos += 1
251 s = pos
251 s = pos
252 while pos < l: # find closing quote
252 while pos < l: # find closing quote
253 d = program[pos]
253 d = program[pos]
254 if d == '\\': # skip over escaped characters
254 if d == '\\': # skip over escaped characters
255 pos += 2
255 pos += 2
256 continue
256 continue
257 if d == c:
257 if d == c:
258 yield ('string', decode(program[s:pos]), s)
258 yield ('string', decode(program[s:pos]), s)
259 break
259 break
260 pos += 1
260 pos += 1
261 else:
261 else:
262 raise error.ParseError(_("unterminated string"), s)
262 raise error.ParseError(_("unterminated string"), s)
263 # gather up a symbol/keyword
263 # gather up a symbol/keyword
264 elif c in syminitletters:
264 elif c in syminitletters:
265 s = pos
265 s = pos
266 pos += 1
266 pos += 1
267 while pos < l: # find end of symbol
267 while pos < l: # find end of symbol
268 d = program[pos]
268 d = program[pos]
269 if d not in symletters:
269 if d not in symletters:
270 break
270 break
271 if d == '.' and program[pos - 1] == '.': # special case for ..
271 if d == '.' and program[pos - 1] == '.': # special case for ..
272 pos -= 1
272 pos -= 1
273 break
273 break
274 pos += 1
274 pos += 1
275 sym = program[s:pos]
275 sym = program[s:pos]
276 if sym in keywords: # operator keywords
276 if sym in keywords: # operator keywords
277 yield (sym, None, s)
277 yield (sym, None, s)
278 elif '-' in sym:
278 elif '-' in sym:
279 # some jerk gave us foo-bar-baz, try to check if it's a symbol
279 # some jerk gave us foo-bar-baz, try to check if it's a symbol
280 if lookup and lookup(sym):
280 if lookup and lookup(sym):
281 # looks like a real symbol
281 # looks like a real symbol
282 yield ('symbol', sym, s)
282 yield ('symbol', sym, s)
283 else:
283 else:
284 # looks like an expression
284 # looks like an expression
285 parts = sym.split('-')
285 parts = sym.split('-')
286 for p in parts[:-1]:
286 for p in parts[:-1]:
287 if p: # possible consecutive -
287 if p: # possible consecutive -
288 yield ('symbol', p, s)
288 yield ('symbol', p, s)
289 s += len(p)
289 s += len(p)
290 yield ('-', None, pos)
290 yield ('-', None, pos)
291 s += 1
291 s += 1
292 if parts[-1]: # possible trailing -
292 if parts[-1]: # possible trailing -
293 yield ('symbol', parts[-1], s)
293 yield ('symbol', parts[-1], s)
294 else:
294 else:
295 yield ('symbol', sym, s)
295 yield ('symbol', sym, s)
296 pos -= 1
296 pos -= 1
297 else:
297 else:
298 raise error.ParseError(_("syntax error in revset '%s'") %
298 raise error.ParseError(_("syntax error in revset '%s'") %
299 program, pos)
299 program, pos)
300 pos += 1
300 pos += 1
301 yield ('end', None, pos)
301 yield ('end', None, pos)
302
302
303 # helpers
303 # helpers
304
304
305 def getstring(x, err):
305 def getstring(x, err):
306 if x and (x[0] == 'string' or x[0] == 'symbol'):
306 if x and (x[0] == 'string' or x[0] == 'symbol'):
307 return x[1]
307 return x[1]
308 raise error.ParseError(err)
308 raise error.ParseError(err)
309
309
310 def getlist(x):
310 def getlist(x):
311 if not x:
311 if not x:
312 return []
312 return []
313 if x[0] == 'list':
313 if x[0] == 'list':
314 return list(x[1:])
314 return list(x[1:])
315 return [x]
315 return [x]
316
316
317 def getargs(x, min, max, err):
317 def getargs(x, min, max, err):
318 l = getlist(x)
318 l = getlist(x)
319 if len(l) < min or (max >= 0 and len(l) > max):
319 if len(l) < min or (max >= 0 and len(l) > max):
320 raise error.ParseError(err)
320 raise error.ParseError(err)
321 return l
321 return l
322
322
323 def getargsdict(x, funcname, keys):
323 def getargsdict(x, funcname, keys):
324 return parser.buildargsdict(getlist(x), funcname, keys.split(),
324 return parser.buildargsdict(getlist(x), funcname, keys.split(),
325 keyvaluenode='keyvalue', keynode='symbol')
325 keyvaluenode='keyvalue', keynode='symbol')
326
326
327 def getset(repo, subset, x):
327 def getset(repo, subset, x):
328 if not x:
328 if not x:
329 raise error.ParseError(_("missing argument"))
329 raise error.ParseError(_("missing argument"))
330 s = methods[x[0]](repo, subset, *x[1:])
330 s = methods[x[0]](repo, subset, *x[1:])
331 if util.safehasattr(s, 'isascending'):
331 if util.safehasattr(s, 'isascending'):
332 return s
332 return s
333 if (repo.ui.configbool('devel', 'all-warnings')
333 if (repo.ui.configbool('devel', 'all-warnings')
334 or repo.ui.configbool('devel', 'old-revset')):
334 or repo.ui.configbool('devel', 'old-revset')):
335 # else case should not happen, because all non-func are internal,
335 # else case should not happen, because all non-func are internal,
336 # ignoring for now.
336 # ignoring for now.
337 if x[0] == 'func' and x[1][0] == 'symbol' and x[1][1] in symbols:
337 if x[0] == 'func' and x[1][0] == 'symbol' and x[1][1] in symbols:
338 repo.ui.develwarn('revset "%s" use list instead of smartset, '
338 repo.ui.develwarn('revset "%s" use list instead of smartset, '
339 '(upgrade your code)' % x[1][1])
339 '(upgrade your code)' % x[1][1])
340 return baseset(s)
340 return baseset(s)
341
341
342 def _getrevsource(repo, r):
342 def _getrevsource(repo, r):
343 extra = repo[r].extra()
343 extra = repo[r].extra()
344 for label in ('source', 'transplant_source', 'rebase_source'):
344 for label in ('source', 'transplant_source', 'rebase_source'):
345 if label in extra:
345 if label in extra:
346 try:
346 try:
347 return repo[extra[label]].rev()
347 return repo[extra[label]].rev()
348 except error.RepoLookupError:
348 except error.RepoLookupError:
349 pass
349 pass
350 return None
350 return None
351
351
352 # operator methods
352 # operator methods
353
353
354 def stringset(repo, subset, x):
354 def stringset(repo, subset, x):
355 x = repo[x].rev()
355 x = repo[x].rev()
356 if (x in subset
356 if (x in subset
357 or x == node.nullrev and isinstance(subset, fullreposet)):
357 or x == node.nullrev and isinstance(subset, fullreposet)):
358 return baseset([x])
358 return baseset([x])
359 return baseset()
359 return baseset()
360
360
361 def rangeset(repo, subset, x, y):
361 def rangeset(repo, subset, x, y):
362 m = getset(repo, fullreposet(repo), x)
362 m = getset(repo, fullreposet(repo), x)
363 n = getset(repo, fullreposet(repo), y)
363 n = getset(repo, fullreposet(repo), y)
364
364
365 if not m or not n:
365 if not m or not n:
366 return baseset()
366 return baseset()
367 m, n = m.first(), n.last()
367 m, n = m.first(), n.last()
368
368
369 if m == n:
369 if m == n:
370 r = baseset([m])
370 r = baseset([m])
371 elif n == node.wdirrev:
371 elif n == node.wdirrev:
372 r = spanset(repo, m, len(repo)) + baseset([n])
372 r = spanset(repo, m, len(repo)) + baseset([n])
373 elif m == node.wdirrev:
373 elif m == node.wdirrev:
374 r = baseset([m]) + spanset(repo, len(repo) - 1, n - 1)
374 r = baseset([m]) + spanset(repo, len(repo) - 1, n - 1)
375 elif m < n:
375 elif m < n:
376 r = spanset(repo, m, n + 1)
376 r = spanset(repo, m, n + 1)
377 else:
377 else:
378 r = spanset(repo, m, n - 1)
378 r = spanset(repo, m, n - 1)
379 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
379 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
380 # necessary to ensure we preserve the order in subset.
380 # necessary to ensure we preserve the order in subset.
381 #
381 #
382 # This has performance implication, carrying the sorting over when possible
382 # This has performance implication, carrying the sorting over when possible
383 # would be more efficient.
383 # would be more efficient.
384 return r & subset
384 return r & subset
385
385
386 def dagrange(repo, subset, x, y):
386 def dagrange(repo, subset, x, y):
387 r = fullreposet(repo)
387 r = fullreposet(repo)
388 xs = reachableroots(repo, getset(repo, r, x), getset(repo, r, y),
388 xs = reachableroots(repo, getset(repo, r, x), getset(repo, r, y),
389 includepath=True)
389 includepath=True)
390 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
390 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
391 # necessary to ensure we preserve the order in subset.
391 # necessary to ensure we preserve the order in subset.
392 return xs & subset
392 return xs & subset
393
393
394 def andset(repo, subset, x, y):
394 def andset(repo, subset, x, y):
395 return getset(repo, getset(repo, subset, x), y)
395 return getset(repo, getset(repo, subset, x), y)
396
396
397 def differenceset(repo, subset, x, y):
397 def differenceset(repo, subset, x, y):
398 return getset(repo, subset, x) - getset(repo, subset, y)
398 return getset(repo, subset, x) - getset(repo, subset, y)
399
399
400 def orset(repo, subset, *xs):
400 def orset(repo, subset, *xs):
401 assert xs
401 assert xs
402 if len(xs) == 1:
402 if len(xs) == 1:
403 return getset(repo, subset, xs[0])
403 return getset(repo, subset, xs[0])
404 p = len(xs) // 2
404 p = len(xs) // 2
405 a = orset(repo, subset, *xs[:p])
405 a = orset(repo, subset, *xs[:p])
406 b = orset(repo, subset, *xs[p:])
406 b = orset(repo, subset, *xs[p:])
407 return a + b
407 return a + b
408
408
409 def notset(repo, subset, x):
409 def notset(repo, subset, x):
410 return subset - getset(repo, subset, x)
410 return subset - getset(repo, subset, x)
411
411
412 def listset(repo, subset, *xs):
412 def listset(repo, subset, *xs):
413 raise error.ParseError(_("can't use a list in this context"),
413 raise error.ParseError(_("can't use a list in this context"),
414 hint=_('see hg help "revsets.x or y"'))
414 hint=_('see hg help "revsets.x or y"'))
415
415
416 def keyvaluepair(repo, subset, k, v):
416 def keyvaluepair(repo, subset, k, v):
417 raise error.ParseError(_("can't use a key-value pair in this context"))
417 raise error.ParseError(_("can't use a key-value pair in this context"))
418
418
419 def func(repo, subset, a, b):
419 def func(repo, subset, a, b):
420 if a[0] == 'symbol' and a[1] in symbols:
420 if a[0] == 'symbol' and a[1] in symbols:
421 return symbols[a[1]](repo, subset, b)
421 return symbols[a[1]](repo, subset, b)
422
422
423 keep = lambda fn: getattr(fn, '__doc__', None) is not None
423 keep = lambda fn: getattr(fn, '__doc__', None) is not None
424
424
425 syms = [s for (s, fn) in symbols.items() if keep(fn)]
425 syms = [s for (s, fn) in symbols.items() if keep(fn)]
426 raise error.UnknownIdentifier(a[1], syms)
426 raise error.UnknownIdentifier(a[1], syms)
427
427
428 # functions
428 # functions
429
429
430 # symbols are callables like:
430 # symbols are callables like:
431 # fn(repo, subset, x)
431 # fn(repo, subset, x)
432 # with:
432 # with:
433 # repo - current repository instance
433 # repo - current repository instance
434 # subset - of revisions to be examined
434 # subset - of revisions to be examined
435 # x - argument in tree form
435 # x - argument in tree form
436 symbols = {}
436 symbols = {}
437
437
438 # symbols which can't be used for a DoS attack for any given input
438 # symbols which can't be used for a DoS attack for any given input
439 # (e.g. those which accept regexes as plain strings shouldn't be included)
439 # (e.g. those which accept regexes as plain strings shouldn't be included)
440 # functions that just return a lot of changesets (like all) don't count here
440 # functions that just return a lot of changesets (like all) don't count here
441 safesymbols = set()
441 safesymbols = set()
442
442
443 predicate = registrar.revsetpredicate()
443 predicate = registrar.revsetpredicate()
444
444
445 @predicate('_destupdate')
445 @predicate('_destupdate')
446 def _destupdate(repo, subset, x):
446 def _destupdate(repo, subset, x):
447 # experimental revset for update destination
447 # experimental revset for update destination
448 args = getargsdict(x, 'limit', 'clean check')
448 args = getargsdict(x, 'limit', 'clean check')
449 return subset & baseset([destutil.destupdate(repo, **args)[0]])
449 return subset & baseset([destutil.destupdate(repo, **args)[0]])
450
450
451 @predicate('_destmerge')
451 @predicate('_destmerge')
452 def _destmerge(repo, subset, x):
452 def _destmerge(repo, subset, x):
453 # experimental revset for merge destination
453 # experimental revset for merge destination
454 sourceset = None
454 sourceset = None
455 if x is not None:
455 if x is not None:
456 sourceset = getset(repo, fullreposet(repo), x)
456 sourceset = getset(repo, fullreposet(repo), x)
457 return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
457 return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
458
458
459 @predicate('adds(pattern)', safe=True)
459 @predicate('adds(pattern)', safe=True)
460 def adds(repo, subset, x):
460 def adds(repo, subset, x):
461 """Changesets that add a file matching pattern.
461 """Changesets that add a file matching pattern.
462
462
463 The pattern without explicit kind like ``glob:`` is expected to be
463 The pattern without explicit kind like ``glob:`` is expected to be
464 relative to the current directory and match against a file or a
464 relative to the current directory and match against a file or a
465 directory.
465 directory.
466 """
466 """
467 # i18n: "adds" is a keyword
467 # i18n: "adds" is a keyword
468 pat = getstring(x, _("adds requires a pattern"))
468 pat = getstring(x, _("adds requires a pattern"))
469 return checkstatus(repo, subset, pat, 1)
469 return checkstatus(repo, subset, pat, 1)
470
470
471 @predicate('ancestor(*changeset)', safe=True)
471 @predicate('ancestor(*changeset)', safe=True)
472 def ancestor(repo, subset, x):
472 def ancestor(repo, subset, x):
473 """A greatest common ancestor of the changesets.
473 """A greatest common ancestor of the changesets.
474
474
475 Accepts 0 or more changesets.
475 Accepts 0 or more changesets.
476 Will return empty list when passed no args.
476 Will return empty list when passed no args.
477 Greatest common ancestor of a single changeset is that changeset.
477 Greatest common ancestor of a single changeset is that changeset.
478 """
478 """
479 # i18n: "ancestor" is a keyword
479 # i18n: "ancestor" is a keyword
480 l = getlist(x)
480 l = getlist(x)
481 rl = fullreposet(repo)
481 rl = fullreposet(repo)
482 anc = None
482 anc = None
483
483
484 # (getset(repo, rl, i) for i in l) generates a list of lists
484 # (getset(repo, rl, i) for i in l) generates a list of lists
485 for revs in (getset(repo, rl, i) for i in l):
485 for revs in (getset(repo, rl, i) for i in l):
486 for r in revs:
486 for r in revs:
487 if anc is None:
487 if anc is None:
488 anc = repo[r]
488 anc = repo[r]
489 else:
489 else:
490 anc = anc.ancestor(repo[r])
490 anc = anc.ancestor(repo[r])
491
491
492 if anc is not None and anc.rev() in subset:
492 if anc is not None and anc.rev() in subset:
493 return baseset([anc.rev()])
493 return baseset([anc.rev()])
494 return baseset()
494 return baseset()
495
495
496 def _ancestors(repo, subset, x, followfirst=False):
496 def _ancestors(repo, subset, x, followfirst=False):
497 heads = getset(repo, fullreposet(repo), x)
497 heads = getset(repo, fullreposet(repo), x)
498 if not heads:
498 if not heads:
499 return baseset()
499 return baseset()
500 s = _revancestors(repo, heads, followfirst)
500 s = _revancestors(repo, heads, followfirst)
501 return subset & s
501 return subset & s
502
502
503 @predicate('ancestors(set)', safe=True)
503 @predicate('ancestors(set)', safe=True)
504 def ancestors(repo, subset, x):
504 def ancestors(repo, subset, x):
505 """Changesets that are ancestors of a changeset in set.
505 """Changesets that are ancestors of a changeset in set.
506 """
506 """
507 return _ancestors(repo, subset, x)
507 return _ancestors(repo, subset, x)
508
508
509 @predicate('_firstancestors', safe=True)
509 @predicate('_firstancestors', safe=True)
510 def _firstancestors(repo, subset, x):
510 def _firstancestors(repo, subset, x):
511 # ``_firstancestors(set)``
511 # ``_firstancestors(set)``
512 # Like ``ancestors(set)`` but follows only the first parents.
512 # Like ``ancestors(set)`` but follows only the first parents.
513 return _ancestors(repo, subset, x, followfirst=True)
513 return _ancestors(repo, subset, x, followfirst=True)
514
514
515 def ancestorspec(repo, subset, x, n):
515 def ancestorspec(repo, subset, x, n):
516 """``set~n``
516 """``set~n``
517 Changesets that are the Nth ancestor (first parents only) of a changeset
517 Changesets that are the Nth ancestor (first parents only) of a changeset
518 in set.
518 in set.
519 """
519 """
520 try:
520 try:
521 n = int(n[1])
521 n = int(n[1])
522 except (TypeError, ValueError):
522 except (TypeError, ValueError):
523 raise error.ParseError(_("~ expects a number"))
523 raise error.ParseError(_("~ expects a number"))
524 ps = set()
524 ps = set()
525 cl = repo.changelog
525 cl = repo.changelog
526 for r in getset(repo, fullreposet(repo), x):
526 for r in getset(repo, fullreposet(repo), x):
527 for i in range(n):
527 for i in range(n):
528 r = cl.parentrevs(r)[0]
528 r = cl.parentrevs(r)[0]
529 ps.add(r)
529 ps.add(r)
530 return subset & ps
530 return subset & ps
531
531
532 @predicate('author(string)', safe=True)
532 @predicate('author(string)', safe=True)
533 def author(repo, subset, x):
533 def author(repo, subset, x):
534 """Alias for ``user(string)``.
534 """Alias for ``user(string)``.
535 """
535 """
536 # i18n: "author" is a keyword
536 # i18n: "author" is a keyword
537 n = encoding.lower(getstring(x, _("author requires a string")))
537 n = encoding.lower(getstring(x, _("author requires a string")))
538 kind, pattern, matcher = _substringmatcher(n)
538 kind, pattern, matcher = _substringmatcher(n)
539 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())),
539 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())),
540 condrepr=('<user %r>', n))
540 condrepr=('<user %r>', n))
541
541
542 @predicate('bisect(string)', safe=True)
542 @predicate('bisect(string)', safe=True)
543 def bisect(repo, subset, x):
543 def bisect(repo, subset, x):
544 """Changesets marked in the specified bisect status:
544 """Changesets marked in the specified bisect status:
545
545
546 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
546 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
547 - ``goods``, ``bads`` : csets topologically good/bad
547 - ``goods``, ``bads`` : csets topologically good/bad
548 - ``range`` : csets taking part in the bisection
548 - ``range`` : csets taking part in the bisection
549 - ``pruned`` : csets that are goods, bads or skipped
549 - ``pruned`` : csets that are goods, bads or skipped
550 - ``untested`` : csets whose fate is yet unknown
550 - ``untested`` : csets whose fate is yet unknown
551 - ``ignored`` : csets ignored due to DAG topology
551 - ``ignored`` : csets ignored due to DAG topology
552 - ``current`` : the cset currently being bisected
552 - ``current`` : the cset currently being bisected
553 """
553 """
554 # i18n: "bisect" is a keyword
554 # i18n: "bisect" is a keyword
555 status = getstring(x, _("bisect requires a string")).lower()
555 status = getstring(x, _("bisect requires a string")).lower()
556 state = set(hbisect.get(repo, status))
556 state = set(hbisect.get(repo, status))
557 return subset & state
557 return subset & state
558
558
559 # Backward-compatibility
559 # Backward-compatibility
560 # - no help entry so that we do not advertise it any more
560 # - no help entry so that we do not advertise it any more
561 @predicate('bisected', safe=True)
561 @predicate('bisected', safe=True)
562 def bisected(repo, subset, x):
562 def bisected(repo, subset, x):
563 return bisect(repo, subset, x)
563 return bisect(repo, subset, x)
564
564
565 @predicate('bookmark([name])', safe=True)
565 @predicate('bookmark([name])', safe=True)
566 def bookmark(repo, subset, x):
566 def bookmark(repo, subset, x):
567 """The named bookmark or all bookmarks.
567 """The named bookmark or all bookmarks.
568
568
569 If `name` starts with `re:`, the remainder of the name is treated as
569 If `name` starts with `re:`, the remainder of the name is treated as
570 a regular expression. To match a bookmark that actually starts with `re:`,
570 a regular expression. To match a bookmark that actually starts with `re:`,
571 use the prefix `literal:`.
571 use the prefix `literal:`.
572 """
572 """
573 # i18n: "bookmark" is a keyword
573 # i18n: "bookmark" is a keyword
574 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
574 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
575 if args:
575 if args:
576 bm = getstring(args[0],
576 bm = getstring(args[0],
577 # i18n: "bookmark" is a keyword
577 # i18n: "bookmark" is a keyword
578 _('the argument to bookmark must be a string'))
578 _('the argument to bookmark must be a string'))
579 kind, pattern, matcher = util.stringmatcher(bm)
579 kind, pattern, matcher = util.stringmatcher(bm)
580 bms = set()
580 bms = set()
581 if kind == 'literal':
581 if kind == 'literal':
582 bmrev = repo._bookmarks.get(pattern, None)
582 bmrev = repo._bookmarks.get(pattern, None)
583 if not bmrev:
583 if not bmrev:
584 raise error.RepoLookupError(_("bookmark '%s' does not exist")
584 raise error.RepoLookupError(_("bookmark '%s' does not exist")
585 % pattern)
585 % pattern)
586 bms.add(repo[bmrev].rev())
586 bms.add(repo[bmrev].rev())
587 else:
587 else:
588 matchrevs = set()
588 matchrevs = set()
589 for name, bmrev in repo._bookmarks.iteritems():
589 for name, bmrev in repo._bookmarks.iteritems():
590 if matcher(name):
590 if matcher(name):
591 matchrevs.add(bmrev)
591 matchrevs.add(bmrev)
592 if not matchrevs:
592 if not matchrevs:
593 raise error.RepoLookupError(_("no bookmarks exist"
593 raise error.RepoLookupError(_("no bookmarks exist"
594 " that match '%s'") % pattern)
594 " that match '%s'") % pattern)
595 for bmrev in matchrevs:
595 for bmrev in matchrevs:
596 bms.add(repo[bmrev].rev())
596 bms.add(repo[bmrev].rev())
597 else:
597 else:
598 bms = set([repo[r].rev()
598 bms = set([repo[r].rev()
599 for r in repo._bookmarks.values()])
599 for r in repo._bookmarks.values()])
600 bms -= set([node.nullrev])
600 bms -= set([node.nullrev])
601 return subset & bms
601 return subset & bms
602
602
603 @predicate('branch(string or set)', safe=True)
603 @predicate('branch(string or set)', safe=True)
604 def branch(repo, subset, x):
604 def branch(repo, subset, x):
605 """
605 """
606 All changesets belonging to the given branch or the branches of the given
606 All changesets belonging to the given branch or the branches of the given
607 changesets.
607 changesets.
608
608
609 If `string` starts with `re:`, the remainder of the name is treated as
609 If `string` starts with `re:`, the remainder of the name is treated as
610 a regular expression. To match a branch that actually starts with `re:`,
610 a regular expression. To match a branch that actually starts with `re:`,
611 use the prefix `literal:`.
611 use the prefix `literal:`.
612 """
612 """
613 getbi = repo.revbranchcache().branchinfo
613 getbi = repo.revbranchcache().branchinfo
614
614
615 try:
615 try:
616 b = getstring(x, '')
616 b = getstring(x, '')
617 except error.ParseError:
617 except error.ParseError:
618 # not a string, but another revspec, e.g. tip()
618 # not a string, but another revspec, e.g. tip()
619 pass
619 pass
620 else:
620 else:
621 kind, pattern, matcher = util.stringmatcher(b)
621 kind, pattern, matcher = util.stringmatcher(b)
622 if kind == 'literal':
622 if kind == 'literal':
623 # note: falls through to the revspec case if no branch with
623 # note: falls through to the revspec case if no branch with
624 # this name exists and pattern kind is not specified explicitly
624 # this name exists and pattern kind is not specified explicitly
625 if pattern in repo.branchmap():
625 if pattern in repo.branchmap():
626 return subset.filter(lambda r: matcher(getbi(r)[0]),
626 return subset.filter(lambda r: matcher(getbi(r)[0]),
627 condrepr=('<branch %r>', b))
627 condrepr=('<branch %r>', b))
628 if b.startswith('literal:'):
628 if b.startswith('literal:'):
629 raise error.RepoLookupError(_("branch '%s' does not exist")
629 raise error.RepoLookupError(_("branch '%s' does not exist")
630 % pattern)
630 % pattern)
631 else:
631 else:
632 return subset.filter(lambda r: matcher(getbi(r)[0]),
632 return subset.filter(lambda r: matcher(getbi(r)[0]),
633 condrepr=('<branch %r>', b))
633 condrepr=('<branch %r>', b))
634
634
635 s = getset(repo, fullreposet(repo), x)
635 s = getset(repo, fullreposet(repo), x)
636 b = set()
636 b = set()
637 for r in s:
637 for r in s:
638 b.add(getbi(r)[0])
638 b.add(getbi(r)[0])
639 c = s.__contains__
639 c = s.__contains__
640 return subset.filter(lambda r: c(r) or getbi(r)[0] in b,
640 return subset.filter(lambda r: c(r) or getbi(r)[0] in b,
641 condrepr=lambda: '<branch %r>' % sorted(b))
641 condrepr=lambda: '<branch %r>' % sorted(b))
642
642
643 @predicate('bumped()', safe=True)
643 @predicate('bumped()', safe=True)
644 def bumped(repo, subset, x):
644 def bumped(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 `bumped`.
647 Only non-public and non-obsolete changesets can be `bumped`.
648 """
648 """
649 # i18n: "bumped" is a keyword
649 # i18n: "bumped" is a keyword
650 getargs(x, 0, 0, _("bumped takes no arguments"))
650 getargs(x, 0, 0, _("bumped takes no arguments"))
651 bumped = obsmod.getrevs(repo, 'bumped')
651 bumped = obsmod.getrevs(repo, 'bumped')
652 return subset & bumped
652 return subset & bumped
653
653
654 @predicate('bundle()', safe=True)
654 @predicate('bundle()', safe=True)
655 def bundle(repo, subset, x):
655 def bundle(repo, subset, x):
656 """Changesets in the bundle.
656 """Changesets in the bundle.
657
657
658 Bundle must be specified by the -R option."""
658 Bundle must be specified by the -R option."""
659
659
660 try:
660 try:
661 bundlerevs = repo.changelog.bundlerevs
661 bundlerevs = repo.changelog.bundlerevs
662 except AttributeError:
662 except AttributeError:
663 raise error.Abort(_("no bundle provided - specify with -R"))
663 raise error.Abort(_("no bundle provided - specify with -R"))
664 return subset & bundlerevs
664 return subset & bundlerevs
665
665
666 def checkstatus(repo, subset, pat, field):
666 def checkstatus(repo, subset, pat, field):
667 hasset = matchmod.patkind(pat) == 'set'
667 hasset = matchmod.patkind(pat) == 'set'
668
668
669 mcache = [None]
669 mcache = [None]
670 def matches(x):
670 def matches(x):
671 c = repo[x]
671 c = repo[x]
672 if not mcache[0] or hasset:
672 if not mcache[0] or hasset:
673 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
673 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
674 m = mcache[0]
674 m = mcache[0]
675 fname = None
675 fname = None
676 if not m.anypats() and len(m.files()) == 1:
676 if not m.anypats() and len(m.files()) == 1:
677 fname = m.files()[0]
677 fname = m.files()[0]
678 if fname is not None:
678 if fname is not None:
679 if fname not in c.files():
679 if fname not in c.files():
680 return False
680 return False
681 else:
681 else:
682 for f in c.files():
682 for f in c.files():
683 if m(f):
683 if m(f):
684 break
684 break
685 else:
685 else:
686 return False
686 return False
687 files = repo.status(c.p1().node(), c.node())[field]
687 files = repo.status(c.p1().node(), c.node())[field]
688 if fname is not None:
688 if fname is not None:
689 if fname in files:
689 if fname in files:
690 return True
690 return True
691 else:
691 else:
692 for f in files:
692 for f in files:
693 if m(f):
693 if m(f):
694 return True
694 return True
695
695
696 return subset.filter(matches, condrepr=('<status[%r] %r>', field, pat))
696 return subset.filter(matches, condrepr=('<status[%r] %r>', field, pat))
697
697
698 def _children(repo, narrow, parentset):
698 def _children(repo, narrow, parentset):
699 if not parentset:
699 if not parentset:
700 return baseset()
700 return baseset()
701 cs = set()
701 cs = set()
702 pr = repo.changelog.parentrevs
702 pr = repo.changelog.parentrevs
703 minrev = parentset.min()
703 minrev = parentset.min()
704 for r in narrow:
704 for r in narrow:
705 if r <= minrev:
705 if r <= minrev:
706 continue
706 continue
707 for p in pr(r):
707 for p in pr(r):
708 if p in parentset:
708 if p in parentset:
709 cs.add(r)
709 cs.add(r)
710 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
710 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
711 # This does not break because of other fullreposet misbehavior.
711 # This does not break because of other fullreposet misbehavior.
712 return baseset(cs)
712 return baseset(cs)
713
713
714 @predicate('children(set)', safe=True)
714 @predicate('children(set)', safe=True)
715 def children(repo, subset, x):
715 def children(repo, subset, x):
716 """Child changesets of changesets in set.
716 """Child changesets of changesets in set.
717 """
717 """
718 s = getset(repo, fullreposet(repo), x)
718 s = getset(repo, fullreposet(repo), x)
719 cs = _children(repo, subset, s)
719 cs = _children(repo, subset, s)
720 return subset & cs
720 return subset & cs
721
721
722 @predicate('closed()', safe=True)
722 @predicate('closed()', safe=True)
723 def closed(repo, subset, x):
723 def closed(repo, subset, x):
724 """Changeset is closed.
724 """Changeset is closed.
725 """
725 """
726 # i18n: "closed" is a keyword
726 # i18n: "closed" is a keyword
727 getargs(x, 0, 0, _("closed takes no arguments"))
727 getargs(x, 0, 0, _("closed takes no arguments"))
728 return subset.filter(lambda r: repo[r].closesbranch(),
728 return subset.filter(lambda r: repo[r].closesbranch(),
729 condrepr='<branch closed>')
729 condrepr='<branch closed>')
730
730
731 @predicate('contains(pattern)')
731 @predicate('contains(pattern)')
732 def contains(repo, subset, x):
732 def contains(repo, subset, x):
733 """The revision's manifest contains a file matching pattern (but might not
733 """The revision's manifest contains a file matching pattern (but might not
734 modify it). See :hg:`help patterns` for information about file patterns.
734 modify it). See :hg:`help patterns` for information about file patterns.
735
735
736 The pattern without explicit kind like ``glob:`` is expected to be
736 The pattern without explicit kind like ``glob:`` is expected to be
737 relative to the current directory and match against a file exactly
737 relative to the current directory and match against a file exactly
738 for efficiency.
738 for efficiency.
739 """
739 """
740 # i18n: "contains" is a keyword
740 # i18n: "contains" is a keyword
741 pat = getstring(x, _("contains requires a pattern"))
741 pat = getstring(x, _("contains requires a pattern"))
742
742
743 def matches(x):
743 def matches(x):
744 if not matchmod.patkind(pat):
744 if not matchmod.patkind(pat):
745 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
745 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
746 if pats in repo[x]:
746 if pats in repo[x]:
747 return True
747 return True
748 else:
748 else:
749 c = repo[x]
749 c = repo[x]
750 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
750 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
751 for f in c.manifest():
751 for f in c.manifest():
752 if m(f):
752 if m(f):
753 return True
753 return True
754 return False
754 return False
755
755
756 return subset.filter(matches, condrepr=('<contains %r>', pat))
756 return subset.filter(matches, condrepr=('<contains %r>', pat))
757
757
758 @predicate('converted([id])', safe=True)
758 @predicate('converted([id])', safe=True)
759 def converted(repo, subset, x):
759 def converted(repo, subset, x):
760 """Changesets converted from the given identifier in the old repository if
760 """Changesets converted from the given identifier in the old repository if
761 present, or all converted changesets if no identifier is specified.
761 present, or all converted changesets if no identifier is specified.
762 """
762 """
763
763
764 # There is exactly no chance of resolving the revision, so do a simple
764 # There is exactly no chance of resolving the revision, so do a simple
765 # string compare and hope for the best
765 # string compare and hope for the best
766
766
767 rev = None
767 rev = None
768 # i18n: "converted" is a keyword
768 # i18n: "converted" is a keyword
769 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
769 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
770 if l:
770 if l:
771 # i18n: "converted" is a keyword
771 # i18n: "converted" is a keyword
772 rev = getstring(l[0], _('converted requires a revision'))
772 rev = getstring(l[0], _('converted requires a revision'))
773
773
774 def _matchvalue(r):
774 def _matchvalue(r):
775 source = repo[r].extra().get('convert_revision', None)
775 source = repo[r].extra().get('convert_revision', None)
776 return source is not None and (rev is None or source.startswith(rev))
776 return source is not None and (rev is None or source.startswith(rev))
777
777
778 return subset.filter(lambda r: _matchvalue(r),
778 return subset.filter(lambda r: _matchvalue(r),
779 condrepr=('<converted %r>', rev))
779 condrepr=('<converted %r>', rev))
780
780
781 @predicate('date(interval)', safe=True)
781 @predicate('date(interval)', safe=True)
782 def date(repo, subset, x):
782 def date(repo, subset, x):
783 """Changesets within the interval, see :hg:`help dates`.
783 """Changesets within the interval, see :hg:`help dates`.
784 """
784 """
785 # i18n: "date" is a keyword
785 # i18n: "date" is a keyword
786 ds = getstring(x, _("date requires a string"))
786 ds = getstring(x, _("date requires a string"))
787 dm = util.matchdate(ds)
787 dm = util.matchdate(ds)
788 return subset.filter(lambda x: dm(repo[x].date()[0]),
788 return subset.filter(lambda x: dm(repo[x].date()[0]),
789 condrepr=('<date %r>', ds))
789 condrepr=('<date %r>', ds))
790
790
791 @predicate('desc(string)', safe=True)
791 @predicate('desc(string)', safe=True)
792 def desc(repo, subset, x):
792 def desc(repo, subset, x):
793 """Search commit message for string. The match is case-insensitive.
793 """Search commit message for string. The match is case-insensitive.
794 """
794 """
795 # i18n: "desc" is a keyword
795 # i18n: "desc" is a keyword
796 ds = encoding.lower(getstring(x, _("desc requires a string")))
796 ds = encoding.lower(getstring(x, _("desc requires a string")))
797
797
798 def matches(x):
798 def matches(x):
799 c = repo[x]
799 c = repo[x]
800 return ds in encoding.lower(c.description())
800 return ds in encoding.lower(c.description())
801
801
802 return subset.filter(matches, condrepr=('<desc %r>', ds))
802 return subset.filter(matches, condrepr=('<desc %r>', ds))
803
803
804 def _descendants(repo, subset, x, followfirst=False):
804 def _descendants(repo, subset, x, followfirst=False):
805 roots = getset(repo, fullreposet(repo), x)
805 roots = getset(repo, fullreposet(repo), x)
806 if not roots:
806 if not roots:
807 return baseset()
807 return baseset()
808 s = _revdescendants(repo, roots, followfirst)
808 s = _revdescendants(repo, roots, followfirst)
809
809
810 # Both sets need to be ascending in order to lazily return the union
810 # Both sets need to be ascending in order to lazily return the union
811 # in the correct order.
811 # in the correct order.
812 base = subset & roots
812 base = subset & roots
813 desc = subset & s
813 desc = subset & s
814 result = base + desc
814 result = base + desc
815 if subset.isascending():
815 if subset.isascending():
816 result.sort()
816 result.sort()
817 elif subset.isdescending():
817 elif subset.isdescending():
818 result.sort(reverse=True)
818 result.sort(reverse=True)
819 else:
819 else:
820 result = subset & result
820 result = subset & result
821 return result
821 return result
822
822
823 @predicate('descendants(set)', safe=True)
823 @predicate('descendants(set)', safe=True)
824 def descendants(repo, subset, x):
824 def descendants(repo, subset, x):
825 """Changesets which are descendants of changesets in set.
825 """Changesets which are descendants of changesets in set.
826 """
826 """
827 return _descendants(repo, subset, x)
827 return _descendants(repo, subset, x)
828
828
829 @predicate('_firstdescendants', safe=True)
829 @predicate('_firstdescendants', safe=True)
830 def _firstdescendants(repo, subset, x):
830 def _firstdescendants(repo, subset, x):
831 # ``_firstdescendants(set)``
831 # ``_firstdescendants(set)``
832 # Like ``descendants(set)`` but follows only the first parents.
832 # Like ``descendants(set)`` but follows only the first parents.
833 return _descendants(repo, subset, x, followfirst=True)
833 return _descendants(repo, subset, x, followfirst=True)
834
834
835 @predicate('destination([set])', safe=True)
835 @predicate('destination([set])', safe=True)
836 def destination(repo, subset, x):
836 def destination(repo, subset, x):
837 """Changesets that were created by a graft, transplant or rebase operation,
837 """Changesets that were created by a graft, transplant or rebase operation,
838 with the given revisions specified as the source. Omitting the optional set
838 with the given revisions specified as the source. Omitting the optional set
839 is the same as passing all().
839 is the same as passing all().
840 """
840 """
841 if x is not None:
841 if x is not None:
842 sources = getset(repo, fullreposet(repo), x)
842 sources = getset(repo, fullreposet(repo), x)
843 else:
843 else:
844 sources = fullreposet(repo)
844 sources = fullreposet(repo)
845
845
846 dests = set()
846 dests = set()
847
847
848 # subset contains all of the possible destinations that can be returned, so
848 # subset contains all of the possible destinations that can be returned, so
849 # iterate over them and see if their source(s) were provided in the arg set.
849 # iterate over them and see if their source(s) were provided in the arg set.
850 # Even if the immediate src of r is not in the arg set, src's source (or
850 # Even if the immediate src of r is not in the arg set, src's source (or
851 # further back) may be. Scanning back further than the immediate src allows
851 # further back) may be. Scanning back further than the immediate src allows
852 # transitive transplants and rebases to yield the same results as transitive
852 # transitive transplants and rebases to yield the same results as transitive
853 # grafts.
853 # grafts.
854 for r in subset:
854 for r in subset:
855 src = _getrevsource(repo, r)
855 src = _getrevsource(repo, r)
856 lineage = None
856 lineage = None
857
857
858 while src is not None:
858 while src is not None:
859 if lineage is None:
859 if lineage is None:
860 lineage = list()
860 lineage = list()
861
861
862 lineage.append(r)
862 lineage.append(r)
863
863
864 # The visited lineage is a match if the current source is in the arg
864 # The visited lineage is a match if the current source is in the arg
865 # set. Since every candidate dest is visited by way of iterating
865 # set. Since every candidate dest is visited by way of iterating
866 # subset, any dests further back in the lineage will be tested by a
866 # subset, any dests further back in the lineage will be tested by a
867 # different iteration over subset. Likewise, if the src was already
867 # different iteration over subset. Likewise, if the src was already
868 # selected, the current lineage can be selected without going back
868 # selected, the current lineage can be selected without going back
869 # further.
869 # further.
870 if src in sources or src in dests:
870 if src in sources or src in dests:
871 dests.update(lineage)
871 dests.update(lineage)
872 break
872 break
873
873
874 r = src
874 r = src
875 src = _getrevsource(repo, r)
875 src = _getrevsource(repo, r)
876
876
877 return subset.filter(dests.__contains__,
877 return subset.filter(dests.__contains__,
878 condrepr=lambda: '<destination %r>' % sorted(dests))
878 condrepr=lambda: '<destination %r>' % sorted(dests))
879
879
880 @predicate('divergent()', safe=True)
880 @predicate('divergent()', safe=True)
881 def divergent(repo, subset, x):
881 def divergent(repo, subset, x):
882 """
882 """
883 Final successors of changesets with an alternative set of final successors.
883 Final successors of changesets with an alternative set of final successors.
884 """
884 """
885 # i18n: "divergent" is a keyword
885 # i18n: "divergent" is a keyword
886 getargs(x, 0, 0, _("divergent takes no arguments"))
886 getargs(x, 0, 0, _("divergent takes no arguments"))
887 divergent = obsmod.getrevs(repo, 'divergent')
887 divergent = obsmod.getrevs(repo, 'divergent')
888 return subset & divergent
888 return subset & divergent
889
889
890 @predicate('extinct()', safe=True)
890 @predicate('extinct()', safe=True)
891 def extinct(repo, subset, x):
891 def extinct(repo, subset, x):
892 """Obsolete changesets with obsolete descendants only.
892 """Obsolete changesets with obsolete descendants only.
893 """
893 """
894 # i18n: "extinct" is a keyword
894 # i18n: "extinct" is a keyword
895 getargs(x, 0, 0, _("extinct takes no arguments"))
895 getargs(x, 0, 0, _("extinct takes no arguments"))
896 extincts = obsmod.getrevs(repo, 'extinct')
896 extincts = obsmod.getrevs(repo, 'extinct')
897 return subset & extincts
897 return subset & extincts
898
898
899 @predicate('extra(label, [value])', safe=True)
899 @predicate('extra(label, [value])', safe=True)
900 def extra(repo, subset, x):
900 def extra(repo, subset, x):
901 """Changesets with the given label in the extra metadata, with the given
901 """Changesets with the given label in the extra metadata, with the given
902 optional value.
902 optional value.
903
903
904 If `value` starts with `re:`, the remainder of the value is treated as
904 If `value` starts with `re:`, the remainder of the value is treated as
905 a regular expression. To match a value that actually starts with `re:`,
905 a regular expression. To match a value that actually starts with `re:`,
906 use the prefix `literal:`.
906 use the prefix `literal:`.
907 """
907 """
908 args = getargsdict(x, 'extra', 'label value')
908 args = getargsdict(x, 'extra', 'label value')
909 if 'label' not in args:
909 if 'label' not in args:
910 # i18n: "extra" is a keyword
910 # i18n: "extra" is a keyword
911 raise error.ParseError(_('extra takes at least 1 argument'))
911 raise error.ParseError(_('extra takes at least 1 argument'))
912 # i18n: "extra" is a keyword
912 # i18n: "extra" is a keyword
913 label = getstring(args['label'], _('first argument to extra must be '
913 label = getstring(args['label'], _('first argument to extra must be '
914 'a string'))
914 'a string'))
915 value = None
915 value = None
916
916
917 if 'value' in args:
917 if 'value' in args:
918 # i18n: "extra" is a keyword
918 # i18n: "extra" is a keyword
919 value = getstring(args['value'], _('second argument to extra must be '
919 value = getstring(args['value'], _('second argument to extra must be '
920 'a string'))
920 'a string'))
921 kind, value, matcher = util.stringmatcher(value)
921 kind, value, matcher = util.stringmatcher(value)
922
922
923 def _matchvalue(r):
923 def _matchvalue(r):
924 extra = repo[r].extra()
924 extra = repo[r].extra()
925 return label in extra and (value is None or matcher(extra[label]))
925 return label in extra and (value is None or matcher(extra[label]))
926
926
927 return subset.filter(lambda r: _matchvalue(r),
927 return subset.filter(lambda r: _matchvalue(r),
928 condrepr=('<extra[%r] %r>', label, value))
928 condrepr=('<extra[%r] %r>', label, value))
929
929
930 @predicate('filelog(pattern)', safe=True)
930 @predicate('filelog(pattern)', safe=True)
931 def filelog(repo, subset, x):
931 def filelog(repo, subset, x):
932 """Changesets connected to the specified filelog.
932 """Changesets connected to the specified filelog.
933
933
934 For performance reasons, visits only revisions mentioned in the file-level
934 For performance reasons, visits only revisions mentioned in the file-level
935 filelog, rather than filtering through all changesets (much faster, but
935 filelog, rather than filtering through all changesets (much faster, but
936 doesn't include deletes or duplicate changes). For a slower, more accurate
936 doesn't include deletes or duplicate changes). For a slower, more accurate
937 result, use ``file()``.
937 result, use ``file()``.
938
938
939 The pattern without explicit kind like ``glob:`` is expected to be
939 The pattern without explicit kind like ``glob:`` is expected to be
940 relative to the current directory and match against a file exactly
940 relative to the current directory and match against a file exactly
941 for efficiency.
941 for efficiency.
942
942
943 If some linkrev points to revisions filtered by the current repoview, we'll
943 If some linkrev points to revisions filtered by the current repoview, we'll
944 work around it to return a non-filtered value.
944 work around it to return a non-filtered value.
945 """
945 """
946
946
947 # i18n: "filelog" is a keyword
947 # i18n: "filelog" is a keyword
948 pat = getstring(x, _("filelog requires a pattern"))
948 pat = getstring(x, _("filelog requires a pattern"))
949 s = set()
949 s = set()
950 cl = repo.changelog
950 cl = repo.changelog
951
951
952 if not matchmod.patkind(pat):
952 if not matchmod.patkind(pat):
953 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
953 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
954 files = [f]
954 files = [f]
955 else:
955 else:
956 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
956 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
957 files = (f for f in repo[None] if m(f))
957 files = (f for f in repo[None] if m(f))
958
958
959 for f in files:
959 for f in files:
960 fl = repo.file(f)
960 fl = repo.file(f)
961 known = {}
961 known = {}
962 scanpos = 0
962 scanpos = 0
963 for fr in list(fl):
963 for fr in list(fl):
964 fn = fl.node(fr)
964 fn = fl.node(fr)
965 if fn in known:
965 if fn in known:
966 s.add(known[fn])
966 s.add(known[fn])
967 continue
967 continue
968
968
969 lr = fl.linkrev(fr)
969 lr = fl.linkrev(fr)
970 if lr in cl:
970 if lr in cl:
971 s.add(lr)
971 s.add(lr)
972 elif scanpos is not None:
972 elif scanpos is not None:
973 # lowest matching changeset is filtered, scan further
973 # lowest matching changeset is filtered, scan further
974 # ahead in changelog
974 # ahead in changelog
975 start = max(lr, scanpos) + 1
975 start = max(lr, scanpos) + 1
976 scanpos = None
976 scanpos = None
977 for r in cl.revs(start):
977 for r in cl.revs(start):
978 # minimize parsing of non-matching entries
978 # minimize parsing of non-matching entries
979 if f in cl.revision(r) and f in cl.readfiles(r):
979 if f in cl.revision(r) and f in cl.readfiles(r):
980 try:
980 try:
981 # try to use manifest delta fastpath
981 # try to use manifest delta fastpath
982 n = repo[r].filenode(f)
982 n = repo[r].filenode(f)
983 if n not in known:
983 if n not in known:
984 if n == fn:
984 if n == fn:
985 s.add(r)
985 s.add(r)
986 scanpos = r
986 scanpos = r
987 break
987 break
988 else:
988 else:
989 known[n] = r
989 known[n] = r
990 except error.ManifestLookupError:
990 except error.ManifestLookupError:
991 # deletion in changelog
991 # deletion in changelog
992 continue
992 continue
993
993
994 return subset & s
994 return subset & s
995
995
996 @predicate('first(set, [n])', safe=True)
996 @predicate('first(set, [n])', safe=True)
997 def first(repo, subset, x):
997 def first(repo, subset, x):
998 """An alias for limit().
998 """An alias for limit().
999 """
999 """
1000 return limit(repo, subset, x)
1000 return limit(repo, subset, x)
1001
1001
1002 def _follow(repo, subset, x, name, followfirst=False):
1002 def _follow(repo, subset, x, name, followfirst=False):
1003 l = getargs(x, 0, 1, _("%s takes no arguments or a pattern") % name)
1003 l = getargs(x, 0, 1, _("%s takes no arguments or a pattern") % name)
1004 c = repo['.']
1004 c = repo['.']
1005 if l:
1005 if l:
1006 x = getstring(l[0], _("%s expected a pattern") % name)
1006 x = getstring(l[0], _("%s expected a pattern") % name)
1007 matcher = matchmod.match(repo.root, repo.getcwd(), [x],
1007 matcher = matchmod.match(repo.root, repo.getcwd(), [x],
1008 ctx=repo[None], default='path')
1008 ctx=repo[None], default='path')
1009
1009
1010 files = c.manifest().walk(matcher)
1010 files = c.manifest().walk(matcher)
1011
1011
1012 s = set()
1012 s = set()
1013 for fname in files:
1013 for fname in files:
1014 fctx = c[fname]
1014 fctx = c[fname]
1015 s = s.union(set(c.rev() for c in fctx.ancestors(followfirst)))
1015 s = s.union(set(c.rev() for c in fctx.ancestors(followfirst)))
1016 # include the revision responsible for the most recent version
1016 # include the revision responsible for the most recent version
1017 s.add(fctx.introrev())
1017 s.add(fctx.introrev())
1018 else:
1018 else:
1019 s = _revancestors(repo, baseset([c.rev()]), followfirst)
1019 s = _revancestors(repo, baseset([c.rev()]), followfirst)
1020
1020
1021 return subset & s
1021 return subset & s
1022
1022
1023 @predicate('follow([pattern])', safe=True)
1023 @predicate('follow([pattern])', safe=True)
1024 def follow(repo, subset, x):
1024 def follow(repo, subset, x):
1025 """
1025 """
1026 An alias for ``::.`` (ancestors of the working directory's first parent).
1026 An alias for ``::.`` (ancestors of the working directory's first parent).
1027 If pattern is specified, the histories of files matching given
1027 If pattern is specified, the histories of files matching given
1028 pattern is followed, including copies.
1028 pattern is followed, including copies.
1029 """
1029 """
1030 return _follow(repo, subset, x, 'follow')
1030 return _follow(repo, subset, x, 'follow')
1031
1031
1032 @predicate('_followfirst', safe=True)
1032 @predicate('_followfirst', safe=True)
1033 def _followfirst(repo, subset, x):
1033 def _followfirst(repo, subset, x):
1034 # ``followfirst([pattern])``
1034 # ``followfirst([pattern])``
1035 # Like ``follow([pattern])`` but follows only the first parent of
1035 # Like ``follow([pattern])`` but follows only the first parent of
1036 # every revisions or files revisions.
1036 # every revisions or files revisions.
1037 return _follow(repo, subset, x, '_followfirst', followfirst=True)
1037 return _follow(repo, subset, x, '_followfirst', followfirst=True)
1038
1038
1039 @predicate('all()', safe=True)
1039 @predicate('all()', safe=True)
1040 def getall(repo, subset, x):
1040 def getall(repo, subset, x):
1041 """All changesets, the same as ``0:tip``.
1041 """All changesets, the same as ``0:tip``.
1042 """
1042 """
1043 # i18n: "all" is a keyword
1043 # i18n: "all" is a keyword
1044 getargs(x, 0, 0, _("all takes no arguments"))
1044 getargs(x, 0, 0, _("all takes no arguments"))
1045 return subset & spanset(repo) # drop "null" if any
1045 return subset & spanset(repo) # drop "null" if any
1046
1046
1047 @predicate('grep(regex)')
1047 @predicate('grep(regex)')
1048 def grep(repo, subset, x):
1048 def grep(repo, subset, x):
1049 """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1049 """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1050 to ensure special escape characters are handled correctly. Unlike
1050 to ensure special escape characters are handled correctly. Unlike
1051 ``keyword(string)``, the match is case-sensitive.
1051 ``keyword(string)``, the match is case-sensitive.
1052 """
1052 """
1053 try:
1053 try:
1054 # i18n: "grep" is a keyword
1054 # i18n: "grep" is a keyword
1055 gr = re.compile(getstring(x, _("grep requires a string")))
1055 gr = re.compile(getstring(x, _("grep requires a string")))
1056 except re.error as e:
1056 except re.error as e:
1057 raise error.ParseError(_('invalid match pattern: %s') % e)
1057 raise error.ParseError(_('invalid match pattern: %s') % e)
1058
1058
1059 def matches(x):
1059 def matches(x):
1060 c = repo[x]
1060 c = repo[x]
1061 for e in c.files() + [c.user(), c.description()]:
1061 for e in c.files() + [c.user(), c.description()]:
1062 if gr.search(e):
1062 if gr.search(e):
1063 return True
1063 return True
1064 return False
1064 return False
1065
1065
1066 return subset.filter(matches, condrepr=('<grep %r>', gr.pattern))
1066 return subset.filter(matches, condrepr=('<grep %r>', gr.pattern))
1067
1067
1068 @predicate('_matchfiles', safe=True)
1068 @predicate('_matchfiles', safe=True)
1069 def _matchfiles(repo, subset, x):
1069 def _matchfiles(repo, subset, x):
1070 # _matchfiles takes a revset list of prefixed arguments:
1070 # _matchfiles takes a revset list of prefixed arguments:
1071 #
1071 #
1072 # [p:foo, i:bar, x:baz]
1072 # [p:foo, i:bar, x:baz]
1073 #
1073 #
1074 # builds a match object from them and filters subset. Allowed
1074 # builds a match object from them and filters subset. Allowed
1075 # prefixes are 'p:' for regular patterns, 'i:' for include
1075 # prefixes are 'p:' for regular patterns, 'i:' for include
1076 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1076 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1077 # a revision identifier, or the empty string to reference the
1077 # a revision identifier, or the empty string to reference the
1078 # working directory, from which the match object is
1078 # working directory, from which the match object is
1079 # initialized. Use 'd:' to set the default matching mode, default
1079 # initialized. Use 'd:' to set the default matching mode, default
1080 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1080 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1081
1081
1082 l = getargs(x, 1, -1, "_matchfiles requires at least one argument")
1082 l = getargs(x, 1, -1, "_matchfiles requires at least one argument")
1083 pats, inc, exc = [], [], []
1083 pats, inc, exc = [], [], []
1084 rev, default = None, None
1084 rev, default = None, None
1085 for arg in l:
1085 for arg in l:
1086 s = getstring(arg, "_matchfiles requires string arguments")
1086 s = getstring(arg, "_matchfiles requires string arguments")
1087 prefix, value = s[:2], s[2:]
1087 prefix, value = s[:2], s[2:]
1088 if prefix == 'p:':
1088 if prefix == 'p:':
1089 pats.append(value)
1089 pats.append(value)
1090 elif prefix == 'i:':
1090 elif prefix == 'i:':
1091 inc.append(value)
1091 inc.append(value)
1092 elif prefix == 'x:':
1092 elif prefix == 'x:':
1093 exc.append(value)
1093 exc.append(value)
1094 elif prefix == 'r:':
1094 elif prefix == 'r:':
1095 if rev is not None:
1095 if rev is not None:
1096 raise error.ParseError('_matchfiles expected at most one '
1096 raise error.ParseError('_matchfiles expected at most one '
1097 'revision')
1097 'revision')
1098 if value != '': # empty means working directory; leave rev as None
1098 if value != '': # empty means working directory; leave rev as None
1099 rev = value
1099 rev = value
1100 elif prefix == 'd:':
1100 elif prefix == 'd:':
1101 if default is not None:
1101 if default is not None:
1102 raise error.ParseError('_matchfiles expected at most one '
1102 raise error.ParseError('_matchfiles expected at most one '
1103 'default mode')
1103 'default mode')
1104 default = value
1104 default = value
1105 else:
1105 else:
1106 raise error.ParseError('invalid _matchfiles prefix: %s' % prefix)
1106 raise error.ParseError('invalid _matchfiles prefix: %s' % prefix)
1107 if not default:
1107 if not default:
1108 default = 'glob'
1108 default = 'glob'
1109
1109
1110 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
1110 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
1111 exclude=exc, ctx=repo[rev], default=default)
1111 exclude=exc, ctx=repo[rev], default=default)
1112
1112
1113 # This directly read the changelog data as creating changectx for all
1113 # This directly read the changelog data as creating changectx for all
1114 # revisions is quite expensive.
1114 # revisions is quite expensive.
1115 getfiles = repo.changelog.readfiles
1115 getfiles = repo.changelog.readfiles
1116 wdirrev = node.wdirrev
1116 wdirrev = node.wdirrev
1117 def matches(x):
1117 def matches(x):
1118 if x == wdirrev:
1118 if x == wdirrev:
1119 files = repo[x].files()
1119 files = repo[x].files()
1120 else:
1120 else:
1121 files = getfiles(x)
1121 files = getfiles(x)
1122 for f in files:
1122 for f in files:
1123 if m(f):
1123 if m(f):
1124 return True
1124 return True
1125 return False
1125 return False
1126
1126
1127 return subset.filter(matches,
1127 return subset.filter(matches,
1128 condrepr=('<matchfiles patterns=%r, include=%r '
1128 condrepr=('<matchfiles patterns=%r, include=%r '
1129 'exclude=%r, default=%r, rev=%r>',
1129 'exclude=%r, default=%r, rev=%r>',
1130 pats, inc, exc, default, rev))
1130 pats, inc, exc, default, rev))
1131
1131
1132 @predicate('file(pattern)', safe=True)
1132 @predicate('file(pattern)', safe=True)
1133 def hasfile(repo, subset, x):
1133 def hasfile(repo, subset, x):
1134 """Changesets affecting files matched by pattern.
1134 """Changesets affecting files matched by pattern.
1135
1135
1136 For a faster but less accurate result, consider using ``filelog()``
1136 For a faster but less accurate result, consider using ``filelog()``
1137 instead.
1137 instead.
1138
1138
1139 This predicate uses ``glob:`` as the default kind of pattern.
1139 This predicate uses ``glob:`` as the default kind of pattern.
1140 """
1140 """
1141 # i18n: "file" is a keyword
1141 # i18n: "file" is a keyword
1142 pat = getstring(x, _("file requires a pattern"))
1142 pat = getstring(x, _("file requires a pattern"))
1143 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1143 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1144
1144
1145 @predicate('head()', safe=True)
1145 @predicate('head()', safe=True)
1146 def head(repo, subset, x):
1146 def head(repo, subset, x):
1147 """Changeset is a named branch head.
1147 """Changeset is a named branch head.
1148 """
1148 """
1149 # i18n: "head" is a keyword
1149 # i18n: "head" is a keyword
1150 getargs(x, 0, 0, _("head takes no arguments"))
1150 getargs(x, 0, 0, _("head takes no arguments"))
1151 hs = set()
1151 hs = set()
1152 cl = repo.changelog
1152 cl = repo.changelog
1153 for b, ls in repo.branchmap().iteritems():
1153 for b, ls in repo.branchmap().iteritems():
1154 hs.update(cl.rev(h) for h in ls)
1154 hs.update(cl.rev(h) for h in ls)
1155 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
1155 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
1156 # This does not break because of other fullreposet misbehavior.
1156 # This does not break because of other fullreposet misbehavior.
1157 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
1157 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
1158 # necessary to ensure we preserve the order in subset.
1158 # necessary to ensure we preserve the order in subset.
1159 return baseset(hs) & subset
1159 return baseset(hs) & subset
1160
1160
1161 @predicate('heads(set)', safe=True)
1161 @predicate('heads(set)', safe=True)
1162 def heads(repo, subset, x):
1162 def heads(repo, subset, x):
1163 """Members of set with no children in set.
1163 """Members of set with no children in set.
1164 """
1164 """
1165 s = getset(repo, subset, x)
1165 s = getset(repo, subset, x)
1166 ps = parents(repo, subset, x)
1166 ps = parents(repo, subset, x)
1167 return s - ps
1167 return s - ps
1168
1168
1169 @predicate('hidden()', safe=True)
1169 @predicate('hidden()', safe=True)
1170 def hidden(repo, subset, x):
1170 def hidden(repo, subset, x):
1171 """Hidden changesets.
1171 """Hidden changesets.
1172 """
1172 """
1173 # i18n: "hidden" is a keyword
1173 # i18n: "hidden" is a keyword
1174 getargs(x, 0, 0, _("hidden takes no arguments"))
1174 getargs(x, 0, 0, _("hidden takes no arguments"))
1175 hiddenrevs = repoview.filterrevs(repo, 'visible')
1175 hiddenrevs = repoview.filterrevs(repo, 'visible')
1176 return subset & hiddenrevs
1176 return subset & hiddenrevs
1177
1177
1178 @predicate('keyword(string)', safe=True)
1178 @predicate('keyword(string)', safe=True)
1179 def keyword(repo, subset, x):
1179 def keyword(repo, subset, x):
1180 """Search commit message, user name, and names of changed files for
1180 """Search commit message, user name, and names of changed files for
1181 string. The match is case-insensitive.
1181 string. The match is case-insensitive.
1182 """
1182 """
1183 # i18n: "keyword" is a keyword
1183 # i18n: "keyword" is a keyword
1184 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1184 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1185
1185
1186 def matches(r):
1186 def matches(r):
1187 c = repo[r]
1187 c = repo[r]
1188 return any(kw in encoding.lower(t)
1188 return any(kw in encoding.lower(t)
1189 for t in c.files() + [c.user(), c.description()])
1189 for t in c.files() + [c.user(), c.description()])
1190
1190
1191 return subset.filter(matches, condrepr=('<keyword %r>', kw))
1191 return subset.filter(matches, condrepr=('<keyword %r>', kw))
1192
1192
1193 @predicate('limit(set[, n[, offset]])', safe=True)
1193 @predicate('limit(set[, n[, offset]])', safe=True)
1194 def limit(repo, subset, x):
1194 def limit(repo, subset, x):
1195 """First n members of set, defaulting to 1, starting from offset.
1195 """First n members of set, defaulting to 1, starting from offset.
1196 """
1196 """
1197 args = getargsdict(x, 'limit', 'set n offset')
1197 args = getargsdict(x, 'limit', 'set n offset')
1198 if 'set' not in args:
1198 if 'set' not in args:
1199 # i18n: "limit" is a keyword
1199 # i18n: "limit" is a keyword
1200 raise error.ParseError(_("limit requires one to three arguments"))
1200 raise error.ParseError(_("limit requires one to three arguments"))
1201 try:
1201 try:
1202 lim, ofs = 1, 0
1202 lim, ofs = 1, 0
1203 if 'n' in args:
1203 if 'n' in args:
1204 # i18n: "limit" is a keyword
1204 # i18n: "limit" is a keyword
1205 lim = int(getstring(args['n'], _("limit requires a number")))
1205 lim = int(getstring(args['n'], _("limit requires a number")))
1206 if 'offset' in args:
1206 if 'offset' in args:
1207 # i18n: "limit" is a keyword
1207 # i18n: "limit" is a keyword
1208 ofs = int(getstring(args['offset'], _("limit requires a number")))
1208 ofs = int(getstring(args['offset'], _("limit requires a number")))
1209 if ofs < 0:
1209 if ofs < 0:
1210 raise error.ParseError(_("negative offset"))
1210 raise error.ParseError(_("negative offset"))
1211 except (TypeError, ValueError):
1211 except (TypeError, ValueError):
1212 # i18n: "limit" is a keyword
1212 # i18n: "limit" is a keyword
1213 raise error.ParseError(_("limit expects a number"))
1213 raise error.ParseError(_("limit expects a number"))
1214 os = getset(repo, fullreposet(repo), args['set'])
1214 os = getset(repo, fullreposet(repo), args['set'])
1215 result = []
1215 result = []
1216 it = iter(os)
1216 it = iter(os)
1217 for x in xrange(ofs):
1217 for x in xrange(ofs):
1218 y = next(it, None)
1218 y = next(it, None)
1219 if y is None:
1219 if y is None:
1220 break
1220 break
1221 for x in xrange(lim):
1221 for x in xrange(lim):
1222 y = next(it, None)
1222 y = next(it, None)
1223 if y is None:
1223 if y is None:
1224 break
1224 break
1225 elif y in subset:
1225 elif y in subset:
1226 result.append(y)
1226 result.append(y)
1227 return baseset(result, datarepr=('<limit n=%d, offset=%d, %r, %r>',
1227 return baseset(result, datarepr=('<limit n=%d, offset=%d, %r, %r>',
1228 lim, ofs, subset, os))
1228 lim, ofs, subset, os))
1229
1229
1230 @predicate('last(set, [n])', safe=True)
1230 @predicate('last(set, [n])', safe=True)
1231 def last(repo, subset, x):
1231 def last(repo, subset, x):
1232 """Last n members of set, defaulting to 1.
1232 """Last n members of set, defaulting to 1.
1233 """
1233 """
1234 # i18n: "last" is a keyword
1234 # i18n: "last" is a keyword
1235 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1235 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1236 try:
1236 try:
1237 lim = 1
1237 lim = 1
1238 if len(l) == 2:
1238 if len(l) == 2:
1239 # i18n: "last" is a keyword
1239 # i18n: "last" is a keyword
1240 lim = int(getstring(l[1], _("last requires a number")))
1240 lim = int(getstring(l[1], _("last requires a number")))
1241 except (TypeError, ValueError):
1241 except (TypeError, ValueError):
1242 # i18n: "last" is a keyword
1242 # i18n: "last" is a keyword
1243 raise error.ParseError(_("last expects a number"))
1243 raise error.ParseError(_("last expects a number"))
1244 os = getset(repo, fullreposet(repo), l[0])
1244 os = getset(repo, fullreposet(repo), l[0])
1245 os.reverse()
1245 os.reverse()
1246 result = []
1246 result = []
1247 it = iter(os)
1247 it = iter(os)
1248 for x in xrange(lim):
1248 for x in xrange(lim):
1249 y = next(it, None)
1249 y = next(it, None)
1250 if y is None:
1250 if y is None:
1251 break
1251 break
1252 elif y in subset:
1252 elif y in subset:
1253 result.append(y)
1253 result.append(y)
1254 return baseset(result, datarepr=('<last n=%d, %r, %r>', lim, subset, os))
1254 return baseset(result, datarepr=('<last n=%d, %r, %r>', lim, subset, os))
1255
1255
1256 @predicate('max(set)', safe=True)
1256 @predicate('max(set)', safe=True)
1257 def maxrev(repo, subset, x):
1257 def maxrev(repo, subset, x):
1258 """Changeset with highest revision number in set.
1258 """Changeset with highest revision number in set.
1259 """
1259 """
1260 os = getset(repo, fullreposet(repo), x)
1260 os = getset(repo, fullreposet(repo), x)
1261 try:
1261 try:
1262 m = os.max()
1262 m = os.max()
1263 if m in subset:
1263 if m in subset:
1264 return baseset([m], datarepr=('<max %r, %r>', subset, os))
1264 return baseset([m], datarepr=('<max %r, %r>', subset, os))
1265 except ValueError:
1265 except ValueError:
1266 # os.max() throws a ValueError when the collection is empty.
1266 # os.max() throws a ValueError when the collection is empty.
1267 # Same as python's max().
1267 # Same as python's max().
1268 pass
1268 pass
1269 return baseset(datarepr=('<max %r, %r>', subset, os))
1269 return baseset(datarepr=('<max %r, %r>', subset, os))
1270
1270
1271 @predicate('merge()', safe=True)
1271 @predicate('merge()', safe=True)
1272 def merge(repo, subset, x):
1272 def merge(repo, subset, x):
1273 """Changeset is a merge changeset.
1273 """Changeset is a merge changeset.
1274 """
1274 """
1275 # i18n: "merge" is a keyword
1275 # i18n: "merge" is a keyword
1276 getargs(x, 0, 0, _("merge takes no arguments"))
1276 getargs(x, 0, 0, _("merge takes no arguments"))
1277 cl = repo.changelog
1277 cl = repo.changelog
1278 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1,
1278 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1,
1279 condrepr='<merge>')
1279 condrepr='<merge>')
1280
1280
1281 @predicate('branchpoint()', safe=True)
1281 @predicate('branchpoint()', safe=True)
1282 def branchpoint(repo, subset, x):
1282 def branchpoint(repo, subset, x):
1283 """Changesets with more than one child.
1283 """Changesets with more than one child.
1284 """
1284 """
1285 # i18n: "branchpoint" is a keyword
1285 # i18n: "branchpoint" is a keyword
1286 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1286 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1287 cl = repo.changelog
1287 cl = repo.changelog
1288 if not subset:
1288 if not subset:
1289 return baseset()
1289 return baseset()
1290 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1290 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1291 # (and if it is not, it should.)
1291 # (and if it is not, it should.)
1292 baserev = min(subset)
1292 baserev = min(subset)
1293 parentscount = [0]*(len(repo) - baserev)
1293 parentscount = [0]*(len(repo) - baserev)
1294 for r in cl.revs(start=baserev + 1):
1294 for r in cl.revs(start=baserev + 1):
1295 for p in cl.parentrevs(r):
1295 for p in cl.parentrevs(r):
1296 if p >= baserev:
1296 if p >= baserev:
1297 parentscount[p - baserev] += 1
1297 parentscount[p - baserev] += 1
1298 return subset.filter(lambda r: parentscount[r - baserev] > 1,
1298 return subset.filter(lambda r: parentscount[r - baserev] > 1,
1299 condrepr='<branchpoint>')
1299 condrepr='<branchpoint>')
1300
1300
1301 @predicate('min(set)', safe=True)
1301 @predicate('min(set)', safe=True)
1302 def minrev(repo, subset, x):
1302 def minrev(repo, subset, x):
1303 """Changeset with lowest revision number in set.
1303 """Changeset with lowest revision number in set.
1304 """
1304 """
1305 os = getset(repo, fullreposet(repo), x)
1305 os = getset(repo, fullreposet(repo), x)
1306 try:
1306 try:
1307 m = os.min()
1307 m = os.min()
1308 if m in subset:
1308 if m in subset:
1309 return baseset([m], datarepr=('<min %r, %r>', subset, os))
1309 return baseset([m], datarepr=('<min %r, %r>', subset, os))
1310 except ValueError:
1310 except ValueError:
1311 # os.min() throws a ValueError when the collection is empty.
1311 # os.min() throws a ValueError when the collection is empty.
1312 # Same as python's min().
1312 # Same as python's min().
1313 pass
1313 pass
1314 return baseset(datarepr=('<min %r, %r>', subset, os))
1314 return baseset(datarepr=('<min %r, %r>', subset, os))
1315
1315
1316 @predicate('modifies(pattern)', safe=True)
1316 @predicate('modifies(pattern)', safe=True)
1317 def modifies(repo, subset, x):
1317 def modifies(repo, subset, x):
1318 """Changesets modifying files matched by pattern.
1318 """Changesets modifying files matched by pattern.
1319
1319
1320 The pattern without explicit kind like ``glob:`` is expected to be
1320 The pattern without explicit kind like ``glob:`` is expected to be
1321 relative to the current directory and match against a file or a
1321 relative to the current directory and match against a file or a
1322 directory.
1322 directory.
1323 """
1323 """
1324 # i18n: "modifies" is a keyword
1324 # i18n: "modifies" is a keyword
1325 pat = getstring(x, _("modifies requires a pattern"))
1325 pat = getstring(x, _("modifies requires a pattern"))
1326 return checkstatus(repo, subset, pat, 0)
1326 return checkstatus(repo, subset, pat, 0)
1327
1327
1328 @predicate('named(namespace)')
1328 @predicate('named(namespace)')
1329 def named(repo, subset, x):
1329 def named(repo, subset, x):
1330 """The changesets in a given namespace.
1330 """The changesets in a given namespace.
1331
1331
1332 If `namespace` starts with `re:`, the remainder of the string is treated as
1332 If `namespace` starts with `re:`, the remainder of the string is treated as
1333 a regular expression. To match a namespace that actually starts with `re:`,
1333 a regular expression. To match a namespace that actually starts with `re:`,
1334 use the prefix `literal:`.
1334 use the prefix `literal:`.
1335 """
1335 """
1336 # i18n: "named" is a keyword
1336 # i18n: "named" is a keyword
1337 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1337 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1338
1338
1339 ns = getstring(args[0],
1339 ns = getstring(args[0],
1340 # i18n: "named" is a keyword
1340 # i18n: "named" is a keyword
1341 _('the argument to named must be a string'))
1341 _('the argument to named must be a string'))
1342 kind, pattern, matcher = util.stringmatcher(ns)
1342 kind, pattern, matcher = util.stringmatcher(ns)
1343 namespaces = set()
1343 namespaces = set()
1344 if kind == 'literal':
1344 if kind == 'literal':
1345 if pattern not in repo.names:
1345 if pattern not in repo.names:
1346 raise error.RepoLookupError(_("namespace '%s' does not exist")
1346 raise error.RepoLookupError(_("namespace '%s' does not exist")
1347 % ns)
1347 % ns)
1348 namespaces.add(repo.names[pattern])
1348 namespaces.add(repo.names[pattern])
1349 else:
1349 else:
1350 for name, ns in repo.names.iteritems():
1350 for name, ns in repo.names.iteritems():
1351 if matcher(name):
1351 if matcher(name):
1352 namespaces.add(ns)
1352 namespaces.add(ns)
1353 if not namespaces:
1353 if not namespaces:
1354 raise error.RepoLookupError(_("no namespace exists"
1354 raise error.RepoLookupError(_("no namespace exists"
1355 " that match '%s'") % pattern)
1355 " that match '%s'") % pattern)
1356
1356
1357 names = set()
1357 names = set()
1358 for ns in namespaces:
1358 for ns in namespaces:
1359 for name in ns.listnames(repo):
1359 for name in ns.listnames(repo):
1360 if name not in ns.deprecated:
1360 if name not in ns.deprecated:
1361 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1361 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1362
1362
1363 names -= set([node.nullrev])
1363 names -= set([node.nullrev])
1364 return subset & names
1364 return subset & names
1365
1365
1366 @predicate('id(string)', safe=True)
1366 @predicate('id(string)', safe=True)
1367 def node_(repo, subset, x):
1367 def node_(repo, subset, x):
1368 """Revision non-ambiguously specified by the given hex string prefix.
1368 """Revision non-ambiguously specified by the given hex string prefix.
1369 """
1369 """
1370 # i18n: "id" is a keyword
1370 # i18n: "id" is a keyword
1371 l = getargs(x, 1, 1, _("id requires one argument"))
1371 l = getargs(x, 1, 1, _("id requires one argument"))
1372 # i18n: "id" is a keyword
1372 # i18n: "id" is a keyword
1373 n = getstring(l[0], _("id requires a string"))
1373 n = getstring(l[0], _("id requires a string"))
1374 if len(n) == 40:
1374 if len(n) == 40:
1375 try:
1375 try:
1376 rn = repo.changelog.rev(node.bin(n))
1376 rn = repo.changelog.rev(node.bin(n))
1377 except (LookupError, TypeError):
1377 except (LookupError, TypeError):
1378 rn = None
1378 rn = None
1379 else:
1379 else:
1380 rn = None
1380 rn = None
1381 pm = repo.changelog._partialmatch(n)
1381 pm = repo.changelog._partialmatch(n)
1382 if pm is not None:
1382 if pm is not None:
1383 rn = repo.changelog.rev(pm)
1383 rn = repo.changelog.rev(pm)
1384
1384
1385 if rn is None:
1385 if rn is None:
1386 return baseset()
1386 return baseset()
1387 result = baseset([rn])
1387 result = baseset([rn])
1388 return result & subset
1388 return result & subset
1389
1389
1390 @predicate('obsolete()', safe=True)
1390 @predicate('obsolete()', safe=True)
1391 def obsolete(repo, subset, x):
1391 def obsolete(repo, subset, x):
1392 """Mutable changeset with a newer version."""
1392 """Mutable changeset with a newer version."""
1393 # i18n: "obsolete" is a keyword
1393 # i18n: "obsolete" is a keyword
1394 getargs(x, 0, 0, _("obsolete takes no arguments"))
1394 getargs(x, 0, 0, _("obsolete takes no arguments"))
1395 obsoletes = obsmod.getrevs(repo, 'obsolete')
1395 obsoletes = obsmod.getrevs(repo, 'obsolete')
1396 return subset & obsoletes
1396 return subset & obsoletes
1397
1397
1398 @predicate('only(set, [set])', safe=True)
1398 @predicate('only(set, [set])', safe=True)
1399 def only(repo, subset, x):
1399 def only(repo, subset, x):
1400 """Changesets that are ancestors of the first set that are not ancestors
1400 """Changesets that are ancestors of the first set that are not ancestors
1401 of any other head in the repo. If a second set is specified, the result
1401 of any other head in the repo. If a second set is specified, the result
1402 is ancestors of the first set that are not ancestors of the second set
1402 is ancestors of the first set that are not ancestors of the second set
1403 (i.e. ::<set1> - ::<set2>).
1403 (i.e. ::<set1> - ::<set2>).
1404 """
1404 """
1405 cl = repo.changelog
1405 cl = repo.changelog
1406 # i18n: "only" is a keyword
1406 # i18n: "only" is a keyword
1407 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1407 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1408 include = getset(repo, fullreposet(repo), args[0])
1408 include = getset(repo, fullreposet(repo), args[0])
1409 if len(args) == 1:
1409 if len(args) == 1:
1410 if not include:
1410 if not include:
1411 return baseset()
1411 return baseset()
1412
1412
1413 descendants = set(_revdescendants(repo, include, False))
1413 descendants = set(_revdescendants(repo, include, False))
1414 exclude = [rev for rev in cl.headrevs()
1414 exclude = [rev for rev in cl.headrevs()
1415 if not rev in descendants and not rev in include]
1415 if not rev in descendants and not rev in include]
1416 else:
1416 else:
1417 exclude = getset(repo, fullreposet(repo), args[1])
1417 exclude = getset(repo, fullreposet(repo), args[1])
1418
1418
1419 results = set(cl.findmissingrevs(common=exclude, heads=include))
1419 results = set(cl.findmissingrevs(common=exclude, heads=include))
1420 # XXX we should turn this into a baseset instead of a set, smartset may do
1420 # XXX we should turn this into a baseset instead of a set, smartset may do
1421 # some optimisations from the fact this is a baseset.
1421 # some optimisations from the fact this is a baseset.
1422 return subset & results
1422 return subset & results
1423
1423
1424 @predicate('origin([set])', safe=True)
1424 @predicate('origin([set])', safe=True)
1425 def origin(repo, subset, x):
1425 def origin(repo, subset, x):
1426 """
1426 """
1427 Changesets that were specified as a source for the grafts, transplants or
1427 Changesets that were specified as a source for the grafts, transplants or
1428 rebases that created the given revisions. Omitting the optional set is the
1428 rebases that created the given revisions. Omitting the optional set is the
1429 same as passing all(). If a changeset created by these operations is itself
1429 same as passing all(). If a changeset created by these operations is itself
1430 specified as a source for one of these operations, only the source changeset
1430 specified as a source for one of these operations, only the source changeset
1431 for the first operation is selected.
1431 for the first operation is selected.
1432 """
1432 """
1433 if x is not None:
1433 if x is not None:
1434 dests = getset(repo, fullreposet(repo), x)
1434 dests = getset(repo, fullreposet(repo), x)
1435 else:
1435 else:
1436 dests = fullreposet(repo)
1436 dests = fullreposet(repo)
1437
1437
1438 def _firstsrc(rev):
1438 def _firstsrc(rev):
1439 src = _getrevsource(repo, rev)
1439 src = _getrevsource(repo, rev)
1440 if src is None:
1440 if src is None:
1441 return None
1441 return None
1442
1442
1443 while True:
1443 while True:
1444 prev = _getrevsource(repo, src)
1444 prev = _getrevsource(repo, src)
1445
1445
1446 if prev is None:
1446 if prev is None:
1447 return src
1447 return src
1448 src = prev
1448 src = prev
1449
1449
1450 o = set([_firstsrc(r) for r in dests])
1450 o = set([_firstsrc(r) for r in dests])
1451 o -= set([None])
1451 o -= set([None])
1452 # XXX we should turn this into a baseset instead of a set, smartset may do
1452 # XXX we should turn this into a baseset instead of a set, smartset may do
1453 # some optimisations from the fact this is a baseset.
1453 # some optimisations from the fact this is a baseset.
1454 return subset & o
1454 return subset & o
1455
1455
1456 @predicate('outgoing([path])', safe=True)
1456 @predicate('outgoing([path])', safe=True)
1457 def outgoing(repo, subset, x):
1457 def outgoing(repo, subset, x):
1458 """Changesets not found in the specified destination repository, or the
1458 """Changesets not found in the specified destination repository, or the
1459 default push location.
1459 default push location.
1460 """
1460 """
1461 # Avoid cycles.
1461 # Avoid cycles.
1462 from . import (
1462 from . import (
1463 discovery,
1463 discovery,
1464 hg,
1464 hg,
1465 )
1465 )
1466 # i18n: "outgoing" is a keyword
1466 # i18n: "outgoing" is a keyword
1467 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1467 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1468 # i18n: "outgoing" is a keyword
1468 # i18n: "outgoing" is a keyword
1469 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1469 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1470 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1470 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1471 dest, branches = hg.parseurl(dest)
1471 dest, branches = hg.parseurl(dest)
1472 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1472 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1473 if revs:
1473 if revs:
1474 revs = [repo.lookup(rev) for rev in revs]
1474 revs = [repo.lookup(rev) for rev in revs]
1475 other = hg.peer(repo, {}, dest)
1475 other = hg.peer(repo, {}, dest)
1476 repo.ui.pushbuffer()
1476 repo.ui.pushbuffer()
1477 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1477 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1478 repo.ui.popbuffer()
1478 repo.ui.popbuffer()
1479 cl = repo.changelog
1479 cl = repo.changelog
1480 o = set([cl.rev(r) for r in outgoing.missing])
1480 o = set([cl.rev(r) for r in outgoing.missing])
1481 return subset & o
1481 return subset & o
1482
1482
1483 @predicate('p1([set])', safe=True)
1483 @predicate('p1([set])', safe=True)
1484 def p1(repo, subset, x):
1484 def p1(repo, subset, x):
1485 """First parent of changesets in set, or the working directory.
1485 """First parent of changesets in set, or the working directory.
1486 """
1486 """
1487 if x is None:
1487 if x is None:
1488 p = repo[x].p1().rev()
1488 p = repo[x].p1().rev()
1489 if p >= 0:
1489 if p >= 0:
1490 return subset & baseset([p])
1490 return subset & baseset([p])
1491 return baseset()
1491 return baseset()
1492
1492
1493 ps = set()
1493 ps = set()
1494 cl = repo.changelog
1494 cl = repo.changelog
1495 for r in getset(repo, fullreposet(repo), x):
1495 for r in getset(repo, fullreposet(repo), x):
1496 ps.add(cl.parentrevs(r)[0])
1496 ps.add(cl.parentrevs(r)[0])
1497 ps -= set([node.nullrev])
1497 ps -= set([node.nullrev])
1498 # XXX we should turn this into a baseset instead of a set, smartset may do
1498 # XXX we should turn this into a baseset instead of a set, smartset may do
1499 # some optimisations from the fact this is a baseset.
1499 # some optimisations from the fact this is a baseset.
1500 return subset & ps
1500 return subset & ps
1501
1501
1502 @predicate('p2([set])', safe=True)
1502 @predicate('p2([set])', safe=True)
1503 def p2(repo, subset, x):
1503 def p2(repo, subset, x):
1504 """Second parent of changesets in set, or the working directory.
1504 """Second parent of changesets in set, or the working directory.
1505 """
1505 """
1506 if x is None:
1506 if x is None:
1507 ps = repo[x].parents()
1507 ps = repo[x].parents()
1508 try:
1508 try:
1509 p = ps[1].rev()
1509 p = ps[1].rev()
1510 if p >= 0:
1510 if p >= 0:
1511 return subset & baseset([p])
1511 return subset & baseset([p])
1512 return baseset()
1512 return baseset()
1513 except IndexError:
1513 except IndexError:
1514 return baseset()
1514 return baseset()
1515
1515
1516 ps = set()
1516 ps = set()
1517 cl = repo.changelog
1517 cl = repo.changelog
1518 for r in getset(repo, fullreposet(repo), x):
1518 for r in getset(repo, fullreposet(repo), x):
1519 ps.add(cl.parentrevs(r)[1])
1519 ps.add(cl.parentrevs(r)[1])
1520 ps -= set([node.nullrev])
1520 ps -= set([node.nullrev])
1521 # XXX we should turn this into a baseset instead of a set, smartset may do
1521 # XXX we should turn this into a baseset instead of a set, smartset may do
1522 # some optimisations from the fact this is a baseset.
1522 # some optimisations from the fact this is a baseset.
1523 return subset & ps
1523 return subset & ps
1524
1524
1525 @predicate('parents([set])', safe=True)
1525 @predicate('parents([set])', safe=True)
1526 def parents(repo, subset, x):
1526 def parents(repo, subset, x):
1527 """
1527 """
1528 The set of all parents for all changesets in set, or the working directory.
1528 The set of all parents for all changesets in set, or the working directory.
1529 """
1529 """
1530 if x is None:
1530 if x is None:
1531 ps = set(p.rev() for p in repo[x].parents())
1531 ps = set(p.rev() for p in repo[x].parents())
1532 else:
1532 else:
1533 ps = set()
1533 ps = set()
1534 cl = repo.changelog
1534 cl = repo.changelog
1535 up = ps.update
1535 up = ps.update
1536 parentrevs = cl.parentrevs
1536 parentrevs = cl.parentrevs
1537 for r in getset(repo, fullreposet(repo), x):
1537 for r in getset(repo, fullreposet(repo), x):
1538 if r == node.wdirrev:
1538 if r == node.wdirrev:
1539 up(p.rev() for p in repo[r].parents())
1539 up(p.rev() for p in repo[r].parents())
1540 else:
1540 else:
1541 up(parentrevs(r))
1541 up(parentrevs(r))
1542 ps -= set([node.nullrev])
1542 ps -= set([node.nullrev])
1543 return subset & ps
1543 return subset & ps
1544
1544
1545 def _phase(repo, subset, target):
1545 def _phase(repo, subset, target):
1546 """helper to select all rev in phase <target>"""
1546 """helper to select all rev in phase <target>"""
1547 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1547 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1548 if repo._phasecache._phasesets:
1548 if repo._phasecache._phasesets:
1549 s = repo._phasecache._phasesets[target] - repo.changelog.filteredrevs
1549 s = repo._phasecache._phasesets[target] - repo.changelog.filteredrevs
1550 s = baseset(s)
1550 s = baseset(s)
1551 s.sort() # set are non ordered, so we enforce ascending
1551 s.sort() # set are non ordered, so we enforce ascending
1552 return subset & s
1552 return subset & s
1553 else:
1553 else:
1554 phase = repo._phasecache.phase
1554 phase = repo._phasecache.phase
1555 condition = lambda r: phase(repo, r) == target
1555 condition = lambda r: phase(repo, r) == target
1556 return subset.filter(condition, condrepr=('<phase %r>', target),
1556 return subset.filter(condition, condrepr=('<phase %r>', target),
1557 cache=False)
1557 cache=False)
1558
1558
1559 @predicate('draft()', safe=True)
1559 @predicate('draft()', safe=True)
1560 def draft(repo, subset, x):
1560 def draft(repo, subset, x):
1561 """Changeset in draft phase."""
1561 """Changeset in draft phase."""
1562 # i18n: "draft" is a keyword
1562 # i18n: "draft" is a keyword
1563 getargs(x, 0, 0, _("draft takes no arguments"))
1563 getargs(x, 0, 0, _("draft takes no arguments"))
1564 target = phases.draft
1564 target = phases.draft
1565 return _phase(repo, subset, target)
1565 return _phase(repo, subset, target)
1566
1566
1567 @predicate('secret()', safe=True)
1567 @predicate('secret()', safe=True)
1568 def secret(repo, subset, x):
1568 def secret(repo, subset, x):
1569 """Changeset in secret phase."""
1569 """Changeset in secret phase."""
1570 # i18n: "secret" is a keyword
1570 # i18n: "secret" is a keyword
1571 getargs(x, 0, 0, _("secret takes no arguments"))
1571 getargs(x, 0, 0, _("secret takes no arguments"))
1572 target = phases.secret
1572 target = phases.secret
1573 return _phase(repo, subset, target)
1573 return _phase(repo, subset, target)
1574
1574
1575 def parentspec(repo, subset, x, n):
1575 def parentspec(repo, subset, x, n):
1576 """``set^0``
1576 """``set^0``
1577 The set.
1577 The set.
1578 ``set^1`` (or ``set^``), ``set^2``
1578 ``set^1`` (or ``set^``), ``set^2``
1579 First or second parent, respectively, of all changesets in set.
1579 First or second parent, respectively, of all changesets in set.
1580 """
1580 """
1581 try:
1581 try:
1582 n = int(n[1])
1582 n = int(n[1])
1583 if n not in (0, 1, 2):
1583 if n not in (0, 1, 2):
1584 raise ValueError
1584 raise ValueError
1585 except (TypeError, ValueError):
1585 except (TypeError, ValueError):
1586 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1586 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1587 ps = set()
1587 ps = set()
1588 cl = repo.changelog
1588 cl = repo.changelog
1589 for r in getset(repo, fullreposet(repo), x):
1589 for r in getset(repo, fullreposet(repo), x):
1590 if n == 0:
1590 if n == 0:
1591 ps.add(r)
1591 ps.add(r)
1592 elif n == 1:
1592 elif n == 1:
1593 ps.add(cl.parentrevs(r)[0])
1593 ps.add(cl.parentrevs(r)[0])
1594 elif n == 2:
1594 elif n == 2:
1595 parents = cl.parentrevs(r)
1595 parents = cl.parentrevs(r)
1596 if len(parents) > 1:
1596 if len(parents) > 1:
1597 ps.add(parents[1])
1597 ps.add(parents[1])
1598 return subset & ps
1598 return subset & ps
1599
1599
1600 @predicate('present(set)', safe=True)
1600 @predicate('present(set)', safe=True)
1601 def present(repo, subset, x):
1601 def present(repo, subset, x):
1602 """An empty set, if any revision in set isn't found; otherwise,
1602 """An empty set, if any revision in set isn't found; otherwise,
1603 all revisions in set.
1603 all revisions in set.
1604
1604
1605 If any of specified revisions is not present in the local repository,
1605 If any of specified revisions is not present in the local repository,
1606 the query is normally aborted. But this predicate allows the query
1606 the query is normally aborted. But this predicate allows the query
1607 to continue even in such cases.
1607 to continue even in such cases.
1608 """
1608 """
1609 try:
1609 try:
1610 return getset(repo, subset, x)
1610 return getset(repo, subset, x)
1611 except error.RepoLookupError:
1611 except error.RepoLookupError:
1612 return baseset()
1612 return baseset()
1613
1613
1614 # for internal use
1614 # for internal use
1615 @predicate('_notpublic', safe=True)
1615 @predicate('_notpublic', safe=True)
1616 def _notpublic(repo, subset, x):
1616 def _notpublic(repo, subset, x):
1617 getargs(x, 0, 0, "_notpublic takes no arguments")
1617 getargs(x, 0, 0, "_notpublic takes no arguments")
1618 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1618 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1619 if repo._phasecache._phasesets:
1619 if repo._phasecache._phasesets:
1620 s = set()
1620 s = set()
1621 for u in repo._phasecache._phasesets[1:]:
1621 for u in repo._phasecache._phasesets[1:]:
1622 s.update(u)
1622 s.update(u)
1623 s = baseset(s - repo.changelog.filteredrevs)
1623 s = baseset(s - repo.changelog.filteredrevs)
1624 s.sort()
1624 s.sort()
1625 return subset & s
1625 return subset & s
1626 else:
1626 else:
1627 phase = repo._phasecache.phase
1627 phase = repo._phasecache.phase
1628 target = phases.public
1628 target = phases.public
1629 condition = lambda r: phase(repo, r) != target
1629 condition = lambda r: phase(repo, r) != target
1630 return subset.filter(condition, condrepr=('<phase %r>', target),
1630 return subset.filter(condition, condrepr=('<phase %r>', target),
1631 cache=False)
1631 cache=False)
1632
1632
1633 @predicate('public()', safe=True)
1633 @predicate('public()', safe=True)
1634 def public(repo, subset, x):
1634 def public(repo, subset, x):
1635 """Changeset in public phase."""
1635 """Changeset in public phase."""
1636 # i18n: "public" is a keyword
1636 # i18n: "public" is a keyword
1637 getargs(x, 0, 0, _("public takes no arguments"))
1637 getargs(x, 0, 0, _("public takes no arguments"))
1638 phase = repo._phasecache.phase
1638 phase = repo._phasecache.phase
1639 target = phases.public
1639 target = phases.public
1640 condition = lambda r: phase(repo, r) == target
1640 condition = lambda r: phase(repo, r) == target
1641 return subset.filter(condition, condrepr=('<phase %r>', target),
1641 return subset.filter(condition, condrepr=('<phase %r>', target),
1642 cache=False)
1642 cache=False)
1643
1643
1644 @predicate('remote([id [,path]])', safe=True)
1644 @predicate('remote([id [,path]])', safe=True)
1645 def remote(repo, subset, x):
1645 def remote(repo, subset, x):
1646 """Local revision that corresponds to the given identifier in a
1646 """Local revision that corresponds to the given identifier in a
1647 remote repository, if present. Here, the '.' identifier is a
1647 remote repository, if present. Here, the '.' identifier is a
1648 synonym for the current local branch.
1648 synonym for the current local branch.
1649 """
1649 """
1650
1650
1651 from . import hg # avoid start-up nasties
1651 from . import hg # avoid start-up nasties
1652 # i18n: "remote" is a keyword
1652 # i18n: "remote" is a keyword
1653 l = getargs(x, 0, 2, _("remote takes zero, one, or two arguments"))
1653 l = getargs(x, 0, 2, _("remote takes zero, one, or two arguments"))
1654
1654
1655 q = '.'
1655 q = '.'
1656 if len(l) > 0:
1656 if len(l) > 0:
1657 # i18n: "remote" is a keyword
1657 # i18n: "remote" is a keyword
1658 q = getstring(l[0], _("remote requires a string id"))
1658 q = getstring(l[0], _("remote requires a string id"))
1659 if q == '.':
1659 if q == '.':
1660 q = repo['.'].branch()
1660 q = repo['.'].branch()
1661
1661
1662 dest = ''
1662 dest = ''
1663 if len(l) > 1:
1663 if len(l) > 1:
1664 # i18n: "remote" is a keyword
1664 # i18n: "remote" is a keyword
1665 dest = getstring(l[1], _("remote requires a repository path"))
1665 dest = getstring(l[1], _("remote requires a repository path"))
1666 dest = repo.ui.expandpath(dest or 'default')
1666 dest = repo.ui.expandpath(dest or 'default')
1667 dest, branches = hg.parseurl(dest)
1667 dest, branches = hg.parseurl(dest)
1668 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1668 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1669 if revs:
1669 if revs:
1670 revs = [repo.lookup(rev) for rev in revs]
1670 revs = [repo.lookup(rev) for rev in revs]
1671 other = hg.peer(repo, {}, dest)
1671 other = hg.peer(repo, {}, dest)
1672 n = other.lookup(q)
1672 n = other.lookup(q)
1673 if n in repo:
1673 if n in repo:
1674 r = repo[n].rev()
1674 r = repo[n].rev()
1675 if r in subset:
1675 if r in subset:
1676 return baseset([r])
1676 return baseset([r])
1677 return baseset()
1677 return baseset()
1678
1678
1679 @predicate('removes(pattern)', safe=True)
1679 @predicate('removes(pattern)', safe=True)
1680 def removes(repo, subset, x):
1680 def removes(repo, subset, x):
1681 """Changesets which remove files matching pattern.
1681 """Changesets which remove files matching pattern.
1682
1682
1683 The pattern without explicit kind like ``glob:`` is expected to be
1683 The pattern without explicit kind like ``glob:`` is expected to be
1684 relative to the current directory and match against a file or a
1684 relative to the current directory and match against a file or a
1685 directory.
1685 directory.
1686 """
1686 """
1687 # i18n: "removes" is a keyword
1687 # i18n: "removes" is a keyword
1688 pat = getstring(x, _("removes requires a pattern"))
1688 pat = getstring(x, _("removes requires a pattern"))
1689 return checkstatus(repo, subset, pat, 2)
1689 return checkstatus(repo, subset, pat, 2)
1690
1690
1691 @predicate('rev(number)', safe=True)
1691 @predicate('rev(number)', safe=True)
1692 def rev(repo, subset, x):
1692 def rev(repo, subset, x):
1693 """Revision with the given numeric identifier.
1693 """Revision with the given numeric identifier.
1694 """
1694 """
1695 # i18n: "rev" is a keyword
1695 # i18n: "rev" is a keyword
1696 l = getargs(x, 1, 1, _("rev requires one argument"))
1696 l = getargs(x, 1, 1, _("rev requires one argument"))
1697 try:
1697 try:
1698 # i18n: "rev" is a keyword
1698 # i18n: "rev" is a keyword
1699 l = int(getstring(l[0], _("rev requires a number")))
1699 l = int(getstring(l[0], _("rev requires a number")))
1700 except (TypeError, ValueError):
1700 except (TypeError, ValueError):
1701 # i18n: "rev" is a keyword
1701 # i18n: "rev" is a keyword
1702 raise error.ParseError(_("rev expects a number"))
1702 raise error.ParseError(_("rev expects a number"))
1703 if l not in repo.changelog and l != node.nullrev:
1703 if l not in repo.changelog and l != node.nullrev:
1704 return baseset()
1704 return baseset()
1705 return subset & baseset([l])
1705 return subset & baseset([l])
1706
1706
1707 @predicate('matching(revision [, field])', safe=True)
1707 @predicate('matching(revision [, field])', safe=True)
1708 def matching(repo, subset, x):
1708 def matching(repo, subset, x):
1709 """Changesets in which a given set of fields match the set of fields in the
1709 """Changesets in which a given set of fields match the set of fields in the
1710 selected revision or set.
1710 selected revision or set.
1711
1711
1712 To match more than one field pass the list of fields to match separated
1712 To match more than one field pass the list of fields to match separated
1713 by spaces (e.g. ``author description``).
1713 by spaces (e.g. ``author description``).
1714
1714
1715 Valid fields are most regular revision fields and some special fields.
1715 Valid fields are most regular revision fields and some special fields.
1716
1716
1717 Regular revision fields are ``description``, ``author``, ``branch``,
1717 Regular revision fields are ``description``, ``author``, ``branch``,
1718 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1718 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1719 and ``diff``.
1719 and ``diff``.
1720 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1720 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1721 contents of the revision. Two revisions matching their ``diff`` will
1721 contents of the revision. Two revisions matching their ``diff`` will
1722 also match their ``files``.
1722 also match their ``files``.
1723
1723
1724 Special fields are ``summary`` and ``metadata``:
1724 Special fields are ``summary`` and ``metadata``:
1725 ``summary`` matches the first line of the description.
1725 ``summary`` matches the first line of the description.
1726 ``metadata`` is equivalent to matching ``description user date``
1726 ``metadata`` is equivalent to matching ``description user date``
1727 (i.e. it matches the main metadata fields).
1727 (i.e. it matches the main metadata fields).
1728
1728
1729 ``metadata`` is the default field which is used when no fields are
1729 ``metadata`` is the default field which is used when no fields are
1730 specified. You can match more than one field at a time.
1730 specified. You can match more than one field at a time.
1731 """
1731 """
1732 # i18n: "matching" is a keyword
1732 # i18n: "matching" is a keyword
1733 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1733 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1734
1734
1735 revs = getset(repo, fullreposet(repo), l[0])
1735 revs = getset(repo, fullreposet(repo), l[0])
1736
1736
1737 fieldlist = ['metadata']
1737 fieldlist = ['metadata']
1738 if len(l) > 1:
1738 if len(l) > 1:
1739 fieldlist = getstring(l[1],
1739 fieldlist = getstring(l[1],
1740 # i18n: "matching" is a keyword
1740 # i18n: "matching" is a keyword
1741 _("matching requires a string "
1741 _("matching requires a string "
1742 "as its second argument")).split()
1742 "as its second argument")).split()
1743
1743
1744 # Make sure that there are no repeated fields,
1744 # Make sure that there are no repeated fields,
1745 # expand the 'special' 'metadata' field type
1745 # expand the 'special' 'metadata' field type
1746 # and check the 'files' whenever we check the 'diff'
1746 # and check the 'files' whenever we check the 'diff'
1747 fields = []
1747 fields = []
1748 for field in fieldlist:
1748 for field in fieldlist:
1749 if field == 'metadata':
1749 if field == 'metadata':
1750 fields += ['user', 'description', 'date']
1750 fields += ['user', 'description', 'date']
1751 elif field == 'diff':
1751 elif field == 'diff':
1752 # a revision matching the diff must also match the files
1752 # a revision matching the diff must also match the files
1753 # since matching the diff is very costly, make sure to
1753 # since matching the diff is very costly, make sure to
1754 # also match the files first
1754 # also match the files first
1755 fields += ['files', 'diff']
1755 fields += ['files', 'diff']
1756 else:
1756 else:
1757 if field == 'author':
1757 if field == 'author':
1758 field = 'user'
1758 field = 'user'
1759 fields.append(field)
1759 fields.append(field)
1760 fields = set(fields)
1760 fields = set(fields)
1761 if 'summary' in fields and 'description' in fields:
1761 if 'summary' in fields and 'description' in fields:
1762 # If a revision matches its description it also matches its summary
1762 # If a revision matches its description it also matches its summary
1763 fields.discard('summary')
1763 fields.discard('summary')
1764
1764
1765 # We may want to match more than one field
1765 # We may want to match more than one field
1766 # Not all fields take the same amount of time to be matched
1766 # Not all fields take the same amount of time to be matched
1767 # Sort the selected fields in order of increasing matching cost
1767 # Sort the selected fields in order of increasing matching cost
1768 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1768 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1769 'files', 'description', 'substate', 'diff']
1769 'files', 'description', 'substate', 'diff']
1770 def fieldkeyfunc(f):
1770 def fieldkeyfunc(f):
1771 try:
1771 try:
1772 return fieldorder.index(f)
1772 return fieldorder.index(f)
1773 except ValueError:
1773 except ValueError:
1774 # assume an unknown field is very costly
1774 # assume an unknown field is very costly
1775 return len(fieldorder)
1775 return len(fieldorder)
1776 fields = list(fields)
1776 fields = list(fields)
1777 fields.sort(key=fieldkeyfunc)
1777 fields.sort(key=fieldkeyfunc)
1778
1778
1779 # Each field will be matched with its own "getfield" function
1779 # Each field will be matched with its own "getfield" function
1780 # which will be added to the getfieldfuncs array of functions
1780 # which will be added to the getfieldfuncs array of functions
1781 getfieldfuncs = []
1781 getfieldfuncs = []
1782 _funcs = {
1782 _funcs = {
1783 'user': lambda r: repo[r].user(),
1783 'user': lambda r: repo[r].user(),
1784 'branch': lambda r: repo[r].branch(),
1784 'branch': lambda r: repo[r].branch(),
1785 'date': lambda r: repo[r].date(),
1785 'date': lambda r: repo[r].date(),
1786 'description': lambda r: repo[r].description(),
1786 'description': lambda r: repo[r].description(),
1787 'files': lambda r: repo[r].files(),
1787 'files': lambda r: repo[r].files(),
1788 'parents': lambda r: repo[r].parents(),
1788 'parents': lambda r: repo[r].parents(),
1789 'phase': lambda r: repo[r].phase(),
1789 'phase': lambda r: repo[r].phase(),
1790 'substate': lambda r: repo[r].substate,
1790 'substate': lambda r: repo[r].substate,
1791 'summary': lambda r: repo[r].description().splitlines()[0],
1791 'summary': lambda r: repo[r].description().splitlines()[0],
1792 'diff': lambda r: list(repo[r].diff(git=True),)
1792 'diff': lambda r: list(repo[r].diff(git=True),)
1793 }
1793 }
1794 for info in fields:
1794 for info in fields:
1795 getfield = _funcs.get(info, None)
1795 getfield = _funcs.get(info, None)
1796 if getfield is None:
1796 if getfield is None:
1797 raise error.ParseError(
1797 raise error.ParseError(
1798 # i18n: "matching" is a keyword
1798 # i18n: "matching" is a keyword
1799 _("unexpected field name passed to matching: %s") % info)
1799 _("unexpected field name passed to matching: %s") % info)
1800 getfieldfuncs.append(getfield)
1800 getfieldfuncs.append(getfield)
1801 # convert the getfield array of functions into a "getinfo" function
1801 # convert the getfield array of functions into a "getinfo" function
1802 # which returns an array of field values (or a single value if there
1802 # which returns an array of field values (or a single value if there
1803 # is only one field to match)
1803 # is only one field to match)
1804 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1804 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1805
1805
1806 def matches(x):
1806 def matches(x):
1807 for rev in revs:
1807 for rev in revs:
1808 target = getinfo(rev)
1808 target = getinfo(rev)
1809 match = True
1809 match = True
1810 for n, f in enumerate(getfieldfuncs):
1810 for n, f in enumerate(getfieldfuncs):
1811 if target[n] != f(x):
1811 if target[n] != f(x):
1812 match = False
1812 match = False
1813 if match:
1813 if match:
1814 return True
1814 return True
1815 return False
1815 return False
1816
1816
1817 return subset.filter(matches, condrepr=('<matching%r %r>', fields, revs))
1817 return subset.filter(matches, condrepr=('<matching%r %r>', fields, revs))
1818
1818
1819 @predicate('reverse(set)', safe=True)
1819 @predicate('reverse(set)', safe=True)
1820 def reverse(repo, subset, x):
1820 def reverse(repo, subset, x):
1821 """Reverse order of set.
1821 """Reverse order of set.
1822 """
1822 """
1823 l = getset(repo, subset, x)
1823 l = getset(repo, subset, x)
1824 l.reverse()
1824 l.reverse()
1825 return l
1825 return l
1826
1826
1827 @predicate('roots(set)', safe=True)
1827 @predicate('roots(set)', safe=True)
1828 def roots(repo, subset, x):
1828 def roots(repo, subset, x):
1829 """Changesets in set with no parent changeset in set.
1829 """Changesets in set with no parent changeset in set.
1830 """
1830 """
1831 s = getset(repo, fullreposet(repo), x)
1831 s = getset(repo, fullreposet(repo), x)
1832 parents = repo.changelog.parentrevs
1832 parents = repo.changelog.parentrevs
1833 def filter(r):
1833 def filter(r):
1834 for p in parents(r):
1834 for p in parents(r):
1835 if 0 <= p and p in s:
1835 if 0 <= p and p in s:
1836 return False
1836 return False
1837 return True
1837 return True
1838 return subset & s.filter(filter, condrepr='<roots>')
1838 return subset & s.filter(filter, condrepr='<roots>')
1839
1839
1840 @predicate('sort(set[, [-]key...])', safe=True)
1840 @predicate('sort(set[, [-]key...])', safe=True)
1841 def sort(repo, subset, x):
1841 def sort(repo, subset, x):
1842 """Sort set by keys. The default sort order is ascending, specify a key
1842 """Sort set by keys. The default sort order is ascending, specify a key
1843 as ``-key`` to sort in descending order.
1843 as ``-key`` to sort in descending order.
1844
1844
1845 The keys can be:
1845 The keys can be:
1846
1846
1847 - ``rev`` for the revision number,
1847 - ``rev`` for the revision number,
1848 - ``branch`` for the branch name,
1848 - ``branch`` for the branch name,
1849 - ``desc`` for the commit message (description),
1849 - ``desc`` for the commit message (description),
1850 - ``user`` for user name (``author`` can be used as an alias),
1850 - ``user`` for user name (``author`` can be used as an alias),
1851 - ``date`` for the commit date
1851 - ``date`` for the commit date
1852 """
1852 """
1853 # i18n: "sort" is a keyword
1853 # i18n: "sort" is a keyword
1854 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1854 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1855 keys = "rev"
1855 keys = "rev"
1856 if len(l) == 2:
1856 if len(l) == 2:
1857 # i18n: "sort" is a keyword
1857 # i18n: "sort" is a keyword
1858 keys = getstring(l[1], _("sort spec must be a string"))
1858 keys = getstring(l[1], _("sort spec must be a string"))
1859
1859
1860 s = l[0]
1860 s = l[0]
1861 keys = keys.split()
1861 keys = keys.split()
1862 revs = getset(repo, subset, s)
1862 revs = getset(repo, subset, s)
1863 if keys == ["rev"]:
1863 if keys == ["rev"]:
1864 revs.sort()
1864 revs.sort()
1865 return revs
1865 return revs
1866 elif keys == ["-rev"]:
1866 elif keys == ["-rev"]:
1867 revs.sort(reverse=True)
1867 revs.sort(reverse=True)
1868 return revs
1868 return revs
1869 # sort() is guaranteed to be stable
1869 # sort() is guaranteed to be stable
1870 ctxs = [repo[r] for r in revs]
1870 ctxs = [repo[r] for r in revs]
1871 for k in reversed(keys):
1871 for k in reversed(keys):
1872 if k == 'rev':
1872 if k == 'rev':
1873 ctxs.sort(key=lambda c: c.rev())
1873 ctxs.sort(key=lambda c: c.rev())
1874 elif k == '-rev':
1874 elif k == '-rev':
1875 ctxs.sort(key=lambda c: c.rev(), reverse=True)
1875 ctxs.sort(key=lambda c: c.rev(), reverse=True)
1876 elif k == 'branch':
1876 elif k == 'branch':
1877 ctxs.sort(key=lambda c: c.branch())
1877 ctxs.sort(key=lambda c: c.branch())
1878 elif k == '-branch':
1878 elif k == '-branch':
1879 ctxs.sort(key=lambda c: c.branch(), reverse=True)
1879 ctxs.sort(key=lambda c: c.branch(), reverse=True)
1880 elif k == 'desc':
1880 elif k == 'desc':
1881 ctxs.sort(key=lambda c: c.description())
1881 ctxs.sort(key=lambda c: c.description())
1882 elif k == '-desc':
1882 elif k == '-desc':
1883 ctxs.sort(key=lambda c: c.description(), reverse=True)
1883 ctxs.sort(key=lambda c: c.description(), reverse=True)
1884 elif k in 'user author':
1884 elif k in 'user author':
1885 ctxs.sort(key=lambda c: c.user())
1885 ctxs.sort(key=lambda c: c.user())
1886 elif k in '-user -author':
1886 elif k in '-user -author':
1887 ctxs.sort(key=lambda c: c.user(), reverse=True)
1887 ctxs.sort(key=lambda c: c.user(), reverse=True)
1888 elif k == 'date':
1888 elif k == 'date':
1889 ctxs.sort(key=lambda c: c.date()[0])
1889 ctxs.sort(key=lambda c: c.date()[0])
1890 elif k == '-date':
1890 elif k == '-date':
1891 ctxs.sort(key=lambda c: c.date()[0], reverse=True)
1891 ctxs.sort(key=lambda c: c.date()[0], reverse=True)
1892 else:
1892 else:
1893 raise error.ParseError(_("unknown sort key %r") % k)
1893 raise error.ParseError(_("unknown sort key %r") % k)
1894 return baseset([c.rev() for c in ctxs])
1894 return baseset([c.rev() for c in ctxs])
1895
1895
1896 @predicate('subrepo([pattern])')
1896 @predicate('subrepo([pattern])')
1897 def subrepo(repo, subset, x):
1897 def subrepo(repo, subset, x):
1898 """Changesets that add, modify or remove the given subrepo. If no subrepo
1898 """Changesets that add, modify or remove the given subrepo. If no subrepo
1899 pattern is named, any subrepo changes are returned.
1899 pattern is named, any subrepo changes are returned.
1900 """
1900 """
1901 # i18n: "subrepo" is a keyword
1901 # i18n: "subrepo" is a keyword
1902 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
1902 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
1903 pat = None
1903 pat = None
1904 if len(args) != 0:
1904 if len(args) != 0:
1905 pat = getstring(args[0], _("subrepo requires a pattern"))
1905 pat = getstring(args[0], _("subrepo requires a pattern"))
1906
1906
1907 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
1907 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
1908
1908
1909 def submatches(names):
1909 def submatches(names):
1910 k, p, m = util.stringmatcher(pat)
1910 k, p, m = util.stringmatcher(pat)
1911 for name in names:
1911 for name in names:
1912 if m(name):
1912 if m(name):
1913 yield name
1913 yield name
1914
1914
1915 def matches(x):
1915 def matches(x):
1916 c = repo[x]
1916 c = repo[x]
1917 s = repo.status(c.p1().node(), c.node(), match=m)
1917 s = repo.status(c.p1().node(), c.node(), match=m)
1918
1918
1919 if pat is None:
1919 if pat is None:
1920 return s.added or s.modified or s.removed
1920 return s.added or s.modified or s.removed
1921
1921
1922 if s.added:
1922 if s.added:
1923 return any(submatches(c.substate.keys()))
1923 return any(submatches(c.substate.keys()))
1924
1924
1925 if s.modified:
1925 if s.modified:
1926 subs = set(c.p1().substate.keys())
1926 subs = set(c.p1().substate.keys())
1927 subs.update(c.substate.keys())
1927 subs.update(c.substate.keys())
1928
1928
1929 for path in submatches(subs):
1929 for path in submatches(subs):
1930 if c.p1().substate.get(path) != c.substate.get(path):
1930 if c.p1().substate.get(path) != c.substate.get(path):
1931 return True
1931 return True
1932
1932
1933 if s.removed:
1933 if s.removed:
1934 return any(submatches(c.p1().substate.keys()))
1934 return any(submatches(c.p1().substate.keys()))
1935
1935
1936 return False
1936 return False
1937
1937
1938 return subset.filter(matches, condrepr=('<subrepo %r>', pat))
1938 return subset.filter(matches, condrepr=('<subrepo %r>', pat))
1939
1939
1940 def _substringmatcher(pattern):
1940 def _substringmatcher(pattern):
1941 kind, pattern, matcher = util.stringmatcher(pattern)
1941 kind, pattern, matcher = util.stringmatcher(pattern)
1942 if kind == 'literal':
1942 if kind == 'literal':
1943 matcher = lambda s: pattern in s
1943 matcher = lambda s: pattern in s
1944 return kind, pattern, matcher
1944 return kind, pattern, matcher
1945
1945
1946 @predicate('tag([name])', safe=True)
1946 @predicate('tag([name])', safe=True)
1947 def tag(repo, subset, x):
1947 def tag(repo, subset, x):
1948 """The specified tag by name, or all tagged revisions if no name is given.
1948 """The specified tag by name, or all tagged revisions if no name is given.
1949
1949
1950 If `name` starts with `re:`, the remainder of the name is treated as
1950 If `name` starts with `re:`, the remainder of the name is treated as
1951 a regular expression. To match a tag that actually starts with `re:`,
1951 a regular expression. To match a tag that actually starts with `re:`,
1952 use the prefix `literal:`.
1952 use the prefix `literal:`.
1953 """
1953 """
1954 # i18n: "tag" is a keyword
1954 # i18n: "tag" is a keyword
1955 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1955 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1956 cl = repo.changelog
1956 cl = repo.changelog
1957 if args:
1957 if args:
1958 pattern = getstring(args[0],
1958 pattern = getstring(args[0],
1959 # i18n: "tag" is a keyword
1959 # i18n: "tag" is a keyword
1960 _('the argument to tag must be a string'))
1960 _('the argument to tag must be a string'))
1961 kind, pattern, matcher = util.stringmatcher(pattern)
1961 kind, pattern, matcher = util.stringmatcher(pattern)
1962 if kind == 'literal':
1962 if kind == 'literal':
1963 # avoid resolving all tags
1963 # avoid resolving all tags
1964 tn = repo._tagscache.tags.get(pattern, None)
1964 tn = repo._tagscache.tags.get(pattern, None)
1965 if tn is None:
1965 if tn is None:
1966 raise error.RepoLookupError(_("tag '%s' does not exist")
1966 raise error.RepoLookupError(_("tag '%s' does not exist")
1967 % pattern)
1967 % pattern)
1968 s = set([repo[tn].rev()])
1968 s = set([repo[tn].rev()])
1969 else:
1969 else:
1970 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1970 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1971 else:
1971 else:
1972 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1972 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1973 return subset & s
1973 return subset & s
1974
1974
1975 @predicate('tagged', safe=True)
1975 @predicate('tagged', safe=True)
1976 def tagged(repo, subset, x):
1976 def tagged(repo, subset, x):
1977 return tag(repo, subset, x)
1977 return tag(repo, subset, x)
1978
1978
1979 @predicate('unstable()', safe=True)
1979 @predicate('unstable()', safe=True)
1980 def unstable(repo, subset, x):
1980 def unstable(repo, subset, x):
1981 """Non-obsolete changesets with obsolete ancestors.
1981 """Non-obsolete changesets with obsolete ancestors.
1982 """
1982 """
1983 # i18n: "unstable" is a keyword
1983 # i18n: "unstable" is a keyword
1984 getargs(x, 0, 0, _("unstable takes no arguments"))
1984 getargs(x, 0, 0, _("unstable takes no arguments"))
1985 unstables = obsmod.getrevs(repo, 'unstable')
1985 unstables = obsmod.getrevs(repo, 'unstable')
1986 return subset & unstables
1986 return subset & unstables
1987
1987
1988
1988
1989 @predicate('user(string)', safe=True)
1989 @predicate('user(string)', safe=True)
1990 def user(repo, subset, x):
1990 def user(repo, subset, x):
1991 """User name contains string. The match is case-insensitive.
1991 """User name contains string. The match is case-insensitive.
1992
1992
1993 If `string` starts with `re:`, the remainder of the string is treated as
1993 If `string` starts with `re:`, the remainder of the string is treated as
1994 a regular expression. To match a user that actually contains `re:`, use
1994 a regular expression. To match a user that actually contains `re:`, use
1995 the prefix `literal:`.
1995 the prefix `literal:`.
1996 """
1996 """
1997 return author(repo, subset, x)
1997 return author(repo, subset, x)
1998
1998
1999 # experimental
1999 # experimental
2000 @predicate('wdir', safe=True)
2000 @predicate('wdir', safe=True)
2001 def wdir(repo, subset, x):
2001 def wdir(repo, subset, x):
2002 # i18n: "wdir" is a keyword
2002 # i18n: "wdir" is a keyword
2003 getargs(x, 0, 0, _("wdir takes no arguments"))
2003 getargs(x, 0, 0, _("wdir takes no arguments"))
2004 if node.wdirrev in subset or isinstance(subset, fullreposet):
2004 if node.wdirrev in subset or isinstance(subset, fullreposet):
2005 return baseset([node.wdirrev])
2005 return baseset([node.wdirrev])
2006 return baseset()
2006 return baseset()
2007
2007
2008 # for internal use
2008 # for internal use
2009 @predicate('_list', safe=True)
2009 @predicate('_list', safe=True)
2010 def _list(repo, subset, x):
2010 def _list(repo, subset, x):
2011 s = getstring(x, "internal error")
2011 s = getstring(x, "internal error")
2012 if not s:
2012 if not s:
2013 return baseset()
2013 return baseset()
2014 # remove duplicates here. it's difficult for caller to deduplicate sets
2014 # remove duplicates here. it's difficult for caller to deduplicate sets
2015 # because different symbols can point to the same rev.
2015 # because different symbols can point to the same rev.
2016 cl = repo.changelog
2016 cl = repo.changelog
2017 ls = []
2017 ls = []
2018 seen = set()
2018 seen = set()
2019 for t in s.split('\0'):
2019 for t in s.split('\0'):
2020 try:
2020 try:
2021 # fast path for integer revision
2021 # fast path for integer revision
2022 r = int(t)
2022 r = int(t)
2023 if str(r) != t or r not in cl:
2023 if str(r) != t or r not in cl:
2024 raise ValueError
2024 raise ValueError
2025 revs = [r]
2025 revs = [r]
2026 except ValueError:
2026 except ValueError:
2027 revs = stringset(repo, subset, t)
2027 revs = stringset(repo, subset, t)
2028
2028
2029 for r in revs:
2029 for r in revs:
2030 if r in seen:
2030 if r in seen:
2031 continue
2031 continue
2032 if (r in subset
2032 if (r in subset
2033 or r == node.nullrev and isinstance(subset, fullreposet)):
2033 or r == node.nullrev and isinstance(subset, fullreposet)):
2034 ls.append(r)
2034 ls.append(r)
2035 seen.add(r)
2035 seen.add(r)
2036 return baseset(ls)
2036 return baseset(ls)
2037
2037
2038 # for internal use
2038 # for internal use
2039 @predicate('_intlist', safe=True)
2039 @predicate('_intlist', safe=True)
2040 def _intlist(repo, subset, x):
2040 def _intlist(repo, subset, x):
2041 s = getstring(x, "internal error")
2041 s = getstring(x, "internal error")
2042 if not s:
2042 if not s:
2043 return baseset()
2043 return baseset()
2044 ls = [int(r) for r in s.split('\0')]
2044 ls = [int(r) for r in s.split('\0')]
2045 s = subset
2045 s = subset
2046 return baseset([r for r in ls if r in s])
2046 return baseset([r for r in ls if r in s])
2047
2047
2048 # for internal use
2048 # for internal use
2049 @predicate('_hexlist', safe=True)
2049 @predicate('_hexlist', safe=True)
2050 def _hexlist(repo, subset, x):
2050 def _hexlist(repo, subset, x):
2051 s = getstring(x, "internal error")
2051 s = getstring(x, "internal error")
2052 if not s:
2052 if not s:
2053 return baseset()
2053 return baseset()
2054 cl = repo.changelog
2054 cl = repo.changelog
2055 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
2055 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
2056 s = subset
2056 s = subset
2057 return baseset([r for r in ls if r in s])
2057 return baseset([r for r in ls if r in s])
2058
2058
2059 methods = {
2059 methods = {
2060 "range": rangeset,
2060 "range": rangeset,
2061 "dagrange": dagrange,
2061 "dagrange": dagrange,
2062 "string": stringset,
2062 "string": stringset,
2063 "symbol": stringset,
2063 "symbol": stringset,
2064 "and": andset,
2064 "and": andset,
2065 "or": orset,
2065 "or": orset,
2066 "not": notset,
2066 "not": notset,
2067 "difference": differenceset,
2067 "difference": differenceset,
2068 "list": listset,
2068 "list": listset,
2069 "keyvalue": keyvaluepair,
2069 "keyvalue": keyvaluepair,
2070 "func": func,
2070 "func": func,
2071 "ancestor": ancestorspec,
2071 "ancestor": ancestorspec,
2072 "parent": parentspec,
2072 "parent": parentspec,
2073 "parentpost": p1,
2073 "parentpost": p1,
2074 }
2074 }
2075
2075
2076 def optimize(x, small):
2076 def optimize(x, small):
2077 if x is None:
2077 if x is None:
2078 return 0, x
2078 return 0, x
2079
2079
2080 smallbonus = 1
2080 smallbonus = 1
2081 if small:
2081 if small:
2082 smallbonus = .5
2082 smallbonus = .5
2083
2083
2084 op = x[0]
2084 op = x[0]
2085 if op == 'minus':
2085 if op == 'minus':
2086 return optimize(('and', x[1], ('not', x[2])), small)
2086 return optimize(('and', x[1], ('not', x[2])), small)
2087 elif op == 'only':
2087 elif op == 'only':
2088 return optimize(('func', ('symbol', 'only'),
2088 return optimize(('func', ('symbol', 'only'),
2089 ('list', x[1], x[2])), small)
2089 ('list', x[1], x[2])), small)
2090 elif op == 'onlypost':
2090 elif op == 'onlypost':
2091 return optimize(('func', ('symbol', 'only'), x[1]), small)
2091 return optimize(('func', ('symbol', 'only'), x[1]), small)
2092 elif op == 'dagrangepre':
2092 elif op == 'dagrangepre':
2093 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
2093 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
2094 elif op == 'dagrangepost':
2094 elif op == 'dagrangepost':
2095 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
2095 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
2096 elif op == 'rangeall':
2096 elif op == 'rangeall':
2097 return optimize(('range', ('string', '0'), ('string', 'tip')), small)
2097 return optimize(('range', ('string', '0'), ('string', 'tip')), small)
2098 elif op == 'rangepre':
2098 elif op == 'rangepre':
2099 return optimize(('range', ('string', '0'), x[1]), small)
2099 return optimize(('range', ('string', '0'), x[1]), small)
2100 elif op == 'rangepost':
2100 elif op == 'rangepost':
2101 return optimize(('range', x[1], ('string', 'tip')), small)
2101 return optimize(('range', x[1], ('string', 'tip')), small)
2102 elif op == 'negate':
2102 elif op == 'negate':
2103 return optimize(('string',
2103 return optimize(('string',
2104 '-' + getstring(x[1], _("can't negate that"))), small)
2104 '-' + getstring(x[1], _("can't negate that"))), small)
2105 elif op in 'string symbol negate':
2105 elif op in 'string symbol negate':
2106 return smallbonus, x # single revisions are small
2106 return smallbonus, x # single revisions are small
2107 elif op == 'and':
2107 elif op == 'and':
2108 wa, ta = optimize(x[1], True)
2108 wa, ta = optimize(x[1], True)
2109 wb, tb = optimize(x[2], True)
2109 wb, tb = optimize(x[2], True)
2110
2110
2111 # (::x and not ::y)/(not ::y and ::x) have a fast path
2111 # (::x and not ::y)/(not ::y and ::x) have a fast path
2112 def isonly(revs, bases):
2112 def isonly(revs, bases):
2113 return (
2113 return (
2114 revs is not None
2114 revs is not None
2115 and revs[0] == 'func'
2115 and revs[0] == 'func'
2116 and getstring(revs[1], _('not a symbol')) == 'ancestors'
2116 and getstring(revs[1], _('not a symbol')) == 'ancestors'
2117 and bases is not None
2117 and bases is not None
2118 and bases[0] == 'not'
2118 and bases[0] == 'not'
2119 and bases[1][0] == 'func'
2119 and bases[1][0] == 'func'
2120 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
2120 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
2121
2121
2122 w = min(wa, wb)
2122 w = min(wa, wb)
2123 if isonly(ta, tb):
2123 if isonly(ta, tb):
2124 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
2124 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
2125 if isonly(tb, ta):
2125 if isonly(tb, ta):
2126 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
2126 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
2127
2127
2128 if tb is not None and tb[0] == 'not':
2128 if tb is not None and tb[0] == 'not':
2129 return wa, ('difference', ta, tb[1])
2129 return wa, ('difference', ta, tb[1])
2130
2130
2131 if wa > wb:
2131 if wa > wb:
2132 return w, (op, tb, ta)
2132 return w, (op, tb, ta)
2133 return w, (op, ta, tb)
2133 return w, (op, ta, tb)
2134 elif op == 'or':
2134 elif op == 'or':
2135 # fast path for machine-generated expression, that is likely to have
2135 # fast path for machine-generated expression, that is likely to have
2136 # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
2136 # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
2137 ws, ts, ss = [], [], []
2137 ws, ts, ss = [], [], []
2138 def flushss():
2138 def flushss():
2139 if not ss:
2139 if not ss:
2140 return
2140 return
2141 if len(ss) == 1:
2141 if len(ss) == 1:
2142 w, t = ss[0]
2142 w, t = ss[0]
2143 else:
2143 else:
2144 s = '\0'.join(t[1] for w, t in ss)
2144 s = '\0'.join(t[1] for w, t in ss)
2145 y = ('func', ('symbol', '_list'), ('string', s))
2145 y = ('func', ('symbol', '_list'), ('string', s))
2146 w, t = optimize(y, False)
2146 w, t = optimize(y, False)
2147 ws.append(w)
2147 ws.append(w)
2148 ts.append(t)
2148 ts.append(t)
2149 del ss[:]
2149 del ss[:]
2150 for y in x[1:]:
2150 for y in x[1:]:
2151 w, t = optimize(y, False)
2151 w, t = optimize(y, False)
2152 if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
2152 if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
2153 ss.append((w, t))
2153 ss.append((w, t))
2154 continue
2154 continue
2155 flushss()
2155 flushss()
2156 ws.append(w)
2156 ws.append(w)
2157 ts.append(t)
2157 ts.append(t)
2158 flushss()
2158 flushss()
2159 if len(ts) == 1:
2159 if len(ts) == 1:
2160 return ws[0], ts[0] # 'or' operation is fully optimized out
2160 return ws[0], ts[0] # 'or' operation is fully optimized out
2161 # we can't reorder trees by weight because it would change the order.
2161 # we can't reorder trees by weight because it would change the order.
2162 # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
2162 # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
2163 # ts = tuple(t for w, t in sorted(zip(ws, ts), key=lambda wt: wt[0]))
2163 # ts = tuple(t for w, t in sorted(zip(ws, ts), key=lambda wt: wt[0]))
2164 return max(ws), (op,) + tuple(ts)
2164 return max(ws), (op,) + tuple(ts)
2165 elif op == 'not':
2165 elif op == 'not':
2166 # Optimize not public() to _notpublic() because we have a fast version
2166 # Optimize not public() to _notpublic() because we have a fast version
2167 if x[1] == ('func', ('symbol', 'public'), None):
2167 if x[1] == ('func', ('symbol', 'public'), None):
2168 newsym = ('func', ('symbol', '_notpublic'), None)
2168 newsym = ('func', ('symbol', '_notpublic'), None)
2169 o = optimize(newsym, not small)
2169 o = optimize(newsym, not small)
2170 return o[0], o[1]
2170 return o[0], o[1]
2171 else:
2171 else:
2172 o = optimize(x[1], not small)
2172 o = optimize(x[1], not small)
2173 return o[0], (op, o[1])
2173 return o[0], (op, o[1])
2174 elif op == 'parentpost':
2174 elif op == 'parentpost':
2175 o = optimize(x[1], small)
2175 o = optimize(x[1], small)
2176 return o[0], (op, o[1])
2176 return o[0], (op, o[1])
2177 elif op == 'group':
2177 elif op == 'group':
2178 return optimize(x[1], small)
2178 return optimize(x[1], small)
2179 elif op in 'dagrange range parent ancestorspec':
2179 elif op in 'dagrange range parent ancestorspec':
2180 if op == 'parent':
2180 if op == 'parent':
2181 # x^:y means (x^) : y, not x ^ (:y)
2181 # x^:y means (x^) : y, not x ^ (:y)
2182 post = ('parentpost', x[1])
2182 post = ('parentpost', x[1])
2183 if x[2][0] == 'dagrangepre':
2183 if x[2][0] == 'dagrangepre':
2184 return optimize(('dagrange', post, x[2][1]), small)
2184 return optimize(('dagrange', post, x[2][1]), small)
2185 elif x[2][0] == 'rangepre':
2185 elif x[2][0] == 'rangepre':
2186 return optimize(('range', post, x[2][1]), small)
2186 return optimize(('range', post, x[2][1]), small)
2187
2187
2188 wa, ta = optimize(x[1], small)
2188 wa, ta = optimize(x[1], small)
2189 wb, tb = optimize(x[2], small)
2189 wb, tb = optimize(x[2], small)
2190 return wa + wb, (op, ta, tb)
2190 return wa + wb, (op, ta, tb)
2191 elif op == 'list':
2191 elif op == 'list':
2192 ws, ts = zip(*(optimize(y, small) for y in x[1:]))
2192 ws, ts = zip(*(optimize(y, small) for y in x[1:]))
2193 return sum(ws), (op,) + ts
2193 return sum(ws), (op,) + ts
2194 elif op == 'func':
2194 elif op == 'func':
2195 f = getstring(x[1], _("not a symbol"))
2195 f = getstring(x[1], _("not a symbol"))
2196 wa, ta = optimize(x[2], small)
2196 wa, ta = optimize(x[2], small)
2197 if f in ("author branch closed date desc file grep keyword "
2197 if f in ("author branch closed date desc file grep keyword "
2198 "outgoing user"):
2198 "outgoing user"):
2199 w = 10 # slow
2199 w = 10 # slow
2200 elif f in "modifies adds removes":
2200 elif f in "modifies adds removes":
2201 w = 30 # slower
2201 w = 30 # slower
2202 elif f == "contains":
2202 elif f == "contains":
2203 w = 100 # very slow
2203 w = 100 # very slow
2204 elif f == "ancestor":
2204 elif f == "ancestor":
2205 w = 1 * smallbonus
2205 w = 1 * smallbonus
2206 elif f in "reverse limit first _intlist":
2206 elif f in "reverse limit first _intlist":
2207 w = 0
2207 w = 0
2208 elif f in "sort":
2208 elif f in "sort":
2209 w = 10 # assume most sorts look at changelog
2209 w = 10 # assume most sorts look at changelog
2210 else:
2210 else:
2211 w = 1
2211 w = 1
2212 return w + wa, (op, x[1], ta)
2212 return w + wa, (op, x[1], ta)
2213 return 1, x
2213 return 1, x
2214
2214
2215 # the set of valid characters for the initial letter of symbols in
2215 # the set of valid characters for the initial letter of symbols in
2216 # alias declarations and definitions
2216 # alias declarations and definitions
2217 _aliassyminitletters = set(c for c in [chr(i) for i in xrange(256)]
2217 _aliassyminitletters = set(c for c in [chr(i) for i in xrange(256)]
2218 if c.isalnum() or c in '._@$' or ord(c) > 127)
2218 if c.isalnum() or c in '._@$' or ord(c) > 127)
2219
2219
2220 def _tokenizealias(program, lookup=None):
2220 def _parsealias(spec):
2221 """Parse alias declaration/definition into a stream of tokens
2221 """Parse alias declaration/definition ``spec``
2222
2222
2223 This allows symbol names to use also ``$`` as an initial letter
2223 This allows symbol names to use also ``$`` as an initial letter
2224 (for backward compatibility), and callers of this function should
2224 (for backward compatibility), and callers of this function should
2225 examine whether ``$`` is used also for unexpected symbols or not.
2225 examine whether ``$`` is used also for unexpected symbols or not.
2226 """
2227 return tokenize(program, lookup=lookup,
2228 syminitletters=_aliassyminitletters)
2229
2230 def _parsealias(spec):
2231 """Parse alias declaration/definition ``spec``
2232
2226
2233 >>> _parsealias('foo($1)')
2227 >>> _parsealias('foo($1)')
2234 ('func', ('symbol', 'foo'), ('symbol', '$1'))
2228 ('func', ('symbol', 'foo'), ('symbol', '$1'))
2235 >>> _parsealias('foo bar')
2229 >>> _parsealias('foo bar')
2236 Traceback (most recent call last):
2230 Traceback (most recent call last):
2237 ...
2231 ...
2238 ParseError: ('invalid token', 4)
2232 ParseError: ('invalid token', 4)
2239 """
2233 """
2240 p = parser.parser(elements)
2234 p = parser.parser(elements)
2241 tree, pos = p.parse(_tokenizealias(spec))
2235 tree, pos = p.parse(tokenize(spec, syminitletters=_aliassyminitletters))
2242 if pos != len(spec):
2236 if pos != len(spec):
2243 raise error.ParseError(_('invalid token'), pos)
2237 raise error.ParseError(_('invalid token'), pos)
2244 return parser.simplifyinfixops(tree, ('list', 'or'))
2238 return parser.simplifyinfixops(tree, ('list', 'or'))
2245
2239
2246 class _aliasrules(parser.basealiasrules):
2240 class _aliasrules(parser.basealiasrules):
2247 """Parsing and expansion rule set of revset aliases"""
2241 """Parsing and expansion rule set of revset aliases"""
2248 _section = _('revset alias')
2242 _section = _('revset alias')
2249 _parse = staticmethod(_parsealias)
2243 _parse = staticmethod(_parsealias)
2250
2244
2251 @staticmethod
2245 @staticmethod
2252 def _trygetfunc(tree):
2246 def _trygetfunc(tree):
2253 if tree[0] == 'func' and tree[1][0] == 'symbol':
2247 if tree[0] == 'func' and tree[1][0] == 'symbol':
2254 return tree[1][1], getlist(tree[2])
2248 return tree[1][1], getlist(tree[2])
2255
2249
2256 def expandaliases(ui, tree, showwarning=None):
2250 def expandaliases(ui, tree, showwarning=None):
2257 aliases = _aliasrules.buildmap(ui.configitems('revsetalias'))
2251 aliases = _aliasrules.buildmap(ui.configitems('revsetalias'))
2258 tree = _aliasrules.expand(aliases, tree)
2252 tree = _aliasrules.expand(aliases, tree)
2259 if showwarning:
2253 if showwarning:
2260 # warn about problematic (but not referred) aliases
2254 # warn about problematic (but not referred) aliases
2261 for name, alias in sorted(aliases.iteritems()):
2255 for name, alias in sorted(aliases.iteritems()):
2262 if alias.error and not alias.warned:
2256 if alias.error and not alias.warned:
2263 showwarning(_('warning: %s\n') % (alias.error))
2257 showwarning(_('warning: %s\n') % (alias.error))
2264 alias.warned = True
2258 alias.warned = True
2265 return tree
2259 return tree
2266
2260
2267 def foldconcat(tree):
2261 def foldconcat(tree):
2268 """Fold elements to be concatenated by `##`
2262 """Fold elements to be concatenated by `##`
2269 """
2263 """
2270 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2264 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2271 return tree
2265 return tree
2272 if tree[0] == '_concat':
2266 if tree[0] == '_concat':
2273 pending = [tree]
2267 pending = [tree]
2274 l = []
2268 l = []
2275 while pending:
2269 while pending:
2276 e = pending.pop()
2270 e = pending.pop()
2277 if e[0] == '_concat':
2271 if e[0] == '_concat':
2278 pending.extend(reversed(e[1:]))
2272 pending.extend(reversed(e[1:]))
2279 elif e[0] in ('string', 'symbol'):
2273 elif e[0] in ('string', 'symbol'):
2280 l.append(e[1])
2274 l.append(e[1])
2281 else:
2275 else:
2282 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2276 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2283 raise error.ParseError(msg)
2277 raise error.ParseError(msg)
2284 return ('string', ''.join(l))
2278 return ('string', ''.join(l))
2285 else:
2279 else:
2286 return tuple(foldconcat(t) for t in tree)
2280 return tuple(foldconcat(t) for t in tree)
2287
2281
2288 def parse(spec, lookup=None):
2282 def parse(spec, lookup=None):
2289 p = parser.parser(elements)
2283 p = parser.parser(elements)
2290 tree, pos = p.parse(tokenize(spec, lookup=lookup))
2284 tree, pos = p.parse(tokenize(spec, lookup=lookup))
2291 if pos != len(spec):
2285 if pos != len(spec):
2292 raise error.ParseError(_("invalid token"), pos)
2286 raise error.ParseError(_("invalid token"), pos)
2293 return parser.simplifyinfixops(tree, ('list', 'or'))
2287 return parser.simplifyinfixops(tree, ('list', 'or'))
2294
2288
2295 def posttreebuilthook(tree, repo):
2289 def posttreebuilthook(tree, repo):
2296 # hook for extensions to execute code on the optimized tree
2290 # hook for extensions to execute code on the optimized tree
2297 pass
2291 pass
2298
2292
2299 def match(ui, spec, repo=None):
2293 def match(ui, spec, repo=None):
2300 if not spec:
2294 if not spec:
2301 raise error.ParseError(_("empty query"))
2295 raise error.ParseError(_("empty query"))
2302 lookup = None
2296 lookup = None
2303 if repo:
2297 if repo:
2304 lookup = repo.__contains__
2298 lookup = repo.__contains__
2305 tree = parse(spec, lookup)
2299 tree = parse(spec, lookup)
2306 return _makematcher(ui, tree, repo)
2300 return _makematcher(ui, tree, repo)
2307
2301
2308 def matchany(ui, specs, repo=None):
2302 def matchany(ui, specs, repo=None):
2309 """Create a matcher that will include any revisions matching one of the
2303 """Create a matcher that will include any revisions matching one of the
2310 given specs"""
2304 given specs"""
2311 if not specs:
2305 if not specs:
2312 def mfunc(repo, subset=None):
2306 def mfunc(repo, subset=None):
2313 return baseset()
2307 return baseset()
2314 return mfunc
2308 return mfunc
2315 if not all(specs):
2309 if not all(specs):
2316 raise error.ParseError(_("empty query"))
2310 raise error.ParseError(_("empty query"))
2317 lookup = None
2311 lookup = None
2318 if repo:
2312 if repo:
2319 lookup = repo.__contains__
2313 lookup = repo.__contains__
2320 if len(specs) == 1:
2314 if len(specs) == 1:
2321 tree = parse(specs[0], lookup)
2315 tree = parse(specs[0], lookup)
2322 else:
2316 else:
2323 tree = ('or',) + tuple(parse(s, lookup) for s in specs)
2317 tree = ('or',) + tuple(parse(s, lookup) for s in specs)
2324 return _makematcher(ui, tree, repo)
2318 return _makematcher(ui, tree, repo)
2325
2319
2326 def _makematcher(ui, tree, repo):
2320 def _makematcher(ui, tree, repo):
2327 if ui:
2321 if ui:
2328 tree = expandaliases(ui, tree, showwarning=ui.warn)
2322 tree = expandaliases(ui, tree, showwarning=ui.warn)
2329 tree = foldconcat(tree)
2323 tree = foldconcat(tree)
2330 weight, tree = optimize(tree, True)
2324 weight, tree = optimize(tree, True)
2331 posttreebuilthook(tree, repo)
2325 posttreebuilthook(tree, repo)
2332 def mfunc(repo, subset=None):
2326 def mfunc(repo, subset=None):
2333 if subset is None:
2327 if subset is None:
2334 subset = fullreposet(repo)
2328 subset = fullreposet(repo)
2335 if util.safehasattr(subset, 'isascending'):
2329 if util.safehasattr(subset, 'isascending'):
2336 result = getset(repo, subset, tree)
2330 result = getset(repo, subset, tree)
2337 else:
2331 else:
2338 result = getset(repo, baseset(subset), tree)
2332 result = getset(repo, baseset(subset), tree)
2339 return result
2333 return result
2340 return mfunc
2334 return mfunc
2341
2335
2342 def formatspec(expr, *args):
2336 def formatspec(expr, *args):
2343 '''
2337 '''
2344 This is a convenience function for using revsets internally, and
2338 This is a convenience function for using revsets internally, and
2345 escapes arguments appropriately. Aliases are intentionally ignored
2339 escapes arguments appropriately. Aliases are intentionally ignored
2346 so that intended expression behavior isn't accidentally subverted.
2340 so that intended expression behavior isn't accidentally subverted.
2347
2341
2348 Supported arguments:
2342 Supported arguments:
2349
2343
2350 %r = revset expression, parenthesized
2344 %r = revset expression, parenthesized
2351 %d = int(arg), no quoting
2345 %d = int(arg), no quoting
2352 %s = string(arg), escaped and single-quoted
2346 %s = string(arg), escaped and single-quoted
2353 %b = arg.branch(), escaped and single-quoted
2347 %b = arg.branch(), escaped and single-quoted
2354 %n = hex(arg), single-quoted
2348 %n = hex(arg), single-quoted
2355 %% = a literal '%'
2349 %% = a literal '%'
2356
2350
2357 Prefixing the type with 'l' specifies a parenthesized list of that type.
2351 Prefixing the type with 'l' specifies a parenthesized list of that type.
2358
2352
2359 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2353 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2360 '(10 or 11):: and ((this()) or (that()))'
2354 '(10 or 11):: and ((this()) or (that()))'
2361 >>> formatspec('%d:: and not %d::', 10, 20)
2355 >>> formatspec('%d:: and not %d::', 10, 20)
2362 '10:: and not 20::'
2356 '10:: and not 20::'
2363 >>> formatspec('%ld or %ld', [], [1])
2357 >>> formatspec('%ld or %ld', [], [1])
2364 "_list('') or 1"
2358 "_list('') or 1"
2365 >>> formatspec('keyword(%s)', 'foo\\xe9')
2359 >>> formatspec('keyword(%s)', 'foo\\xe9')
2366 "keyword('foo\\\\xe9')"
2360 "keyword('foo\\\\xe9')"
2367 >>> b = lambda: 'default'
2361 >>> b = lambda: 'default'
2368 >>> b.branch = b
2362 >>> b.branch = b
2369 >>> formatspec('branch(%b)', b)
2363 >>> formatspec('branch(%b)', b)
2370 "branch('default')"
2364 "branch('default')"
2371 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2365 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2372 "root(_list('a\\x00b\\x00c\\x00d'))"
2366 "root(_list('a\\x00b\\x00c\\x00d'))"
2373 '''
2367 '''
2374
2368
2375 def quote(s):
2369 def quote(s):
2376 return repr(str(s))
2370 return repr(str(s))
2377
2371
2378 def argtype(c, arg):
2372 def argtype(c, arg):
2379 if c == 'd':
2373 if c == 'd':
2380 return str(int(arg))
2374 return str(int(arg))
2381 elif c == 's':
2375 elif c == 's':
2382 return quote(arg)
2376 return quote(arg)
2383 elif c == 'r':
2377 elif c == 'r':
2384 parse(arg) # make sure syntax errors are confined
2378 parse(arg) # make sure syntax errors are confined
2385 return '(%s)' % arg
2379 return '(%s)' % arg
2386 elif c == 'n':
2380 elif c == 'n':
2387 return quote(node.hex(arg))
2381 return quote(node.hex(arg))
2388 elif c == 'b':
2382 elif c == 'b':
2389 return quote(arg.branch())
2383 return quote(arg.branch())
2390
2384
2391 def listexp(s, t):
2385 def listexp(s, t):
2392 l = len(s)
2386 l = len(s)
2393 if l == 0:
2387 if l == 0:
2394 return "_list('')"
2388 return "_list('')"
2395 elif l == 1:
2389 elif l == 1:
2396 return argtype(t, s[0])
2390 return argtype(t, s[0])
2397 elif t == 'd':
2391 elif t == 'd':
2398 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2392 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2399 elif t == 's':
2393 elif t == 's':
2400 return "_list('%s')" % "\0".join(s)
2394 return "_list('%s')" % "\0".join(s)
2401 elif t == 'n':
2395 elif t == 'n':
2402 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2396 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2403 elif t == 'b':
2397 elif t == 'b':
2404 return "_list('%s')" % "\0".join(a.branch() for a in s)
2398 return "_list('%s')" % "\0".join(a.branch() for a in s)
2405
2399
2406 m = l // 2
2400 m = l // 2
2407 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2401 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2408
2402
2409 ret = ''
2403 ret = ''
2410 pos = 0
2404 pos = 0
2411 arg = 0
2405 arg = 0
2412 while pos < len(expr):
2406 while pos < len(expr):
2413 c = expr[pos]
2407 c = expr[pos]
2414 if c == '%':
2408 if c == '%':
2415 pos += 1
2409 pos += 1
2416 d = expr[pos]
2410 d = expr[pos]
2417 if d == '%':
2411 if d == '%':
2418 ret += d
2412 ret += d
2419 elif d in 'dsnbr':
2413 elif d in 'dsnbr':
2420 ret += argtype(d, args[arg])
2414 ret += argtype(d, args[arg])
2421 arg += 1
2415 arg += 1
2422 elif d == 'l':
2416 elif d == 'l':
2423 # a list of some type
2417 # a list of some type
2424 pos += 1
2418 pos += 1
2425 d = expr[pos]
2419 d = expr[pos]
2426 ret += listexp(list(args[arg]), d)
2420 ret += listexp(list(args[arg]), d)
2427 arg += 1
2421 arg += 1
2428 else:
2422 else:
2429 raise error.Abort('unexpected revspec format character %s' % d)
2423 raise error.Abort('unexpected revspec format character %s' % d)
2430 else:
2424 else:
2431 ret += c
2425 ret += c
2432 pos += 1
2426 pos += 1
2433
2427
2434 return ret
2428 return ret
2435
2429
2436 def prettyformat(tree):
2430 def prettyformat(tree):
2437 return parser.prettyformat(tree, ('string', 'symbol'))
2431 return parser.prettyformat(tree, ('string', 'symbol'))
2438
2432
2439 def depth(tree):
2433 def depth(tree):
2440 if isinstance(tree, tuple):
2434 if isinstance(tree, tuple):
2441 return max(map(depth, tree)) + 1
2435 return max(map(depth, tree)) + 1
2442 else:
2436 else:
2443 return 0
2437 return 0
2444
2438
2445 def funcsused(tree):
2439 def funcsused(tree):
2446 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2440 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2447 return set()
2441 return set()
2448 else:
2442 else:
2449 funcs = set()
2443 funcs = set()
2450 for s in tree[1:]:
2444 for s in tree[1:]:
2451 funcs |= funcsused(s)
2445 funcs |= funcsused(s)
2452 if tree[0] == 'func':
2446 if tree[0] == 'func':
2453 funcs.add(tree[1][1])
2447 funcs.add(tree[1][1])
2454 return funcs
2448 return funcs
2455
2449
2456 def _formatsetrepr(r):
2450 def _formatsetrepr(r):
2457 """Format an optional printable representation of a set
2451 """Format an optional printable representation of a set
2458
2452
2459 ======== =================================
2453 ======== =================================
2460 type(r) example
2454 type(r) example
2461 ======== =================================
2455 ======== =================================
2462 tuple ('<not %r>', other)
2456 tuple ('<not %r>', other)
2463 str '<branch closed>'
2457 str '<branch closed>'
2464 callable lambda: '<branch %r>' % sorted(b)
2458 callable lambda: '<branch %r>' % sorted(b)
2465 object other
2459 object other
2466 ======== =================================
2460 ======== =================================
2467 """
2461 """
2468 if r is None:
2462 if r is None:
2469 return ''
2463 return ''
2470 elif isinstance(r, tuple):
2464 elif isinstance(r, tuple):
2471 return r[0] % r[1:]
2465 return r[0] % r[1:]
2472 elif isinstance(r, str):
2466 elif isinstance(r, str):
2473 return r
2467 return r
2474 elif callable(r):
2468 elif callable(r):
2475 return r()
2469 return r()
2476 else:
2470 else:
2477 return repr(r)
2471 return repr(r)
2478
2472
2479 class abstractsmartset(object):
2473 class abstractsmartset(object):
2480
2474
2481 def __nonzero__(self):
2475 def __nonzero__(self):
2482 """True if the smartset is not empty"""
2476 """True if the smartset is not empty"""
2483 raise NotImplementedError()
2477 raise NotImplementedError()
2484
2478
2485 def __contains__(self, rev):
2479 def __contains__(self, rev):
2486 """provide fast membership testing"""
2480 """provide fast membership testing"""
2487 raise NotImplementedError()
2481 raise NotImplementedError()
2488
2482
2489 def __iter__(self):
2483 def __iter__(self):
2490 """iterate the set in the order it is supposed to be iterated"""
2484 """iterate the set in the order it is supposed to be iterated"""
2491 raise NotImplementedError()
2485 raise NotImplementedError()
2492
2486
2493 # Attributes containing a function to perform a fast iteration in a given
2487 # Attributes containing a function to perform a fast iteration in a given
2494 # direction. A smartset can have none, one, or both defined.
2488 # direction. A smartset can have none, one, or both defined.
2495 #
2489 #
2496 # Default value is None instead of a function returning None to avoid
2490 # Default value is None instead of a function returning None to avoid
2497 # initializing an iterator just for testing if a fast method exists.
2491 # initializing an iterator just for testing if a fast method exists.
2498 fastasc = None
2492 fastasc = None
2499 fastdesc = None
2493 fastdesc = None
2500
2494
2501 def isascending(self):
2495 def isascending(self):
2502 """True if the set will iterate in ascending order"""
2496 """True if the set will iterate in ascending order"""
2503 raise NotImplementedError()
2497 raise NotImplementedError()
2504
2498
2505 def isdescending(self):
2499 def isdescending(self):
2506 """True if the set will iterate in descending order"""
2500 """True if the set will iterate in descending order"""
2507 raise NotImplementedError()
2501 raise NotImplementedError()
2508
2502
2509 @util.cachefunc
2503 @util.cachefunc
2510 def min(self):
2504 def min(self):
2511 """return the minimum element in the set"""
2505 """return the minimum element in the set"""
2512 if self.fastasc is not None:
2506 if self.fastasc is not None:
2513 for r in self.fastasc():
2507 for r in self.fastasc():
2514 return r
2508 return r
2515 raise ValueError('arg is an empty sequence')
2509 raise ValueError('arg is an empty sequence')
2516 return min(self)
2510 return min(self)
2517
2511
2518 @util.cachefunc
2512 @util.cachefunc
2519 def max(self):
2513 def max(self):
2520 """return the maximum element in the set"""
2514 """return the maximum element in the set"""
2521 if self.fastdesc is not None:
2515 if self.fastdesc is not None:
2522 for r in self.fastdesc():
2516 for r in self.fastdesc():
2523 return r
2517 return r
2524 raise ValueError('arg is an empty sequence')
2518 raise ValueError('arg is an empty sequence')
2525 return max(self)
2519 return max(self)
2526
2520
2527 def first(self):
2521 def first(self):
2528 """return the first element in the set (user iteration perspective)
2522 """return the first element in the set (user iteration perspective)
2529
2523
2530 Return None if the set is empty"""
2524 Return None if the set is empty"""
2531 raise NotImplementedError()
2525 raise NotImplementedError()
2532
2526
2533 def last(self):
2527 def last(self):
2534 """return the last element in the set (user iteration perspective)
2528 """return the last element in the set (user iteration perspective)
2535
2529
2536 Return None if the set is empty"""
2530 Return None if the set is empty"""
2537 raise NotImplementedError()
2531 raise NotImplementedError()
2538
2532
2539 def __len__(self):
2533 def __len__(self):
2540 """return the length of the smartsets
2534 """return the length of the smartsets
2541
2535
2542 This can be expensive on smartset that could be lazy otherwise."""
2536 This can be expensive on smartset that could be lazy otherwise."""
2543 raise NotImplementedError()
2537 raise NotImplementedError()
2544
2538
2545 def reverse(self):
2539 def reverse(self):
2546 """reverse the expected iteration order"""
2540 """reverse the expected iteration order"""
2547 raise NotImplementedError()
2541 raise NotImplementedError()
2548
2542
2549 def sort(self, reverse=True):
2543 def sort(self, reverse=True):
2550 """get the set to iterate in an ascending or descending order"""
2544 """get the set to iterate in an ascending or descending order"""
2551 raise NotImplementedError()
2545 raise NotImplementedError()
2552
2546
2553 def __and__(self, other):
2547 def __and__(self, other):
2554 """Returns a new object with the intersection of the two collections.
2548 """Returns a new object with the intersection of the two collections.
2555
2549
2556 This is part of the mandatory API for smartset."""
2550 This is part of the mandatory API for smartset."""
2557 if isinstance(other, fullreposet):
2551 if isinstance(other, fullreposet):
2558 return self
2552 return self
2559 return self.filter(other.__contains__, condrepr=other, cache=False)
2553 return self.filter(other.__contains__, condrepr=other, cache=False)
2560
2554
2561 def __add__(self, other):
2555 def __add__(self, other):
2562 """Returns a new object with the union of the two collections.
2556 """Returns a new object with the union of the two collections.
2563
2557
2564 This is part of the mandatory API for smartset."""
2558 This is part of the mandatory API for smartset."""
2565 return addset(self, other)
2559 return addset(self, other)
2566
2560
2567 def __sub__(self, other):
2561 def __sub__(self, other):
2568 """Returns a new object with the substraction of the two collections.
2562 """Returns a new object with the substraction of the two collections.
2569
2563
2570 This is part of the mandatory API for smartset."""
2564 This is part of the mandatory API for smartset."""
2571 c = other.__contains__
2565 c = other.__contains__
2572 return self.filter(lambda r: not c(r), condrepr=('<not %r>', other),
2566 return self.filter(lambda r: not c(r), condrepr=('<not %r>', other),
2573 cache=False)
2567 cache=False)
2574
2568
2575 def filter(self, condition, condrepr=None, cache=True):
2569 def filter(self, condition, condrepr=None, cache=True):
2576 """Returns this smartset filtered by condition as a new smartset.
2570 """Returns this smartset filtered by condition as a new smartset.
2577
2571
2578 `condition` is a callable which takes a revision number and returns a
2572 `condition` is a callable which takes a revision number and returns a
2579 boolean. Optional `condrepr` provides a printable representation of
2573 boolean. Optional `condrepr` provides a printable representation of
2580 the given `condition`.
2574 the given `condition`.
2581
2575
2582 This is part of the mandatory API for smartset."""
2576 This is part of the mandatory API for smartset."""
2583 # builtin cannot be cached. but do not needs to
2577 # builtin cannot be cached. but do not needs to
2584 if cache and util.safehasattr(condition, 'func_code'):
2578 if cache and util.safehasattr(condition, 'func_code'):
2585 condition = util.cachefunc(condition)
2579 condition = util.cachefunc(condition)
2586 return filteredset(self, condition, condrepr)
2580 return filteredset(self, condition, condrepr)
2587
2581
2588 class baseset(abstractsmartset):
2582 class baseset(abstractsmartset):
2589 """Basic data structure that represents a revset and contains the basic
2583 """Basic data structure that represents a revset and contains the basic
2590 operation that it should be able to perform.
2584 operation that it should be able to perform.
2591
2585
2592 Every method in this class should be implemented by any smartset class.
2586 Every method in this class should be implemented by any smartset class.
2593 """
2587 """
2594 def __init__(self, data=(), datarepr=None):
2588 def __init__(self, data=(), datarepr=None):
2595 """
2589 """
2596 datarepr: a tuple of (format, obj, ...), a function or an object that
2590 datarepr: a tuple of (format, obj, ...), a function or an object that
2597 provides a printable representation of the given data.
2591 provides a printable representation of the given data.
2598 """
2592 """
2599 self._ascending = None
2593 self._ascending = None
2600 if not isinstance(data, list):
2594 if not isinstance(data, list):
2601 if isinstance(data, set):
2595 if isinstance(data, set):
2602 self._set = data
2596 self._set = data
2603 # set has no order we pick one for stability purpose
2597 # set has no order we pick one for stability purpose
2604 self._ascending = True
2598 self._ascending = True
2605 data = list(data)
2599 data = list(data)
2606 self._list = data
2600 self._list = data
2607 self._datarepr = datarepr
2601 self._datarepr = datarepr
2608
2602
2609 @util.propertycache
2603 @util.propertycache
2610 def _set(self):
2604 def _set(self):
2611 return set(self._list)
2605 return set(self._list)
2612
2606
2613 @util.propertycache
2607 @util.propertycache
2614 def _asclist(self):
2608 def _asclist(self):
2615 asclist = self._list[:]
2609 asclist = self._list[:]
2616 asclist.sort()
2610 asclist.sort()
2617 return asclist
2611 return asclist
2618
2612
2619 def __iter__(self):
2613 def __iter__(self):
2620 if self._ascending is None:
2614 if self._ascending is None:
2621 return iter(self._list)
2615 return iter(self._list)
2622 elif self._ascending:
2616 elif self._ascending:
2623 return iter(self._asclist)
2617 return iter(self._asclist)
2624 else:
2618 else:
2625 return reversed(self._asclist)
2619 return reversed(self._asclist)
2626
2620
2627 def fastasc(self):
2621 def fastasc(self):
2628 return iter(self._asclist)
2622 return iter(self._asclist)
2629
2623
2630 def fastdesc(self):
2624 def fastdesc(self):
2631 return reversed(self._asclist)
2625 return reversed(self._asclist)
2632
2626
2633 @util.propertycache
2627 @util.propertycache
2634 def __contains__(self):
2628 def __contains__(self):
2635 return self._set.__contains__
2629 return self._set.__contains__
2636
2630
2637 def __nonzero__(self):
2631 def __nonzero__(self):
2638 return bool(self._list)
2632 return bool(self._list)
2639
2633
2640 def sort(self, reverse=False):
2634 def sort(self, reverse=False):
2641 self._ascending = not bool(reverse)
2635 self._ascending = not bool(reverse)
2642
2636
2643 def reverse(self):
2637 def reverse(self):
2644 if self._ascending is None:
2638 if self._ascending is None:
2645 self._list.reverse()
2639 self._list.reverse()
2646 else:
2640 else:
2647 self._ascending = not self._ascending
2641 self._ascending = not self._ascending
2648
2642
2649 def __len__(self):
2643 def __len__(self):
2650 return len(self._list)
2644 return len(self._list)
2651
2645
2652 def isascending(self):
2646 def isascending(self):
2653 """Returns True if the collection is ascending order, False if not.
2647 """Returns True if the collection is ascending order, False if not.
2654
2648
2655 This is part of the mandatory API for smartset."""
2649 This is part of the mandatory API for smartset."""
2656 if len(self) <= 1:
2650 if len(self) <= 1:
2657 return True
2651 return True
2658 return self._ascending is not None and self._ascending
2652 return self._ascending is not None and self._ascending
2659
2653
2660 def isdescending(self):
2654 def isdescending(self):
2661 """Returns True if the collection is descending order, False if not.
2655 """Returns True if the collection is descending order, False if not.
2662
2656
2663 This is part of the mandatory API for smartset."""
2657 This is part of the mandatory API for smartset."""
2664 if len(self) <= 1:
2658 if len(self) <= 1:
2665 return True
2659 return True
2666 return self._ascending is not None and not self._ascending
2660 return self._ascending is not None and not self._ascending
2667
2661
2668 def first(self):
2662 def first(self):
2669 if self:
2663 if self:
2670 if self._ascending is None:
2664 if self._ascending is None:
2671 return self._list[0]
2665 return self._list[0]
2672 elif self._ascending:
2666 elif self._ascending:
2673 return self._asclist[0]
2667 return self._asclist[0]
2674 else:
2668 else:
2675 return self._asclist[-1]
2669 return self._asclist[-1]
2676 return None
2670 return None
2677
2671
2678 def last(self):
2672 def last(self):
2679 if self:
2673 if self:
2680 if self._ascending is None:
2674 if self._ascending is None:
2681 return self._list[-1]
2675 return self._list[-1]
2682 elif self._ascending:
2676 elif self._ascending:
2683 return self._asclist[-1]
2677 return self._asclist[-1]
2684 else:
2678 else:
2685 return self._asclist[0]
2679 return self._asclist[0]
2686 return None
2680 return None
2687
2681
2688 def __repr__(self):
2682 def __repr__(self):
2689 d = {None: '', False: '-', True: '+'}[self._ascending]
2683 d = {None: '', False: '-', True: '+'}[self._ascending]
2690 s = _formatsetrepr(self._datarepr)
2684 s = _formatsetrepr(self._datarepr)
2691 if not s:
2685 if not s:
2692 l = self._list
2686 l = self._list
2693 # if _list has been built from a set, it might have a different
2687 # if _list has been built from a set, it might have a different
2694 # order from one python implementation to another.
2688 # order from one python implementation to another.
2695 # We fallback to the sorted version for a stable output.
2689 # We fallback to the sorted version for a stable output.
2696 if self._ascending is not None:
2690 if self._ascending is not None:
2697 l = self._asclist
2691 l = self._asclist
2698 s = repr(l)
2692 s = repr(l)
2699 return '<%s%s %s>' % (type(self).__name__, d, s)
2693 return '<%s%s %s>' % (type(self).__name__, d, s)
2700
2694
2701 class filteredset(abstractsmartset):
2695 class filteredset(abstractsmartset):
2702 """Duck type for baseset class which iterates lazily over the revisions in
2696 """Duck type for baseset class which iterates lazily over the revisions in
2703 the subset and contains a function which tests for membership in the
2697 the subset and contains a function which tests for membership in the
2704 revset
2698 revset
2705 """
2699 """
2706 def __init__(self, subset, condition=lambda x: True, condrepr=None):
2700 def __init__(self, subset, condition=lambda x: True, condrepr=None):
2707 """
2701 """
2708 condition: a function that decide whether a revision in the subset
2702 condition: a function that decide whether a revision in the subset
2709 belongs to the revset or not.
2703 belongs to the revset or not.
2710 condrepr: a tuple of (format, obj, ...), a function or an object that
2704 condrepr: a tuple of (format, obj, ...), a function or an object that
2711 provides a printable representation of the given condition.
2705 provides a printable representation of the given condition.
2712 """
2706 """
2713 self._subset = subset
2707 self._subset = subset
2714 self._condition = condition
2708 self._condition = condition
2715 self._condrepr = condrepr
2709 self._condrepr = condrepr
2716
2710
2717 def __contains__(self, x):
2711 def __contains__(self, x):
2718 return x in self._subset and self._condition(x)
2712 return x in self._subset and self._condition(x)
2719
2713
2720 def __iter__(self):
2714 def __iter__(self):
2721 return self._iterfilter(self._subset)
2715 return self._iterfilter(self._subset)
2722
2716
2723 def _iterfilter(self, it):
2717 def _iterfilter(self, it):
2724 cond = self._condition
2718 cond = self._condition
2725 for x in it:
2719 for x in it:
2726 if cond(x):
2720 if cond(x):
2727 yield x
2721 yield x
2728
2722
2729 @property
2723 @property
2730 def fastasc(self):
2724 def fastasc(self):
2731 it = self._subset.fastasc
2725 it = self._subset.fastasc
2732 if it is None:
2726 if it is None:
2733 return None
2727 return None
2734 return lambda: self._iterfilter(it())
2728 return lambda: self._iterfilter(it())
2735
2729
2736 @property
2730 @property
2737 def fastdesc(self):
2731 def fastdesc(self):
2738 it = self._subset.fastdesc
2732 it = self._subset.fastdesc
2739 if it is None:
2733 if it is None:
2740 return None
2734 return None
2741 return lambda: self._iterfilter(it())
2735 return lambda: self._iterfilter(it())
2742
2736
2743 def __nonzero__(self):
2737 def __nonzero__(self):
2744 fast = self.fastasc
2738 fast = self.fastasc
2745 if fast is None:
2739 if fast is None:
2746 fast = self.fastdesc
2740 fast = self.fastdesc
2747 if fast is not None:
2741 if fast is not None:
2748 it = fast()
2742 it = fast()
2749 else:
2743 else:
2750 it = self
2744 it = self
2751
2745
2752 for r in it:
2746 for r in it:
2753 return True
2747 return True
2754 return False
2748 return False
2755
2749
2756 def __len__(self):
2750 def __len__(self):
2757 # Basic implementation to be changed in future patches.
2751 # Basic implementation to be changed in future patches.
2758 # until this gets improved, we use generator expression
2752 # until this gets improved, we use generator expression
2759 # here, since list compr is free to call __len__ again
2753 # here, since list compr is free to call __len__ again
2760 # causing infinite recursion
2754 # causing infinite recursion
2761 l = baseset(r for r in self)
2755 l = baseset(r for r in self)
2762 return len(l)
2756 return len(l)
2763
2757
2764 def sort(self, reverse=False):
2758 def sort(self, reverse=False):
2765 self._subset.sort(reverse=reverse)
2759 self._subset.sort(reverse=reverse)
2766
2760
2767 def reverse(self):
2761 def reverse(self):
2768 self._subset.reverse()
2762 self._subset.reverse()
2769
2763
2770 def isascending(self):
2764 def isascending(self):
2771 return self._subset.isascending()
2765 return self._subset.isascending()
2772
2766
2773 def isdescending(self):
2767 def isdescending(self):
2774 return self._subset.isdescending()
2768 return self._subset.isdescending()
2775
2769
2776 def first(self):
2770 def first(self):
2777 for x in self:
2771 for x in self:
2778 return x
2772 return x
2779 return None
2773 return None
2780
2774
2781 def last(self):
2775 def last(self):
2782 it = None
2776 it = None
2783 if self.isascending():
2777 if self.isascending():
2784 it = self.fastdesc
2778 it = self.fastdesc
2785 elif self.isdescending():
2779 elif self.isdescending():
2786 it = self.fastasc
2780 it = self.fastasc
2787 if it is not None:
2781 if it is not None:
2788 for x in it():
2782 for x in it():
2789 return x
2783 return x
2790 return None #empty case
2784 return None #empty case
2791 else:
2785 else:
2792 x = None
2786 x = None
2793 for x in self:
2787 for x in self:
2794 pass
2788 pass
2795 return x
2789 return x
2796
2790
2797 def __repr__(self):
2791 def __repr__(self):
2798 xs = [repr(self._subset)]
2792 xs = [repr(self._subset)]
2799 s = _formatsetrepr(self._condrepr)
2793 s = _formatsetrepr(self._condrepr)
2800 if s:
2794 if s:
2801 xs.append(s)
2795 xs.append(s)
2802 return '<%s %s>' % (type(self).__name__, ', '.join(xs))
2796 return '<%s %s>' % (type(self).__name__, ', '.join(xs))
2803
2797
2804 def _iterordered(ascending, iter1, iter2):
2798 def _iterordered(ascending, iter1, iter2):
2805 """produce an ordered iteration from two iterators with the same order
2799 """produce an ordered iteration from two iterators with the same order
2806
2800
2807 The ascending is used to indicated the iteration direction.
2801 The ascending is used to indicated the iteration direction.
2808 """
2802 """
2809 choice = max
2803 choice = max
2810 if ascending:
2804 if ascending:
2811 choice = min
2805 choice = min
2812
2806
2813 val1 = None
2807 val1 = None
2814 val2 = None
2808 val2 = None
2815 try:
2809 try:
2816 # Consume both iterators in an ordered way until one is empty
2810 # Consume both iterators in an ordered way until one is empty
2817 while True:
2811 while True:
2818 if val1 is None:
2812 if val1 is None:
2819 val1 = iter1.next()
2813 val1 = iter1.next()
2820 if val2 is None:
2814 if val2 is None:
2821 val2 = iter2.next()
2815 val2 = iter2.next()
2822 next = choice(val1, val2)
2816 next = choice(val1, val2)
2823 yield next
2817 yield next
2824 if val1 == next:
2818 if val1 == next:
2825 val1 = None
2819 val1 = None
2826 if val2 == next:
2820 if val2 == next:
2827 val2 = None
2821 val2 = None
2828 except StopIteration:
2822 except StopIteration:
2829 # Flush any remaining values and consume the other one
2823 # Flush any remaining values and consume the other one
2830 it = iter2
2824 it = iter2
2831 if val1 is not None:
2825 if val1 is not None:
2832 yield val1
2826 yield val1
2833 it = iter1
2827 it = iter1
2834 elif val2 is not None:
2828 elif val2 is not None:
2835 # might have been equality and both are empty
2829 # might have been equality and both are empty
2836 yield val2
2830 yield val2
2837 for val in it:
2831 for val in it:
2838 yield val
2832 yield val
2839
2833
2840 class addset(abstractsmartset):
2834 class addset(abstractsmartset):
2841 """Represent the addition of two sets
2835 """Represent the addition of two sets
2842
2836
2843 Wrapper structure for lazily adding two structures without losing much
2837 Wrapper structure for lazily adding two structures without losing much
2844 performance on the __contains__ method
2838 performance on the __contains__ method
2845
2839
2846 If the ascending attribute is set, that means the two structures are
2840 If the ascending attribute is set, that means the two structures are
2847 ordered in either an ascending or descending way. Therefore, we can add
2841 ordered in either an ascending or descending way. Therefore, we can add
2848 them maintaining the order by iterating over both at the same time
2842 them maintaining the order by iterating over both at the same time
2849
2843
2850 >>> xs = baseset([0, 3, 2])
2844 >>> xs = baseset([0, 3, 2])
2851 >>> ys = baseset([5, 2, 4])
2845 >>> ys = baseset([5, 2, 4])
2852
2846
2853 >>> rs = addset(xs, ys)
2847 >>> rs = addset(xs, ys)
2854 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
2848 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
2855 (True, True, False, True, 0, 4)
2849 (True, True, False, True, 0, 4)
2856 >>> rs = addset(xs, baseset([]))
2850 >>> rs = addset(xs, baseset([]))
2857 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
2851 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
2858 (True, True, False, 0, 2)
2852 (True, True, False, 0, 2)
2859 >>> rs = addset(baseset([]), baseset([]))
2853 >>> rs = addset(baseset([]), baseset([]))
2860 >>> bool(rs), 0 in rs, rs.first(), rs.last()
2854 >>> bool(rs), 0 in rs, rs.first(), rs.last()
2861 (False, False, None, None)
2855 (False, False, None, None)
2862
2856
2863 iterate unsorted:
2857 iterate unsorted:
2864 >>> rs = addset(xs, ys)
2858 >>> rs = addset(xs, ys)
2865 >>> # (use generator because pypy could call len())
2859 >>> # (use generator because pypy could call len())
2866 >>> list(x for x in rs) # without _genlist
2860 >>> list(x for x in rs) # without _genlist
2867 [0, 3, 2, 5, 4]
2861 [0, 3, 2, 5, 4]
2868 >>> assert not rs._genlist
2862 >>> assert not rs._genlist
2869 >>> len(rs)
2863 >>> len(rs)
2870 5
2864 5
2871 >>> [x for x in rs] # with _genlist
2865 >>> [x for x in rs] # with _genlist
2872 [0, 3, 2, 5, 4]
2866 [0, 3, 2, 5, 4]
2873 >>> assert rs._genlist
2867 >>> assert rs._genlist
2874
2868
2875 iterate ascending:
2869 iterate ascending:
2876 >>> rs = addset(xs, ys, ascending=True)
2870 >>> rs = addset(xs, ys, ascending=True)
2877 >>> # (use generator because pypy could call len())
2871 >>> # (use generator because pypy could call len())
2878 >>> list(x for x in rs), list(x for x in rs.fastasc()) # without _asclist
2872 >>> list(x for x in rs), list(x for x in rs.fastasc()) # without _asclist
2879 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
2873 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
2880 >>> assert not rs._asclist
2874 >>> assert not rs._asclist
2881 >>> len(rs)
2875 >>> len(rs)
2882 5
2876 5
2883 >>> [x for x in rs], [x for x in rs.fastasc()]
2877 >>> [x for x in rs], [x for x in rs.fastasc()]
2884 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
2878 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
2885 >>> assert rs._asclist
2879 >>> assert rs._asclist
2886
2880
2887 iterate descending:
2881 iterate descending:
2888 >>> rs = addset(xs, ys, ascending=False)
2882 >>> rs = addset(xs, ys, ascending=False)
2889 >>> # (use generator because pypy could call len())
2883 >>> # (use generator because pypy could call len())
2890 >>> list(x for x in rs), list(x for x in rs.fastdesc()) # without _asclist
2884 >>> list(x for x in rs), list(x for x in rs.fastdesc()) # without _asclist
2891 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
2885 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
2892 >>> assert not rs._asclist
2886 >>> assert not rs._asclist
2893 >>> len(rs)
2887 >>> len(rs)
2894 5
2888 5
2895 >>> [x for x in rs], [x for x in rs.fastdesc()]
2889 >>> [x for x in rs], [x for x in rs.fastdesc()]
2896 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
2890 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
2897 >>> assert rs._asclist
2891 >>> assert rs._asclist
2898
2892
2899 iterate ascending without fastasc:
2893 iterate ascending without fastasc:
2900 >>> rs = addset(xs, generatorset(ys), ascending=True)
2894 >>> rs = addset(xs, generatorset(ys), ascending=True)
2901 >>> assert rs.fastasc is None
2895 >>> assert rs.fastasc is None
2902 >>> [x for x in rs]
2896 >>> [x for x in rs]
2903 [0, 2, 3, 4, 5]
2897 [0, 2, 3, 4, 5]
2904
2898
2905 iterate descending without fastdesc:
2899 iterate descending without fastdesc:
2906 >>> rs = addset(generatorset(xs), ys, ascending=False)
2900 >>> rs = addset(generatorset(xs), ys, ascending=False)
2907 >>> assert rs.fastdesc is None
2901 >>> assert rs.fastdesc is None
2908 >>> [x for x in rs]
2902 >>> [x for x in rs]
2909 [5, 4, 3, 2, 0]
2903 [5, 4, 3, 2, 0]
2910 """
2904 """
2911 def __init__(self, revs1, revs2, ascending=None):
2905 def __init__(self, revs1, revs2, ascending=None):
2912 self._r1 = revs1
2906 self._r1 = revs1
2913 self._r2 = revs2
2907 self._r2 = revs2
2914 self._iter = None
2908 self._iter = None
2915 self._ascending = ascending
2909 self._ascending = ascending
2916 self._genlist = None
2910 self._genlist = None
2917 self._asclist = None
2911 self._asclist = None
2918
2912
2919 def __len__(self):
2913 def __len__(self):
2920 return len(self._list)
2914 return len(self._list)
2921
2915
2922 def __nonzero__(self):
2916 def __nonzero__(self):
2923 return bool(self._r1) or bool(self._r2)
2917 return bool(self._r1) or bool(self._r2)
2924
2918
2925 @util.propertycache
2919 @util.propertycache
2926 def _list(self):
2920 def _list(self):
2927 if not self._genlist:
2921 if not self._genlist:
2928 self._genlist = baseset(iter(self))
2922 self._genlist = baseset(iter(self))
2929 return self._genlist
2923 return self._genlist
2930
2924
2931 def __iter__(self):
2925 def __iter__(self):
2932 """Iterate over both collections without repeating elements
2926 """Iterate over both collections without repeating elements
2933
2927
2934 If the ascending attribute is not set, iterate over the first one and
2928 If the ascending attribute is not set, iterate over the first one and
2935 then over the second one checking for membership on the first one so we
2929 then over the second one checking for membership on the first one so we
2936 dont yield any duplicates.
2930 dont yield any duplicates.
2937
2931
2938 If the ascending attribute is set, iterate over both collections at the
2932 If the ascending attribute is set, iterate over both collections at the
2939 same time, yielding only one value at a time in the given order.
2933 same time, yielding only one value at a time in the given order.
2940 """
2934 """
2941 if self._ascending is None:
2935 if self._ascending is None:
2942 if self._genlist:
2936 if self._genlist:
2943 return iter(self._genlist)
2937 return iter(self._genlist)
2944 def arbitraryordergen():
2938 def arbitraryordergen():
2945 for r in self._r1:
2939 for r in self._r1:
2946 yield r
2940 yield r
2947 inr1 = self._r1.__contains__
2941 inr1 = self._r1.__contains__
2948 for r in self._r2:
2942 for r in self._r2:
2949 if not inr1(r):
2943 if not inr1(r):
2950 yield r
2944 yield r
2951 return arbitraryordergen()
2945 return arbitraryordergen()
2952 # try to use our own fast iterator if it exists
2946 # try to use our own fast iterator if it exists
2953 self._trysetasclist()
2947 self._trysetasclist()
2954 if self._ascending:
2948 if self._ascending:
2955 attr = 'fastasc'
2949 attr = 'fastasc'
2956 else:
2950 else:
2957 attr = 'fastdesc'
2951 attr = 'fastdesc'
2958 it = getattr(self, attr)
2952 it = getattr(self, attr)
2959 if it is not None:
2953 if it is not None:
2960 return it()
2954 return it()
2961 # maybe half of the component supports fast
2955 # maybe half of the component supports fast
2962 # get iterator for _r1
2956 # get iterator for _r1
2963 iter1 = getattr(self._r1, attr)
2957 iter1 = getattr(self._r1, attr)
2964 if iter1 is None:
2958 if iter1 is None:
2965 # let's avoid side effect (not sure it matters)
2959 # let's avoid side effect (not sure it matters)
2966 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
2960 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
2967 else:
2961 else:
2968 iter1 = iter1()
2962 iter1 = iter1()
2969 # get iterator for _r2
2963 # get iterator for _r2
2970 iter2 = getattr(self._r2, attr)
2964 iter2 = getattr(self._r2, attr)
2971 if iter2 is None:
2965 if iter2 is None:
2972 # let's avoid side effect (not sure it matters)
2966 # let's avoid side effect (not sure it matters)
2973 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
2967 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
2974 else:
2968 else:
2975 iter2 = iter2()
2969 iter2 = iter2()
2976 return _iterordered(self._ascending, iter1, iter2)
2970 return _iterordered(self._ascending, iter1, iter2)
2977
2971
2978 def _trysetasclist(self):
2972 def _trysetasclist(self):
2979 """populate the _asclist attribute if possible and necessary"""
2973 """populate the _asclist attribute if possible and necessary"""
2980 if self._genlist is not None and self._asclist is None:
2974 if self._genlist is not None and self._asclist is None:
2981 self._asclist = sorted(self._genlist)
2975 self._asclist = sorted(self._genlist)
2982
2976
2983 @property
2977 @property
2984 def fastasc(self):
2978 def fastasc(self):
2985 self._trysetasclist()
2979 self._trysetasclist()
2986 if self._asclist is not None:
2980 if self._asclist is not None:
2987 return self._asclist.__iter__
2981 return self._asclist.__iter__
2988 iter1 = self._r1.fastasc
2982 iter1 = self._r1.fastasc
2989 iter2 = self._r2.fastasc
2983 iter2 = self._r2.fastasc
2990 if None in (iter1, iter2):
2984 if None in (iter1, iter2):
2991 return None
2985 return None
2992 return lambda: _iterordered(True, iter1(), iter2())
2986 return lambda: _iterordered(True, iter1(), iter2())
2993
2987
2994 @property
2988 @property
2995 def fastdesc(self):
2989 def fastdesc(self):
2996 self._trysetasclist()
2990 self._trysetasclist()
2997 if self._asclist is not None:
2991 if self._asclist is not None:
2998 return self._asclist.__reversed__
2992 return self._asclist.__reversed__
2999 iter1 = self._r1.fastdesc
2993 iter1 = self._r1.fastdesc
3000 iter2 = self._r2.fastdesc
2994 iter2 = self._r2.fastdesc
3001 if None in (iter1, iter2):
2995 if None in (iter1, iter2):
3002 return None
2996 return None
3003 return lambda: _iterordered(False, iter1(), iter2())
2997 return lambda: _iterordered(False, iter1(), iter2())
3004
2998
3005 def __contains__(self, x):
2999 def __contains__(self, x):
3006 return x in self._r1 or x in self._r2
3000 return x in self._r1 or x in self._r2
3007
3001
3008 def sort(self, reverse=False):
3002 def sort(self, reverse=False):
3009 """Sort the added set
3003 """Sort the added set
3010
3004
3011 For this we use the cached list with all the generated values and if we
3005 For this we use the cached list with all the generated values and if we
3012 know they are ascending or descending we can sort them in a smart way.
3006 know they are ascending or descending we can sort them in a smart way.
3013 """
3007 """
3014 self._ascending = not reverse
3008 self._ascending = not reverse
3015
3009
3016 def isascending(self):
3010 def isascending(self):
3017 return self._ascending is not None and self._ascending
3011 return self._ascending is not None and self._ascending
3018
3012
3019 def isdescending(self):
3013 def isdescending(self):
3020 return self._ascending is not None and not self._ascending
3014 return self._ascending is not None and not self._ascending
3021
3015
3022 def reverse(self):
3016 def reverse(self):
3023 if self._ascending is None:
3017 if self._ascending is None:
3024 self._list.reverse()
3018 self._list.reverse()
3025 else:
3019 else:
3026 self._ascending = not self._ascending
3020 self._ascending = not self._ascending
3027
3021
3028 def first(self):
3022 def first(self):
3029 for x in self:
3023 for x in self:
3030 return x
3024 return x
3031 return None
3025 return None
3032
3026
3033 def last(self):
3027 def last(self):
3034 self.reverse()
3028 self.reverse()
3035 val = self.first()
3029 val = self.first()
3036 self.reverse()
3030 self.reverse()
3037 return val
3031 return val
3038
3032
3039 def __repr__(self):
3033 def __repr__(self):
3040 d = {None: '', False: '-', True: '+'}[self._ascending]
3034 d = {None: '', False: '-', True: '+'}[self._ascending]
3041 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
3035 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
3042
3036
3043 class generatorset(abstractsmartset):
3037 class generatorset(abstractsmartset):
3044 """Wrap a generator for lazy iteration
3038 """Wrap a generator for lazy iteration
3045
3039
3046 Wrapper structure for generators that provides lazy membership and can
3040 Wrapper structure for generators that provides lazy membership and can
3047 be iterated more than once.
3041 be iterated more than once.
3048 When asked for membership it generates values until either it finds the
3042 When asked for membership it generates values until either it finds the
3049 requested one or has gone through all the elements in the generator
3043 requested one or has gone through all the elements in the generator
3050 """
3044 """
3051 def __init__(self, gen, iterasc=None):
3045 def __init__(self, gen, iterasc=None):
3052 """
3046 """
3053 gen: a generator producing the values for the generatorset.
3047 gen: a generator producing the values for the generatorset.
3054 """
3048 """
3055 self._gen = gen
3049 self._gen = gen
3056 self._asclist = None
3050 self._asclist = None
3057 self._cache = {}
3051 self._cache = {}
3058 self._genlist = []
3052 self._genlist = []
3059 self._finished = False
3053 self._finished = False
3060 self._ascending = True
3054 self._ascending = True
3061 if iterasc is not None:
3055 if iterasc is not None:
3062 if iterasc:
3056 if iterasc:
3063 self.fastasc = self._iterator
3057 self.fastasc = self._iterator
3064 self.__contains__ = self._asccontains
3058 self.__contains__ = self._asccontains
3065 else:
3059 else:
3066 self.fastdesc = self._iterator
3060 self.fastdesc = self._iterator
3067 self.__contains__ = self._desccontains
3061 self.__contains__ = self._desccontains
3068
3062
3069 def __nonzero__(self):
3063 def __nonzero__(self):
3070 # Do not use 'for r in self' because it will enforce the iteration
3064 # Do not use 'for r in self' because it will enforce the iteration
3071 # order (default ascending), possibly unrolling a whole descending
3065 # order (default ascending), possibly unrolling a whole descending
3072 # iterator.
3066 # iterator.
3073 if self._genlist:
3067 if self._genlist:
3074 return True
3068 return True
3075 for r in self._consumegen():
3069 for r in self._consumegen():
3076 return True
3070 return True
3077 return False
3071 return False
3078
3072
3079 def __contains__(self, x):
3073 def __contains__(self, x):
3080 if x in self._cache:
3074 if x in self._cache:
3081 return self._cache[x]
3075 return self._cache[x]
3082
3076
3083 # Use new values only, as existing values would be cached.
3077 # Use new values only, as existing values would be cached.
3084 for l in self._consumegen():
3078 for l in self._consumegen():
3085 if l == x:
3079 if l == x:
3086 return True
3080 return True
3087
3081
3088 self._cache[x] = False
3082 self._cache[x] = False
3089 return False
3083 return False
3090
3084
3091 def _asccontains(self, x):
3085 def _asccontains(self, x):
3092 """version of contains optimised for ascending generator"""
3086 """version of contains optimised for ascending generator"""
3093 if x in self._cache:
3087 if x in self._cache:
3094 return self._cache[x]
3088 return self._cache[x]
3095
3089
3096 # Use new values only, as existing values would be cached.
3090 # Use new values only, as existing values would be cached.
3097 for l in self._consumegen():
3091 for l in self._consumegen():
3098 if l == x:
3092 if l == x:
3099 return True
3093 return True
3100 if l > x:
3094 if l > x:
3101 break
3095 break
3102
3096
3103 self._cache[x] = False
3097 self._cache[x] = False
3104 return False
3098 return False
3105
3099
3106 def _desccontains(self, x):
3100 def _desccontains(self, x):
3107 """version of contains optimised for descending generator"""
3101 """version of contains optimised for descending generator"""
3108 if x in self._cache:
3102 if x in self._cache:
3109 return self._cache[x]
3103 return self._cache[x]
3110
3104
3111 # Use new values only, as existing values would be cached.
3105 # Use new values only, as existing values would be cached.
3112 for l in self._consumegen():
3106 for l in self._consumegen():
3113 if l == x:
3107 if l == x:
3114 return True
3108 return True
3115 if l < x:
3109 if l < x:
3116 break
3110 break
3117
3111
3118 self._cache[x] = False
3112 self._cache[x] = False
3119 return False
3113 return False
3120
3114
3121 def __iter__(self):
3115 def __iter__(self):
3122 if self._ascending:
3116 if self._ascending:
3123 it = self.fastasc
3117 it = self.fastasc
3124 else:
3118 else:
3125 it = self.fastdesc
3119 it = self.fastdesc
3126 if it is not None:
3120 if it is not None:
3127 return it()
3121 return it()
3128 # we need to consume the iterator
3122 # we need to consume the iterator
3129 for x in self._consumegen():
3123 for x in self._consumegen():
3130 pass
3124 pass
3131 # recall the same code
3125 # recall the same code
3132 return iter(self)
3126 return iter(self)
3133
3127
3134 def _iterator(self):
3128 def _iterator(self):
3135 if self._finished:
3129 if self._finished:
3136 return iter(self._genlist)
3130 return iter(self._genlist)
3137
3131
3138 # We have to use this complex iteration strategy to allow multiple
3132 # We have to use this complex iteration strategy to allow multiple
3139 # iterations at the same time. We need to be able to catch revision
3133 # iterations at the same time. We need to be able to catch revision
3140 # removed from _consumegen and added to genlist in another instance.
3134 # removed from _consumegen and added to genlist in another instance.
3141 #
3135 #
3142 # Getting rid of it would provide an about 15% speed up on this
3136 # Getting rid of it would provide an about 15% speed up on this
3143 # iteration.
3137 # iteration.
3144 genlist = self._genlist
3138 genlist = self._genlist
3145 nextrev = self._consumegen().next
3139 nextrev = self._consumegen().next
3146 _len = len # cache global lookup
3140 _len = len # cache global lookup
3147 def gen():
3141 def gen():
3148 i = 0
3142 i = 0
3149 while True:
3143 while True:
3150 if i < _len(genlist):
3144 if i < _len(genlist):
3151 yield genlist[i]
3145 yield genlist[i]
3152 else:
3146 else:
3153 yield nextrev()
3147 yield nextrev()
3154 i += 1
3148 i += 1
3155 return gen()
3149 return gen()
3156
3150
3157 def _consumegen(self):
3151 def _consumegen(self):
3158 cache = self._cache
3152 cache = self._cache
3159 genlist = self._genlist.append
3153 genlist = self._genlist.append
3160 for item in self._gen:
3154 for item in self._gen:
3161 cache[item] = True
3155 cache[item] = True
3162 genlist(item)
3156 genlist(item)
3163 yield item
3157 yield item
3164 if not self._finished:
3158 if not self._finished:
3165 self._finished = True
3159 self._finished = True
3166 asc = self._genlist[:]
3160 asc = self._genlist[:]
3167 asc.sort()
3161 asc.sort()
3168 self._asclist = asc
3162 self._asclist = asc
3169 self.fastasc = asc.__iter__
3163 self.fastasc = asc.__iter__
3170 self.fastdesc = asc.__reversed__
3164 self.fastdesc = asc.__reversed__
3171
3165
3172 def __len__(self):
3166 def __len__(self):
3173 for x in self._consumegen():
3167 for x in self._consumegen():
3174 pass
3168 pass
3175 return len(self._genlist)
3169 return len(self._genlist)
3176
3170
3177 def sort(self, reverse=False):
3171 def sort(self, reverse=False):
3178 self._ascending = not reverse
3172 self._ascending = not reverse
3179
3173
3180 def reverse(self):
3174 def reverse(self):
3181 self._ascending = not self._ascending
3175 self._ascending = not self._ascending
3182
3176
3183 def isascending(self):
3177 def isascending(self):
3184 return self._ascending
3178 return self._ascending
3185
3179
3186 def isdescending(self):
3180 def isdescending(self):
3187 return not self._ascending
3181 return not self._ascending
3188
3182
3189 def first(self):
3183 def first(self):
3190 if self._ascending:
3184 if self._ascending:
3191 it = self.fastasc
3185 it = self.fastasc
3192 else:
3186 else:
3193 it = self.fastdesc
3187 it = self.fastdesc
3194 if it is None:
3188 if it is None:
3195 # we need to consume all and try again
3189 # we need to consume all and try again
3196 for x in self._consumegen():
3190 for x in self._consumegen():
3197 pass
3191 pass
3198 return self.first()
3192 return self.first()
3199 return next(it(), None)
3193 return next(it(), None)
3200
3194
3201 def last(self):
3195 def last(self):
3202 if self._ascending:
3196 if self._ascending:
3203 it = self.fastdesc
3197 it = self.fastdesc
3204 else:
3198 else:
3205 it = self.fastasc
3199 it = self.fastasc
3206 if it is None:
3200 if it is None:
3207 # we need to consume all and try again
3201 # we need to consume all and try again
3208 for x in self._consumegen():
3202 for x in self._consumegen():
3209 pass
3203 pass
3210 return self.first()
3204 return self.first()
3211 return next(it(), None)
3205 return next(it(), None)
3212
3206
3213 def __repr__(self):
3207 def __repr__(self):
3214 d = {False: '-', True: '+'}[self._ascending]
3208 d = {False: '-', True: '+'}[self._ascending]
3215 return '<%s%s>' % (type(self).__name__, d)
3209 return '<%s%s>' % (type(self).__name__, d)
3216
3210
3217 class spanset(abstractsmartset):
3211 class spanset(abstractsmartset):
3218 """Duck type for baseset class which represents a range of revisions and
3212 """Duck type for baseset class which represents a range of revisions and
3219 can work lazily and without having all the range in memory
3213 can work lazily and without having all the range in memory
3220
3214
3221 Note that spanset(x, y) behave almost like xrange(x, y) except for two
3215 Note that spanset(x, y) behave almost like xrange(x, y) except for two
3222 notable points:
3216 notable points:
3223 - when x < y it will be automatically descending,
3217 - when x < y it will be automatically descending,
3224 - revision filtered with this repoview will be skipped.
3218 - revision filtered with this repoview will be skipped.
3225
3219
3226 """
3220 """
3227 def __init__(self, repo, start=0, end=None):
3221 def __init__(self, repo, start=0, end=None):
3228 """
3222 """
3229 start: first revision included the set
3223 start: first revision included the set
3230 (default to 0)
3224 (default to 0)
3231 end: first revision excluded (last+1)
3225 end: first revision excluded (last+1)
3232 (default to len(repo)
3226 (default to len(repo)
3233
3227
3234 Spanset will be descending if `end` < `start`.
3228 Spanset will be descending if `end` < `start`.
3235 """
3229 """
3236 if end is None:
3230 if end is None:
3237 end = len(repo)
3231 end = len(repo)
3238 self._ascending = start <= end
3232 self._ascending = start <= end
3239 if not self._ascending:
3233 if not self._ascending:
3240 start, end = end + 1, start +1
3234 start, end = end + 1, start +1
3241 self._start = start
3235 self._start = start
3242 self._end = end
3236 self._end = end
3243 self._hiddenrevs = repo.changelog.filteredrevs
3237 self._hiddenrevs = repo.changelog.filteredrevs
3244
3238
3245 def sort(self, reverse=False):
3239 def sort(self, reverse=False):
3246 self._ascending = not reverse
3240 self._ascending = not reverse
3247
3241
3248 def reverse(self):
3242 def reverse(self):
3249 self._ascending = not self._ascending
3243 self._ascending = not self._ascending
3250
3244
3251 def _iterfilter(self, iterrange):
3245 def _iterfilter(self, iterrange):
3252 s = self._hiddenrevs
3246 s = self._hiddenrevs
3253 for r in iterrange:
3247 for r in iterrange:
3254 if r not in s:
3248 if r not in s:
3255 yield r
3249 yield r
3256
3250
3257 def __iter__(self):
3251 def __iter__(self):
3258 if self._ascending:
3252 if self._ascending:
3259 return self.fastasc()
3253 return self.fastasc()
3260 else:
3254 else:
3261 return self.fastdesc()
3255 return self.fastdesc()
3262
3256
3263 def fastasc(self):
3257 def fastasc(self):
3264 iterrange = xrange(self._start, self._end)
3258 iterrange = xrange(self._start, self._end)
3265 if self._hiddenrevs:
3259 if self._hiddenrevs:
3266 return self._iterfilter(iterrange)
3260 return self._iterfilter(iterrange)
3267 return iter(iterrange)
3261 return iter(iterrange)
3268
3262
3269 def fastdesc(self):
3263 def fastdesc(self):
3270 iterrange = xrange(self._end - 1, self._start - 1, -1)
3264 iterrange = xrange(self._end - 1, self._start - 1, -1)
3271 if self._hiddenrevs:
3265 if self._hiddenrevs:
3272 return self._iterfilter(iterrange)
3266 return self._iterfilter(iterrange)
3273 return iter(iterrange)
3267 return iter(iterrange)
3274
3268
3275 def __contains__(self, rev):
3269 def __contains__(self, rev):
3276 hidden = self._hiddenrevs
3270 hidden = self._hiddenrevs
3277 return ((self._start <= rev < self._end)
3271 return ((self._start <= rev < self._end)
3278 and not (hidden and rev in hidden))
3272 and not (hidden and rev in hidden))
3279
3273
3280 def __nonzero__(self):
3274 def __nonzero__(self):
3281 for r in self:
3275 for r in self:
3282 return True
3276 return True
3283 return False
3277 return False
3284
3278
3285 def __len__(self):
3279 def __len__(self):
3286 if not self._hiddenrevs:
3280 if not self._hiddenrevs:
3287 return abs(self._end - self._start)
3281 return abs(self._end - self._start)
3288 else:
3282 else:
3289 count = 0
3283 count = 0
3290 start = self._start
3284 start = self._start
3291 end = self._end
3285 end = self._end
3292 for rev in self._hiddenrevs:
3286 for rev in self._hiddenrevs:
3293 if (end < rev <= start) or (start <= rev < end):
3287 if (end < rev <= start) or (start <= rev < end):
3294 count += 1
3288 count += 1
3295 return abs(self._end - self._start) - count
3289 return abs(self._end - self._start) - count
3296
3290
3297 def isascending(self):
3291 def isascending(self):
3298 return self._ascending
3292 return self._ascending
3299
3293
3300 def isdescending(self):
3294 def isdescending(self):
3301 return not self._ascending
3295 return not self._ascending
3302
3296
3303 def first(self):
3297 def first(self):
3304 if self._ascending:
3298 if self._ascending:
3305 it = self.fastasc
3299 it = self.fastasc
3306 else:
3300 else:
3307 it = self.fastdesc
3301 it = self.fastdesc
3308 for x in it():
3302 for x in it():
3309 return x
3303 return x
3310 return None
3304 return None
3311
3305
3312 def last(self):
3306 def last(self):
3313 if self._ascending:
3307 if self._ascending:
3314 it = self.fastdesc
3308 it = self.fastdesc
3315 else:
3309 else:
3316 it = self.fastasc
3310 it = self.fastasc
3317 for x in it():
3311 for x in it():
3318 return x
3312 return x
3319 return None
3313 return None
3320
3314
3321 def __repr__(self):
3315 def __repr__(self):
3322 d = {False: '-', True: '+'}[self._ascending]
3316 d = {False: '-', True: '+'}[self._ascending]
3323 return '<%s%s %d:%d>' % (type(self).__name__, d,
3317 return '<%s%s %d:%d>' % (type(self).__name__, d,
3324 self._start, self._end - 1)
3318 self._start, self._end - 1)
3325
3319
3326 class fullreposet(spanset):
3320 class fullreposet(spanset):
3327 """a set containing all revisions in the repo
3321 """a set containing all revisions in the repo
3328
3322
3329 This class exists to host special optimization and magic to handle virtual
3323 This class exists to host special optimization and magic to handle virtual
3330 revisions such as "null".
3324 revisions such as "null".
3331 """
3325 """
3332
3326
3333 def __init__(self, repo):
3327 def __init__(self, repo):
3334 super(fullreposet, self).__init__(repo)
3328 super(fullreposet, self).__init__(repo)
3335
3329
3336 def __and__(self, other):
3330 def __and__(self, other):
3337 """As self contains the whole repo, all of the other set should also be
3331 """As self contains the whole repo, all of the other set should also be
3338 in self. Therefore `self & other = other`.
3332 in self. Therefore `self & other = other`.
3339
3333
3340 This boldly assumes the other contains valid revs only.
3334 This boldly assumes the other contains valid revs only.
3341 """
3335 """
3342 # other not a smartset, make is so
3336 # other not a smartset, make is so
3343 if not util.safehasattr(other, 'isascending'):
3337 if not util.safehasattr(other, 'isascending'):
3344 # filter out hidden revision
3338 # filter out hidden revision
3345 # (this boldly assumes all smartset are pure)
3339 # (this boldly assumes all smartset are pure)
3346 #
3340 #
3347 # `other` was used with "&", let's assume this is a set like
3341 # `other` was used with "&", let's assume this is a set like
3348 # object.
3342 # object.
3349 other = baseset(other - self._hiddenrevs)
3343 other = baseset(other - self._hiddenrevs)
3350
3344
3351 # XXX As fullreposet is also used as bootstrap, this is wrong.
3345 # XXX As fullreposet is also used as bootstrap, this is wrong.
3352 #
3346 #
3353 # With a giveme312() revset returning [3,1,2], this makes
3347 # With a giveme312() revset returning [3,1,2], this makes
3354 # 'hg log -r "giveme312()"' -> 1, 2, 3 (wrong)
3348 # 'hg log -r "giveme312()"' -> 1, 2, 3 (wrong)
3355 # We cannot just drop it because other usage still need to sort it:
3349 # We cannot just drop it because other usage still need to sort it:
3356 # 'hg log -r "all() and giveme312()"' -> 1, 2, 3 (right)
3350 # 'hg log -r "all() and giveme312()"' -> 1, 2, 3 (right)
3357 #
3351 #
3358 # There is also some faulty revset implementations that rely on it
3352 # There is also some faulty revset implementations that rely on it
3359 # (eg: children as of its state in e8075329c5fb)
3353 # (eg: children as of its state in e8075329c5fb)
3360 #
3354 #
3361 # When we fix the two points above we can move this into the if clause
3355 # When we fix the two points above we can move this into the if clause
3362 other.sort(reverse=self.isdescending())
3356 other.sort(reverse=self.isdescending())
3363 return other
3357 return other
3364
3358
3365 def prettyformatset(revs):
3359 def prettyformatset(revs):
3366 lines = []
3360 lines = []
3367 rs = repr(revs)
3361 rs = repr(revs)
3368 p = 0
3362 p = 0
3369 while p < len(rs):
3363 while p < len(rs):
3370 q = rs.find('<', p + 1)
3364 q = rs.find('<', p + 1)
3371 if q < 0:
3365 if q < 0:
3372 q = len(rs)
3366 q = len(rs)
3373 l = rs.count('<', 0, p) - rs.count('>', 0, p)
3367 l = rs.count('<', 0, p) - rs.count('>', 0, p)
3374 assert l >= 0
3368 assert l >= 0
3375 lines.append((l, rs[p:q].rstrip()))
3369 lines.append((l, rs[p:q].rstrip()))
3376 p = q
3370 p = q
3377 return '\n'.join(' ' * l + s for l, s in lines)
3371 return '\n'.join(' ' * l + s for l, s in lines)
3378
3372
3379 def loadpredicate(ui, extname, registrarobj):
3373 def loadpredicate(ui, extname, registrarobj):
3380 """Load revset predicates from specified registrarobj
3374 """Load revset predicates from specified registrarobj
3381 """
3375 """
3382 for name, func in registrarobj._table.iteritems():
3376 for name, func in registrarobj._table.iteritems():
3383 symbols[name] = func
3377 symbols[name] = func
3384 if func._safe:
3378 if func._safe:
3385 safesymbols.add(name)
3379 safesymbols.add(name)
3386
3380
3387 # load built-in predicates explicitly to setup safesymbols
3381 # load built-in predicates explicitly to setup safesymbols
3388 loadpredicate(None, None, predicate)
3382 loadpredicate(None, None, predicate)
3389
3383
3390 # tell hggettext to extract docstrings from these functions:
3384 # tell hggettext to extract docstrings from these functions:
3391 i18nfunctions = symbols.values()
3385 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now