##// END OF EJS Templates
revset: unindent "if True" block in sort()...
Yuya Nishihara -
r29002:ea794f2e stable
parent child Browse files
Show More
@@ -1,3392 +1,3391 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 if True:
1871 for k in reversed(keys):
1872 for k in reversed(keys):
1872 if k == 'rev':
1873 if k == 'rev':
1873 ctxs.sort(key=lambda c: c.rev())
1874 ctxs.sort(key=lambda c: c.rev())
1874 elif k == '-rev':
1875 elif k == '-rev':
1875 ctxs.sort(key=lambda c: c.rev(), reverse=True)
1876 ctxs.sort(key=lambda c: c.rev(), reverse=True)
1876 elif k == 'branch':
1877 elif k == 'branch':
1877 ctxs.sort(key=lambda c: c.branch())
1878 ctxs.sort(key=lambda c: c.branch())
1878 elif k == '-branch':
1879 elif k == '-branch':
1879 ctxs.sort(key=lambda c: c.branch(), reverse=True)
1880 ctxs.sort(key=lambda c: c.branch(), reverse=True)
1880 elif k == 'desc':
1881 elif k == 'desc':
1881 ctxs.sort(key=lambda c: c.description())
1882 ctxs.sort(key=lambda c: c.description())
1882 elif k == '-desc':
1883 elif k == '-desc':
1883 ctxs.sort(key=lambda c: c.description(), reverse=True)
1884 ctxs.sort(key=lambda c: c.description(), reverse=True)
1884 elif k in 'user author':
1885 elif k in 'user author':
1885 ctxs.sort(key=lambda c: c.user())
1886 ctxs.sort(key=lambda c: c.user())
1886 elif k in '-user -author':
1887 elif k in '-user -author':
1887 ctxs.sort(key=lambda c: c.user(), reverse=True)
1888 ctxs.sort(key=lambda c: c.user(), reverse=True)
1888 elif k == 'date':
1889 elif k == 'date':
1889 ctxs.sort(key=lambda c: c.date()[0])
1890 ctxs.sort(key=lambda c: c.date()[0])
1890 elif k == '-date':
1891 elif k == '-date':
1891 ctxs.sort(key=lambda c: c.date()[0], reverse=True)
1892 ctxs.sort(key=lambda c: c.date()[0], reverse=True)
1892 else:
1893 else:
1893 raise error.ParseError(_("unknown sort key %r") % k)
1894 raise error.ParseError(_("unknown sort key %r") % k)
1895 return baseset([c.rev() for c in ctxs])
1894 return baseset([c.rev() for c in ctxs])
1896
1895
1897 @predicate('subrepo([pattern])')
1896 @predicate('subrepo([pattern])')
1898 def subrepo(repo, subset, x):
1897 def subrepo(repo, subset, x):
1899 """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
1900 pattern is named, any subrepo changes are returned.
1899 pattern is named, any subrepo changes are returned.
1901 """
1900 """
1902 # i18n: "subrepo" is a keyword
1901 # i18n: "subrepo" is a keyword
1903 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
1902 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
1904 pat = None
1903 pat = None
1905 if len(args) != 0:
1904 if len(args) != 0:
1906 pat = getstring(args[0], _("subrepo requires a pattern"))
1905 pat = getstring(args[0], _("subrepo requires a pattern"))
1907
1906
1908 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
1907 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
1909
1908
1910 def submatches(names):
1909 def submatches(names):
1911 k, p, m = util.stringmatcher(pat)
1910 k, p, m = util.stringmatcher(pat)
1912 for name in names:
1911 for name in names:
1913 if m(name):
1912 if m(name):
1914 yield name
1913 yield name
1915
1914
1916 def matches(x):
1915 def matches(x):
1917 c = repo[x]
1916 c = repo[x]
1918 s = repo.status(c.p1().node(), c.node(), match=m)
1917 s = repo.status(c.p1().node(), c.node(), match=m)
1919
1918
1920 if pat is None:
1919 if pat is None:
1921 return s.added or s.modified or s.removed
1920 return s.added or s.modified or s.removed
1922
1921
1923 if s.added:
1922 if s.added:
1924 return any(submatches(c.substate.keys()))
1923 return any(submatches(c.substate.keys()))
1925
1924
1926 if s.modified:
1925 if s.modified:
1927 subs = set(c.p1().substate.keys())
1926 subs = set(c.p1().substate.keys())
1928 subs.update(c.substate.keys())
1927 subs.update(c.substate.keys())
1929
1928
1930 for path in submatches(subs):
1929 for path in submatches(subs):
1931 if c.p1().substate.get(path) != c.substate.get(path):
1930 if c.p1().substate.get(path) != c.substate.get(path):
1932 return True
1931 return True
1933
1932
1934 if s.removed:
1933 if s.removed:
1935 return any(submatches(c.p1().substate.keys()))
1934 return any(submatches(c.p1().substate.keys()))
1936
1935
1937 return False
1936 return False
1938
1937
1939 return subset.filter(matches, condrepr=('<subrepo %r>', pat))
1938 return subset.filter(matches, condrepr=('<subrepo %r>', pat))
1940
1939
1941 def _substringmatcher(pattern):
1940 def _substringmatcher(pattern):
1942 kind, pattern, matcher = util.stringmatcher(pattern)
1941 kind, pattern, matcher = util.stringmatcher(pattern)
1943 if kind == 'literal':
1942 if kind == 'literal':
1944 matcher = lambda s: pattern in s
1943 matcher = lambda s: pattern in s
1945 return kind, pattern, matcher
1944 return kind, pattern, matcher
1946
1945
1947 @predicate('tag([name])', safe=True)
1946 @predicate('tag([name])', safe=True)
1948 def tag(repo, subset, x):
1947 def tag(repo, subset, x):
1949 """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.
1950
1949
1951 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
1952 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:`,
1953 use the prefix `literal:`.
1952 use the prefix `literal:`.
1954 """
1953 """
1955 # i18n: "tag" is a keyword
1954 # i18n: "tag" is a keyword
1956 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1955 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1957 cl = repo.changelog
1956 cl = repo.changelog
1958 if args:
1957 if args:
1959 pattern = getstring(args[0],
1958 pattern = getstring(args[0],
1960 # i18n: "tag" is a keyword
1959 # i18n: "tag" is a keyword
1961 _('the argument to tag must be a string'))
1960 _('the argument to tag must be a string'))
1962 kind, pattern, matcher = util.stringmatcher(pattern)
1961 kind, pattern, matcher = util.stringmatcher(pattern)
1963 if kind == 'literal':
1962 if kind == 'literal':
1964 # avoid resolving all tags
1963 # avoid resolving all tags
1965 tn = repo._tagscache.tags.get(pattern, None)
1964 tn = repo._tagscache.tags.get(pattern, None)
1966 if tn is None:
1965 if tn is None:
1967 raise error.RepoLookupError(_("tag '%s' does not exist")
1966 raise error.RepoLookupError(_("tag '%s' does not exist")
1968 % pattern)
1967 % pattern)
1969 s = set([repo[tn].rev()])
1968 s = set([repo[tn].rev()])
1970 else:
1969 else:
1971 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)])
1972 else:
1971 else:
1973 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'])
1974 return subset & s
1973 return subset & s
1975
1974
1976 @predicate('tagged', safe=True)
1975 @predicate('tagged', safe=True)
1977 def tagged(repo, subset, x):
1976 def tagged(repo, subset, x):
1978 return tag(repo, subset, x)
1977 return tag(repo, subset, x)
1979
1978
1980 @predicate('unstable()', safe=True)
1979 @predicate('unstable()', safe=True)
1981 def unstable(repo, subset, x):
1980 def unstable(repo, subset, x):
1982 """Non-obsolete changesets with obsolete ancestors.
1981 """Non-obsolete changesets with obsolete ancestors.
1983 """
1982 """
1984 # i18n: "unstable" is a keyword
1983 # i18n: "unstable" is a keyword
1985 getargs(x, 0, 0, _("unstable takes no arguments"))
1984 getargs(x, 0, 0, _("unstable takes no arguments"))
1986 unstables = obsmod.getrevs(repo, 'unstable')
1985 unstables = obsmod.getrevs(repo, 'unstable')
1987 return subset & unstables
1986 return subset & unstables
1988
1987
1989
1988
1990 @predicate('user(string)', safe=True)
1989 @predicate('user(string)', safe=True)
1991 def user(repo, subset, x):
1990 def user(repo, subset, x):
1992 """User name contains string. The match is case-insensitive.
1991 """User name contains string. The match is case-insensitive.
1993
1992
1994 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
1995 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
1996 the prefix `literal:`.
1995 the prefix `literal:`.
1997 """
1996 """
1998 return author(repo, subset, x)
1997 return author(repo, subset, x)
1999
1998
2000 # experimental
1999 # experimental
2001 @predicate('wdir', safe=True)
2000 @predicate('wdir', safe=True)
2002 def wdir(repo, subset, x):
2001 def wdir(repo, subset, x):
2003 # i18n: "wdir" is a keyword
2002 # i18n: "wdir" is a keyword
2004 getargs(x, 0, 0, _("wdir takes no arguments"))
2003 getargs(x, 0, 0, _("wdir takes no arguments"))
2005 if node.wdirrev in subset or isinstance(subset, fullreposet):
2004 if node.wdirrev in subset or isinstance(subset, fullreposet):
2006 return baseset([node.wdirrev])
2005 return baseset([node.wdirrev])
2007 return baseset()
2006 return baseset()
2008
2007
2009 # for internal use
2008 # for internal use
2010 @predicate('_list', safe=True)
2009 @predicate('_list', safe=True)
2011 def _list(repo, subset, x):
2010 def _list(repo, subset, x):
2012 s = getstring(x, "internal error")
2011 s = getstring(x, "internal error")
2013 if not s:
2012 if not s:
2014 return baseset()
2013 return baseset()
2015 # remove duplicates here. it's difficult for caller to deduplicate sets
2014 # remove duplicates here. it's difficult for caller to deduplicate sets
2016 # because different symbols can point to the same rev.
2015 # because different symbols can point to the same rev.
2017 cl = repo.changelog
2016 cl = repo.changelog
2018 ls = []
2017 ls = []
2019 seen = set()
2018 seen = set()
2020 for t in s.split('\0'):
2019 for t in s.split('\0'):
2021 try:
2020 try:
2022 # fast path for integer revision
2021 # fast path for integer revision
2023 r = int(t)
2022 r = int(t)
2024 if str(r) != t or r not in cl:
2023 if str(r) != t or r not in cl:
2025 raise ValueError
2024 raise ValueError
2026 revs = [r]
2025 revs = [r]
2027 except ValueError:
2026 except ValueError:
2028 revs = stringset(repo, subset, t)
2027 revs = stringset(repo, subset, t)
2029
2028
2030 for r in revs:
2029 for r in revs:
2031 if r in seen:
2030 if r in seen:
2032 continue
2031 continue
2033 if (r in subset
2032 if (r in subset
2034 or r == node.nullrev and isinstance(subset, fullreposet)):
2033 or r == node.nullrev and isinstance(subset, fullreposet)):
2035 ls.append(r)
2034 ls.append(r)
2036 seen.add(r)
2035 seen.add(r)
2037 return baseset(ls)
2036 return baseset(ls)
2038
2037
2039 # for internal use
2038 # for internal use
2040 @predicate('_intlist', safe=True)
2039 @predicate('_intlist', safe=True)
2041 def _intlist(repo, subset, x):
2040 def _intlist(repo, subset, x):
2042 s = getstring(x, "internal error")
2041 s = getstring(x, "internal error")
2043 if not s:
2042 if not s:
2044 return baseset()
2043 return baseset()
2045 ls = [int(r) for r in s.split('\0')]
2044 ls = [int(r) for r in s.split('\0')]
2046 s = subset
2045 s = subset
2047 return baseset([r for r in ls if r in s])
2046 return baseset([r for r in ls if r in s])
2048
2047
2049 # for internal use
2048 # for internal use
2050 @predicate('_hexlist', safe=True)
2049 @predicate('_hexlist', safe=True)
2051 def _hexlist(repo, subset, x):
2050 def _hexlist(repo, subset, x):
2052 s = getstring(x, "internal error")
2051 s = getstring(x, "internal error")
2053 if not s:
2052 if not s:
2054 return baseset()
2053 return baseset()
2055 cl = repo.changelog
2054 cl = repo.changelog
2056 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')]
2057 s = subset
2056 s = subset
2058 return baseset([r for r in ls if r in s])
2057 return baseset([r for r in ls if r in s])
2059
2058
2060 methods = {
2059 methods = {
2061 "range": rangeset,
2060 "range": rangeset,
2062 "dagrange": dagrange,
2061 "dagrange": dagrange,
2063 "string": stringset,
2062 "string": stringset,
2064 "symbol": stringset,
2063 "symbol": stringset,
2065 "and": andset,
2064 "and": andset,
2066 "or": orset,
2065 "or": orset,
2067 "not": notset,
2066 "not": notset,
2068 "difference": differenceset,
2067 "difference": differenceset,
2069 "list": listset,
2068 "list": listset,
2070 "keyvalue": keyvaluepair,
2069 "keyvalue": keyvaluepair,
2071 "func": func,
2070 "func": func,
2072 "ancestor": ancestorspec,
2071 "ancestor": ancestorspec,
2073 "parent": parentspec,
2072 "parent": parentspec,
2074 "parentpost": p1,
2073 "parentpost": p1,
2075 }
2074 }
2076
2075
2077 def optimize(x, small):
2076 def optimize(x, small):
2078 if x is None:
2077 if x is None:
2079 return 0, x
2078 return 0, x
2080
2079
2081 smallbonus = 1
2080 smallbonus = 1
2082 if small:
2081 if small:
2083 smallbonus = .5
2082 smallbonus = .5
2084
2083
2085 op = x[0]
2084 op = x[0]
2086 if op == 'minus':
2085 if op == 'minus':
2087 return optimize(('and', x[1], ('not', x[2])), small)
2086 return optimize(('and', x[1], ('not', x[2])), small)
2088 elif op == 'only':
2087 elif op == 'only':
2089 return optimize(('func', ('symbol', 'only'),
2088 return optimize(('func', ('symbol', 'only'),
2090 ('list', x[1], x[2])), small)
2089 ('list', x[1], x[2])), small)
2091 elif op == 'onlypost':
2090 elif op == 'onlypost':
2092 return optimize(('func', ('symbol', 'only'), x[1]), small)
2091 return optimize(('func', ('symbol', 'only'), x[1]), small)
2093 elif op == 'dagrangepre':
2092 elif op == 'dagrangepre':
2094 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
2093 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
2095 elif op == 'dagrangepost':
2094 elif op == 'dagrangepost':
2096 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
2095 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
2097 elif op == 'rangeall':
2096 elif op == 'rangeall':
2098 return optimize(('range', ('string', '0'), ('string', 'tip')), small)
2097 return optimize(('range', ('string', '0'), ('string', 'tip')), small)
2099 elif op == 'rangepre':
2098 elif op == 'rangepre':
2100 return optimize(('range', ('string', '0'), x[1]), small)
2099 return optimize(('range', ('string', '0'), x[1]), small)
2101 elif op == 'rangepost':
2100 elif op == 'rangepost':
2102 return optimize(('range', x[1], ('string', 'tip')), small)
2101 return optimize(('range', x[1], ('string', 'tip')), small)
2103 elif op == 'negate':
2102 elif op == 'negate':
2104 return optimize(('string',
2103 return optimize(('string',
2105 '-' + getstring(x[1], _("can't negate that"))), small)
2104 '-' + getstring(x[1], _("can't negate that"))), small)
2106 elif op in 'string symbol negate':
2105 elif op in 'string symbol negate':
2107 return smallbonus, x # single revisions are small
2106 return smallbonus, x # single revisions are small
2108 elif op == 'and':
2107 elif op == 'and':
2109 wa, ta = optimize(x[1], True)
2108 wa, ta = optimize(x[1], True)
2110 wb, tb = optimize(x[2], True)
2109 wb, tb = optimize(x[2], True)
2111
2110
2112 # (::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
2113 def isonly(revs, bases):
2112 def isonly(revs, bases):
2114 return (
2113 return (
2115 revs is not None
2114 revs is not None
2116 and revs[0] == 'func'
2115 and revs[0] == 'func'
2117 and getstring(revs[1], _('not a symbol')) == 'ancestors'
2116 and getstring(revs[1], _('not a symbol')) == 'ancestors'
2118 and bases is not None
2117 and bases is not None
2119 and bases[0] == 'not'
2118 and bases[0] == 'not'
2120 and bases[1][0] == 'func'
2119 and bases[1][0] == 'func'
2121 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
2120 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
2122
2121
2123 w = min(wa, wb)
2122 w = min(wa, wb)
2124 if isonly(ta, tb):
2123 if isonly(ta, tb):
2125 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
2124 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
2126 if isonly(tb, ta):
2125 if isonly(tb, ta):
2127 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
2126 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
2128
2127
2129 if tb is not None and tb[0] == 'not':
2128 if tb is not None and tb[0] == 'not':
2130 return wa, ('difference', ta, tb[1])
2129 return wa, ('difference', ta, tb[1])
2131
2130
2132 if wa > wb:
2131 if wa > wb:
2133 return w, (op, tb, ta)
2132 return w, (op, tb, ta)
2134 return w, (op, ta, tb)
2133 return w, (op, ta, tb)
2135 elif op == 'or':
2134 elif op == 'or':
2136 # fast path for machine-generated expression, that is likely to have
2135 # fast path for machine-generated expression, that is likely to have
2137 # 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()'
2138 ws, ts, ss = [], [], []
2137 ws, ts, ss = [], [], []
2139 def flushss():
2138 def flushss():
2140 if not ss:
2139 if not ss:
2141 return
2140 return
2142 if len(ss) == 1:
2141 if len(ss) == 1:
2143 w, t = ss[0]
2142 w, t = ss[0]
2144 else:
2143 else:
2145 s = '\0'.join(t[1] for w, t in ss)
2144 s = '\0'.join(t[1] for w, t in ss)
2146 y = ('func', ('symbol', '_list'), ('string', s))
2145 y = ('func', ('symbol', '_list'), ('string', s))
2147 w, t = optimize(y, False)
2146 w, t = optimize(y, False)
2148 ws.append(w)
2147 ws.append(w)
2149 ts.append(t)
2148 ts.append(t)
2150 del ss[:]
2149 del ss[:]
2151 for y in x[1:]:
2150 for y in x[1:]:
2152 w, t = optimize(y, False)
2151 w, t = optimize(y, False)
2153 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'):
2154 ss.append((w, t))
2153 ss.append((w, t))
2155 continue
2154 continue
2156 flushss()
2155 flushss()
2157 ws.append(w)
2156 ws.append(w)
2158 ts.append(t)
2157 ts.append(t)
2159 flushss()
2158 flushss()
2160 if len(ts) == 1:
2159 if len(ts) == 1:
2161 return ws[0], ts[0] # 'or' operation is fully optimized out
2160 return ws[0], ts[0] # 'or' operation is fully optimized out
2162 # 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.
2163 # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
2162 # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
2164 # 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]))
2165 return max(ws), (op,) + tuple(ts)
2164 return max(ws), (op,) + tuple(ts)
2166 elif op == 'not':
2165 elif op == 'not':
2167 # Optimize not public() to _notpublic() because we have a fast version
2166 # Optimize not public() to _notpublic() because we have a fast version
2168 if x[1] == ('func', ('symbol', 'public'), None):
2167 if x[1] == ('func', ('symbol', 'public'), None):
2169 newsym = ('func', ('symbol', '_notpublic'), None)
2168 newsym = ('func', ('symbol', '_notpublic'), None)
2170 o = optimize(newsym, not small)
2169 o = optimize(newsym, not small)
2171 return o[0], o[1]
2170 return o[0], o[1]
2172 else:
2171 else:
2173 o = optimize(x[1], not small)
2172 o = optimize(x[1], not small)
2174 return o[0], (op, o[1])
2173 return o[0], (op, o[1])
2175 elif op == 'parentpost':
2174 elif op == 'parentpost':
2176 o = optimize(x[1], small)
2175 o = optimize(x[1], small)
2177 return o[0], (op, o[1])
2176 return o[0], (op, o[1])
2178 elif op == 'group':
2177 elif op == 'group':
2179 return optimize(x[1], small)
2178 return optimize(x[1], small)
2180 elif op in 'dagrange range parent ancestorspec':
2179 elif op in 'dagrange range parent ancestorspec':
2181 if op == 'parent':
2180 if op == 'parent':
2182 # x^:y means (x^) : y, not x ^ (:y)
2181 # x^:y means (x^) : y, not x ^ (:y)
2183 post = ('parentpost', x[1])
2182 post = ('parentpost', x[1])
2184 if x[2][0] == 'dagrangepre':
2183 if x[2][0] == 'dagrangepre':
2185 return optimize(('dagrange', post, x[2][1]), small)
2184 return optimize(('dagrange', post, x[2][1]), small)
2186 elif x[2][0] == 'rangepre':
2185 elif x[2][0] == 'rangepre':
2187 return optimize(('range', post, x[2][1]), small)
2186 return optimize(('range', post, x[2][1]), small)
2188
2187
2189 wa, ta = optimize(x[1], small)
2188 wa, ta = optimize(x[1], small)
2190 wb, tb = optimize(x[2], small)
2189 wb, tb = optimize(x[2], small)
2191 return wa + wb, (op, ta, tb)
2190 return wa + wb, (op, ta, tb)
2192 elif op == 'list':
2191 elif op == 'list':
2193 ws, ts = zip(*(optimize(y, small) for y in x[1:]))
2192 ws, ts = zip(*(optimize(y, small) for y in x[1:]))
2194 return sum(ws), (op,) + ts
2193 return sum(ws), (op,) + ts
2195 elif op == 'func':
2194 elif op == 'func':
2196 f = getstring(x[1], _("not a symbol"))
2195 f = getstring(x[1], _("not a symbol"))
2197 wa, ta = optimize(x[2], small)
2196 wa, ta = optimize(x[2], small)
2198 if f in ("author branch closed date desc file grep keyword "
2197 if f in ("author branch closed date desc file grep keyword "
2199 "outgoing user"):
2198 "outgoing user"):
2200 w = 10 # slow
2199 w = 10 # slow
2201 elif f in "modifies adds removes":
2200 elif f in "modifies adds removes":
2202 w = 30 # slower
2201 w = 30 # slower
2203 elif f == "contains":
2202 elif f == "contains":
2204 w = 100 # very slow
2203 w = 100 # very slow
2205 elif f == "ancestor":
2204 elif f == "ancestor":
2206 w = 1 * smallbonus
2205 w = 1 * smallbonus
2207 elif f in "reverse limit first _intlist":
2206 elif f in "reverse limit first _intlist":
2208 w = 0
2207 w = 0
2209 elif f in "sort":
2208 elif f in "sort":
2210 w = 10 # assume most sorts look at changelog
2209 w = 10 # assume most sorts look at changelog
2211 else:
2210 else:
2212 w = 1
2211 w = 1
2213 return w + wa, (op, x[1], ta)
2212 return w + wa, (op, x[1], ta)
2214 return 1, x
2213 return 1, x
2215
2214
2216 # 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
2217 # alias declarations and definitions
2216 # alias declarations and definitions
2218 _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)]
2219 if c.isalnum() or c in '._@$' or ord(c) > 127)
2218 if c.isalnum() or c in '._@$' or ord(c) > 127)
2220
2219
2221 def _tokenizealias(program, lookup=None):
2220 def _tokenizealias(program, lookup=None):
2222 """Parse alias declaration/definition into a stream of tokens
2221 """Parse alias declaration/definition into a stream of tokens
2223
2222
2224 This allows symbol names to use also ``$`` as an initial letter
2223 This allows symbol names to use also ``$`` as an initial letter
2225 (for backward compatibility), and callers of this function should
2224 (for backward compatibility), and callers of this function should
2226 examine whether ``$`` is used also for unexpected symbols or not.
2225 examine whether ``$`` is used also for unexpected symbols or not.
2227 """
2226 """
2228 return tokenize(program, lookup=lookup,
2227 return tokenize(program, lookup=lookup,
2229 syminitletters=_aliassyminitletters)
2228 syminitletters=_aliassyminitletters)
2230
2229
2231 def _parsealias(spec):
2230 def _parsealias(spec):
2232 """Parse alias declaration/definition ``spec``
2231 """Parse alias declaration/definition ``spec``
2233
2232
2234 >>> _parsealias('foo($1)')
2233 >>> _parsealias('foo($1)')
2235 ('func', ('symbol', 'foo'), ('symbol', '$1'))
2234 ('func', ('symbol', 'foo'), ('symbol', '$1'))
2236 >>> _parsealias('foo bar')
2235 >>> _parsealias('foo bar')
2237 Traceback (most recent call last):
2236 Traceback (most recent call last):
2238 ...
2237 ...
2239 ParseError: ('invalid token', 4)
2238 ParseError: ('invalid token', 4)
2240 """
2239 """
2241 p = parser.parser(elements)
2240 p = parser.parser(elements)
2242 tree, pos = p.parse(_tokenizealias(spec))
2241 tree, pos = p.parse(_tokenizealias(spec))
2243 if pos != len(spec):
2242 if pos != len(spec):
2244 raise error.ParseError(_('invalid token'), pos)
2243 raise error.ParseError(_('invalid token'), pos)
2245 return parser.simplifyinfixops(tree, ('list', 'or'))
2244 return parser.simplifyinfixops(tree, ('list', 'or'))
2246
2245
2247 class _aliasrules(parser.basealiasrules):
2246 class _aliasrules(parser.basealiasrules):
2248 """Parsing and expansion rule set of revset aliases"""
2247 """Parsing and expansion rule set of revset aliases"""
2249 _section = _('revset alias')
2248 _section = _('revset alias')
2250 _parse = staticmethod(_parsealias)
2249 _parse = staticmethod(_parsealias)
2251
2250
2252 @staticmethod
2251 @staticmethod
2253 def _trygetfunc(tree):
2252 def _trygetfunc(tree):
2254 if tree[0] == 'func' and tree[1][0] == 'symbol':
2253 if tree[0] == 'func' and tree[1][0] == 'symbol':
2255 return tree[1][1], getlist(tree[2])
2254 return tree[1][1], getlist(tree[2])
2256
2255
2257 def expandaliases(ui, tree, showwarning=None):
2256 def expandaliases(ui, tree, showwarning=None):
2258 aliases = _aliasrules.buildmap(ui.configitems('revsetalias'))
2257 aliases = _aliasrules.buildmap(ui.configitems('revsetalias'))
2259 tree = _aliasrules.expand(aliases, tree)
2258 tree = _aliasrules.expand(aliases, tree)
2260 if showwarning:
2259 if showwarning:
2261 # warn about problematic (but not referred) aliases
2260 # warn about problematic (but not referred) aliases
2262 for name, alias in sorted(aliases.iteritems()):
2261 for name, alias in sorted(aliases.iteritems()):
2263 if alias.error and not alias.warned:
2262 if alias.error and not alias.warned:
2264 showwarning(_('warning: %s\n') % (alias.error))
2263 showwarning(_('warning: %s\n') % (alias.error))
2265 alias.warned = True
2264 alias.warned = True
2266 return tree
2265 return tree
2267
2266
2268 def foldconcat(tree):
2267 def foldconcat(tree):
2269 """Fold elements to be concatenated by `##`
2268 """Fold elements to be concatenated by `##`
2270 """
2269 """
2271 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2270 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2272 return tree
2271 return tree
2273 if tree[0] == '_concat':
2272 if tree[0] == '_concat':
2274 pending = [tree]
2273 pending = [tree]
2275 l = []
2274 l = []
2276 while pending:
2275 while pending:
2277 e = pending.pop()
2276 e = pending.pop()
2278 if e[0] == '_concat':
2277 if e[0] == '_concat':
2279 pending.extend(reversed(e[1:]))
2278 pending.extend(reversed(e[1:]))
2280 elif e[0] in ('string', 'symbol'):
2279 elif e[0] in ('string', 'symbol'):
2281 l.append(e[1])
2280 l.append(e[1])
2282 else:
2281 else:
2283 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2282 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2284 raise error.ParseError(msg)
2283 raise error.ParseError(msg)
2285 return ('string', ''.join(l))
2284 return ('string', ''.join(l))
2286 else:
2285 else:
2287 return tuple(foldconcat(t) for t in tree)
2286 return tuple(foldconcat(t) for t in tree)
2288
2287
2289 def parse(spec, lookup=None):
2288 def parse(spec, lookup=None):
2290 p = parser.parser(elements)
2289 p = parser.parser(elements)
2291 tree, pos = p.parse(tokenize(spec, lookup=lookup))
2290 tree, pos = p.parse(tokenize(spec, lookup=lookup))
2292 if pos != len(spec):
2291 if pos != len(spec):
2293 raise error.ParseError(_("invalid token"), pos)
2292 raise error.ParseError(_("invalid token"), pos)
2294 return parser.simplifyinfixops(tree, ('list', 'or'))
2293 return parser.simplifyinfixops(tree, ('list', 'or'))
2295
2294
2296 def posttreebuilthook(tree, repo):
2295 def posttreebuilthook(tree, repo):
2297 # hook for extensions to execute code on the optimized tree
2296 # hook for extensions to execute code on the optimized tree
2298 pass
2297 pass
2299
2298
2300 def match(ui, spec, repo=None):
2299 def match(ui, spec, repo=None):
2301 if not spec:
2300 if not spec:
2302 raise error.ParseError(_("empty query"))
2301 raise error.ParseError(_("empty query"))
2303 lookup = None
2302 lookup = None
2304 if repo:
2303 if repo:
2305 lookup = repo.__contains__
2304 lookup = repo.__contains__
2306 tree = parse(spec, lookup)
2305 tree = parse(spec, lookup)
2307 return _makematcher(ui, tree, repo)
2306 return _makematcher(ui, tree, repo)
2308
2307
2309 def matchany(ui, specs, repo=None):
2308 def matchany(ui, specs, repo=None):
2310 """Create a matcher that will include any revisions matching one of the
2309 """Create a matcher that will include any revisions matching one of the
2311 given specs"""
2310 given specs"""
2312 if not specs:
2311 if not specs:
2313 def mfunc(repo, subset=None):
2312 def mfunc(repo, subset=None):
2314 return baseset()
2313 return baseset()
2315 return mfunc
2314 return mfunc
2316 if not all(specs):
2315 if not all(specs):
2317 raise error.ParseError(_("empty query"))
2316 raise error.ParseError(_("empty query"))
2318 lookup = None
2317 lookup = None
2319 if repo:
2318 if repo:
2320 lookup = repo.__contains__
2319 lookup = repo.__contains__
2321 if len(specs) == 1:
2320 if len(specs) == 1:
2322 tree = parse(specs[0], lookup)
2321 tree = parse(specs[0], lookup)
2323 else:
2322 else:
2324 tree = ('or',) + tuple(parse(s, lookup) for s in specs)
2323 tree = ('or',) + tuple(parse(s, lookup) for s in specs)
2325 return _makematcher(ui, tree, repo)
2324 return _makematcher(ui, tree, repo)
2326
2325
2327 def _makematcher(ui, tree, repo):
2326 def _makematcher(ui, tree, repo):
2328 if ui:
2327 if ui:
2329 tree = expandaliases(ui, tree, showwarning=ui.warn)
2328 tree = expandaliases(ui, tree, showwarning=ui.warn)
2330 tree = foldconcat(tree)
2329 tree = foldconcat(tree)
2331 weight, tree = optimize(tree, True)
2330 weight, tree = optimize(tree, True)
2332 posttreebuilthook(tree, repo)
2331 posttreebuilthook(tree, repo)
2333 def mfunc(repo, subset=None):
2332 def mfunc(repo, subset=None):
2334 if subset is None:
2333 if subset is None:
2335 subset = fullreposet(repo)
2334 subset = fullreposet(repo)
2336 if util.safehasattr(subset, 'isascending'):
2335 if util.safehasattr(subset, 'isascending'):
2337 result = getset(repo, subset, tree)
2336 result = getset(repo, subset, tree)
2338 else:
2337 else:
2339 result = getset(repo, baseset(subset), tree)
2338 result = getset(repo, baseset(subset), tree)
2340 return result
2339 return result
2341 return mfunc
2340 return mfunc
2342
2341
2343 def formatspec(expr, *args):
2342 def formatspec(expr, *args):
2344 '''
2343 '''
2345 This is a convenience function for using revsets internally, and
2344 This is a convenience function for using revsets internally, and
2346 escapes arguments appropriately. Aliases are intentionally ignored
2345 escapes arguments appropriately. Aliases are intentionally ignored
2347 so that intended expression behavior isn't accidentally subverted.
2346 so that intended expression behavior isn't accidentally subverted.
2348
2347
2349 Supported arguments:
2348 Supported arguments:
2350
2349
2351 %r = revset expression, parenthesized
2350 %r = revset expression, parenthesized
2352 %d = int(arg), no quoting
2351 %d = int(arg), no quoting
2353 %s = string(arg), escaped and single-quoted
2352 %s = string(arg), escaped and single-quoted
2354 %b = arg.branch(), escaped and single-quoted
2353 %b = arg.branch(), escaped and single-quoted
2355 %n = hex(arg), single-quoted
2354 %n = hex(arg), single-quoted
2356 %% = a literal '%'
2355 %% = a literal '%'
2357
2356
2358 Prefixing the type with 'l' specifies a parenthesized list of that type.
2357 Prefixing the type with 'l' specifies a parenthesized list of that type.
2359
2358
2360 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2359 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2361 '(10 or 11):: and ((this()) or (that()))'
2360 '(10 or 11):: and ((this()) or (that()))'
2362 >>> formatspec('%d:: and not %d::', 10, 20)
2361 >>> formatspec('%d:: and not %d::', 10, 20)
2363 '10:: and not 20::'
2362 '10:: and not 20::'
2364 >>> formatspec('%ld or %ld', [], [1])
2363 >>> formatspec('%ld or %ld', [], [1])
2365 "_list('') or 1"
2364 "_list('') or 1"
2366 >>> formatspec('keyword(%s)', 'foo\\xe9')
2365 >>> formatspec('keyword(%s)', 'foo\\xe9')
2367 "keyword('foo\\\\xe9')"
2366 "keyword('foo\\\\xe9')"
2368 >>> b = lambda: 'default'
2367 >>> b = lambda: 'default'
2369 >>> b.branch = b
2368 >>> b.branch = b
2370 >>> formatspec('branch(%b)', b)
2369 >>> formatspec('branch(%b)', b)
2371 "branch('default')"
2370 "branch('default')"
2372 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2371 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2373 "root(_list('a\\x00b\\x00c\\x00d'))"
2372 "root(_list('a\\x00b\\x00c\\x00d'))"
2374 '''
2373 '''
2375
2374
2376 def quote(s):
2375 def quote(s):
2377 return repr(str(s))
2376 return repr(str(s))
2378
2377
2379 def argtype(c, arg):
2378 def argtype(c, arg):
2380 if c == 'd':
2379 if c == 'd':
2381 return str(int(arg))
2380 return str(int(arg))
2382 elif c == 's':
2381 elif c == 's':
2383 return quote(arg)
2382 return quote(arg)
2384 elif c == 'r':
2383 elif c == 'r':
2385 parse(arg) # make sure syntax errors are confined
2384 parse(arg) # make sure syntax errors are confined
2386 return '(%s)' % arg
2385 return '(%s)' % arg
2387 elif c == 'n':
2386 elif c == 'n':
2388 return quote(node.hex(arg))
2387 return quote(node.hex(arg))
2389 elif c == 'b':
2388 elif c == 'b':
2390 return quote(arg.branch())
2389 return quote(arg.branch())
2391
2390
2392 def listexp(s, t):
2391 def listexp(s, t):
2393 l = len(s)
2392 l = len(s)
2394 if l == 0:
2393 if l == 0:
2395 return "_list('')"
2394 return "_list('')"
2396 elif l == 1:
2395 elif l == 1:
2397 return argtype(t, s[0])
2396 return argtype(t, s[0])
2398 elif t == 'd':
2397 elif t == 'd':
2399 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2398 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2400 elif t == 's':
2399 elif t == 's':
2401 return "_list('%s')" % "\0".join(s)
2400 return "_list('%s')" % "\0".join(s)
2402 elif t == 'n':
2401 elif t == 'n':
2403 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2402 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2404 elif t == 'b':
2403 elif t == 'b':
2405 return "_list('%s')" % "\0".join(a.branch() for a in s)
2404 return "_list('%s')" % "\0".join(a.branch() for a in s)
2406
2405
2407 m = l // 2
2406 m = l // 2
2408 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2407 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2409
2408
2410 ret = ''
2409 ret = ''
2411 pos = 0
2410 pos = 0
2412 arg = 0
2411 arg = 0
2413 while pos < len(expr):
2412 while pos < len(expr):
2414 c = expr[pos]
2413 c = expr[pos]
2415 if c == '%':
2414 if c == '%':
2416 pos += 1
2415 pos += 1
2417 d = expr[pos]
2416 d = expr[pos]
2418 if d == '%':
2417 if d == '%':
2419 ret += d
2418 ret += d
2420 elif d in 'dsnbr':
2419 elif d in 'dsnbr':
2421 ret += argtype(d, args[arg])
2420 ret += argtype(d, args[arg])
2422 arg += 1
2421 arg += 1
2423 elif d == 'l':
2422 elif d == 'l':
2424 # a list of some type
2423 # a list of some type
2425 pos += 1
2424 pos += 1
2426 d = expr[pos]
2425 d = expr[pos]
2427 ret += listexp(list(args[arg]), d)
2426 ret += listexp(list(args[arg]), d)
2428 arg += 1
2427 arg += 1
2429 else:
2428 else:
2430 raise error.Abort('unexpected revspec format character %s' % d)
2429 raise error.Abort('unexpected revspec format character %s' % d)
2431 else:
2430 else:
2432 ret += c
2431 ret += c
2433 pos += 1
2432 pos += 1
2434
2433
2435 return ret
2434 return ret
2436
2435
2437 def prettyformat(tree):
2436 def prettyformat(tree):
2438 return parser.prettyformat(tree, ('string', 'symbol'))
2437 return parser.prettyformat(tree, ('string', 'symbol'))
2439
2438
2440 def depth(tree):
2439 def depth(tree):
2441 if isinstance(tree, tuple):
2440 if isinstance(tree, tuple):
2442 return max(map(depth, tree)) + 1
2441 return max(map(depth, tree)) + 1
2443 else:
2442 else:
2444 return 0
2443 return 0
2445
2444
2446 def funcsused(tree):
2445 def funcsused(tree):
2447 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2446 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2448 return set()
2447 return set()
2449 else:
2448 else:
2450 funcs = set()
2449 funcs = set()
2451 for s in tree[1:]:
2450 for s in tree[1:]:
2452 funcs |= funcsused(s)
2451 funcs |= funcsused(s)
2453 if tree[0] == 'func':
2452 if tree[0] == 'func':
2454 funcs.add(tree[1][1])
2453 funcs.add(tree[1][1])
2455 return funcs
2454 return funcs
2456
2455
2457 def _formatsetrepr(r):
2456 def _formatsetrepr(r):
2458 """Format an optional printable representation of a set
2457 """Format an optional printable representation of a set
2459
2458
2460 ======== =================================
2459 ======== =================================
2461 type(r) example
2460 type(r) example
2462 ======== =================================
2461 ======== =================================
2463 tuple ('<not %r>', other)
2462 tuple ('<not %r>', other)
2464 str '<branch closed>'
2463 str '<branch closed>'
2465 callable lambda: '<branch %r>' % sorted(b)
2464 callable lambda: '<branch %r>' % sorted(b)
2466 object other
2465 object other
2467 ======== =================================
2466 ======== =================================
2468 """
2467 """
2469 if r is None:
2468 if r is None:
2470 return ''
2469 return ''
2471 elif isinstance(r, tuple):
2470 elif isinstance(r, tuple):
2472 return r[0] % r[1:]
2471 return r[0] % r[1:]
2473 elif isinstance(r, str):
2472 elif isinstance(r, str):
2474 return r
2473 return r
2475 elif callable(r):
2474 elif callable(r):
2476 return r()
2475 return r()
2477 else:
2476 else:
2478 return repr(r)
2477 return repr(r)
2479
2478
2480 class abstractsmartset(object):
2479 class abstractsmartset(object):
2481
2480
2482 def __nonzero__(self):
2481 def __nonzero__(self):
2483 """True if the smartset is not empty"""
2482 """True if the smartset is not empty"""
2484 raise NotImplementedError()
2483 raise NotImplementedError()
2485
2484
2486 def __contains__(self, rev):
2485 def __contains__(self, rev):
2487 """provide fast membership testing"""
2486 """provide fast membership testing"""
2488 raise NotImplementedError()
2487 raise NotImplementedError()
2489
2488
2490 def __iter__(self):
2489 def __iter__(self):
2491 """iterate the set in the order it is supposed to be iterated"""
2490 """iterate the set in the order it is supposed to be iterated"""
2492 raise NotImplementedError()
2491 raise NotImplementedError()
2493
2492
2494 # Attributes containing a function to perform a fast iteration in a given
2493 # Attributes containing a function to perform a fast iteration in a given
2495 # direction. A smartset can have none, one, or both defined.
2494 # direction. A smartset can have none, one, or both defined.
2496 #
2495 #
2497 # Default value is None instead of a function returning None to avoid
2496 # Default value is None instead of a function returning None to avoid
2498 # initializing an iterator just for testing if a fast method exists.
2497 # initializing an iterator just for testing if a fast method exists.
2499 fastasc = None
2498 fastasc = None
2500 fastdesc = None
2499 fastdesc = None
2501
2500
2502 def isascending(self):
2501 def isascending(self):
2503 """True if the set will iterate in ascending order"""
2502 """True if the set will iterate in ascending order"""
2504 raise NotImplementedError()
2503 raise NotImplementedError()
2505
2504
2506 def isdescending(self):
2505 def isdescending(self):
2507 """True if the set will iterate in descending order"""
2506 """True if the set will iterate in descending order"""
2508 raise NotImplementedError()
2507 raise NotImplementedError()
2509
2508
2510 @util.cachefunc
2509 @util.cachefunc
2511 def min(self):
2510 def min(self):
2512 """return the minimum element in the set"""
2511 """return the minimum element in the set"""
2513 if self.fastasc is not None:
2512 if self.fastasc is not None:
2514 for r in self.fastasc():
2513 for r in self.fastasc():
2515 return r
2514 return r
2516 raise ValueError('arg is an empty sequence')
2515 raise ValueError('arg is an empty sequence')
2517 return min(self)
2516 return min(self)
2518
2517
2519 @util.cachefunc
2518 @util.cachefunc
2520 def max(self):
2519 def max(self):
2521 """return the maximum element in the set"""
2520 """return the maximum element in the set"""
2522 if self.fastdesc is not None:
2521 if self.fastdesc is not None:
2523 for r in self.fastdesc():
2522 for r in self.fastdesc():
2524 return r
2523 return r
2525 raise ValueError('arg is an empty sequence')
2524 raise ValueError('arg is an empty sequence')
2526 return max(self)
2525 return max(self)
2527
2526
2528 def first(self):
2527 def first(self):
2529 """return the first element in the set (user iteration perspective)
2528 """return the first element in the set (user iteration perspective)
2530
2529
2531 Return None if the set is empty"""
2530 Return None if the set is empty"""
2532 raise NotImplementedError()
2531 raise NotImplementedError()
2533
2532
2534 def last(self):
2533 def last(self):
2535 """return the last element in the set (user iteration perspective)
2534 """return the last element in the set (user iteration perspective)
2536
2535
2537 Return None if the set is empty"""
2536 Return None if the set is empty"""
2538 raise NotImplementedError()
2537 raise NotImplementedError()
2539
2538
2540 def __len__(self):
2539 def __len__(self):
2541 """return the length of the smartsets
2540 """return the length of the smartsets
2542
2541
2543 This can be expensive on smartset that could be lazy otherwise."""
2542 This can be expensive on smartset that could be lazy otherwise."""
2544 raise NotImplementedError()
2543 raise NotImplementedError()
2545
2544
2546 def reverse(self):
2545 def reverse(self):
2547 """reverse the expected iteration order"""
2546 """reverse the expected iteration order"""
2548 raise NotImplementedError()
2547 raise NotImplementedError()
2549
2548
2550 def sort(self, reverse=True):
2549 def sort(self, reverse=True):
2551 """get the set to iterate in an ascending or descending order"""
2550 """get the set to iterate in an ascending or descending order"""
2552 raise NotImplementedError()
2551 raise NotImplementedError()
2553
2552
2554 def __and__(self, other):
2553 def __and__(self, other):
2555 """Returns a new object with the intersection of the two collections.
2554 """Returns a new object with the intersection of the two collections.
2556
2555
2557 This is part of the mandatory API for smartset."""
2556 This is part of the mandatory API for smartset."""
2558 if isinstance(other, fullreposet):
2557 if isinstance(other, fullreposet):
2559 return self
2558 return self
2560 return self.filter(other.__contains__, condrepr=other, cache=False)
2559 return self.filter(other.__contains__, condrepr=other, cache=False)
2561
2560
2562 def __add__(self, other):
2561 def __add__(self, other):
2563 """Returns a new object with the union of the two collections.
2562 """Returns a new object with the union of the two collections.
2564
2563
2565 This is part of the mandatory API for smartset."""
2564 This is part of the mandatory API for smartset."""
2566 return addset(self, other)
2565 return addset(self, other)
2567
2566
2568 def __sub__(self, other):
2567 def __sub__(self, other):
2569 """Returns a new object with the substraction of the two collections.
2568 """Returns a new object with the substraction of the two collections.
2570
2569
2571 This is part of the mandatory API for smartset."""
2570 This is part of the mandatory API for smartset."""
2572 c = other.__contains__
2571 c = other.__contains__
2573 return self.filter(lambda r: not c(r), condrepr=('<not %r>', other),
2572 return self.filter(lambda r: not c(r), condrepr=('<not %r>', other),
2574 cache=False)
2573 cache=False)
2575
2574
2576 def filter(self, condition, condrepr=None, cache=True):
2575 def filter(self, condition, condrepr=None, cache=True):
2577 """Returns this smartset filtered by condition as a new smartset.
2576 """Returns this smartset filtered by condition as a new smartset.
2578
2577
2579 `condition` is a callable which takes a revision number and returns a
2578 `condition` is a callable which takes a revision number and returns a
2580 boolean. Optional `condrepr` provides a printable representation of
2579 boolean. Optional `condrepr` provides a printable representation of
2581 the given `condition`.
2580 the given `condition`.
2582
2581
2583 This is part of the mandatory API for smartset."""
2582 This is part of the mandatory API for smartset."""
2584 # builtin cannot be cached. but do not needs to
2583 # builtin cannot be cached. but do not needs to
2585 if cache and util.safehasattr(condition, 'func_code'):
2584 if cache and util.safehasattr(condition, 'func_code'):
2586 condition = util.cachefunc(condition)
2585 condition = util.cachefunc(condition)
2587 return filteredset(self, condition, condrepr)
2586 return filteredset(self, condition, condrepr)
2588
2587
2589 class baseset(abstractsmartset):
2588 class baseset(abstractsmartset):
2590 """Basic data structure that represents a revset and contains the basic
2589 """Basic data structure that represents a revset and contains the basic
2591 operation that it should be able to perform.
2590 operation that it should be able to perform.
2592
2591
2593 Every method in this class should be implemented by any smartset class.
2592 Every method in this class should be implemented by any smartset class.
2594 """
2593 """
2595 def __init__(self, data=(), datarepr=None):
2594 def __init__(self, data=(), datarepr=None):
2596 """
2595 """
2597 datarepr: a tuple of (format, obj, ...), a function or an object that
2596 datarepr: a tuple of (format, obj, ...), a function or an object that
2598 provides a printable representation of the given data.
2597 provides a printable representation of the given data.
2599 """
2598 """
2600 self._ascending = None
2599 self._ascending = None
2601 if not isinstance(data, list):
2600 if not isinstance(data, list):
2602 if isinstance(data, set):
2601 if isinstance(data, set):
2603 self._set = data
2602 self._set = data
2604 # set has no order we pick one for stability purpose
2603 # set has no order we pick one for stability purpose
2605 self._ascending = True
2604 self._ascending = True
2606 data = list(data)
2605 data = list(data)
2607 self._list = data
2606 self._list = data
2608 self._datarepr = datarepr
2607 self._datarepr = datarepr
2609
2608
2610 @util.propertycache
2609 @util.propertycache
2611 def _set(self):
2610 def _set(self):
2612 return set(self._list)
2611 return set(self._list)
2613
2612
2614 @util.propertycache
2613 @util.propertycache
2615 def _asclist(self):
2614 def _asclist(self):
2616 asclist = self._list[:]
2615 asclist = self._list[:]
2617 asclist.sort()
2616 asclist.sort()
2618 return asclist
2617 return asclist
2619
2618
2620 def __iter__(self):
2619 def __iter__(self):
2621 if self._ascending is None:
2620 if self._ascending is None:
2622 return iter(self._list)
2621 return iter(self._list)
2623 elif self._ascending:
2622 elif self._ascending:
2624 return iter(self._asclist)
2623 return iter(self._asclist)
2625 else:
2624 else:
2626 return reversed(self._asclist)
2625 return reversed(self._asclist)
2627
2626
2628 def fastasc(self):
2627 def fastasc(self):
2629 return iter(self._asclist)
2628 return iter(self._asclist)
2630
2629
2631 def fastdesc(self):
2630 def fastdesc(self):
2632 return reversed(self._asclist)
2631 return reversed(self._asclist)
2633
2632
2634 @util.propertycache
2633 @util.propertycache
2635 def __contains__(self):
2634 def __contains__(self):
2636 return self._set.__contains__
2635 return self._set.__contains__
2637
2636
2638 def __nonzero__(self):
2637 def __nonzero__(self):
2639 return bool(self._list)
2638 return bool(self._list)
2640
2639
2641 def sort(self, reverse=False):
2640 def sort(self, reverse=False):
2642 self._ascending = not bool(reverse)
2641 self._ascending = not bool(reverse)
2643
2642
2644 def reverse(self):
2643 def reverse(self):
2645 if self._ascending is None:
2644 if self._ascending is None:
2646 self._list.reverse()
2645 self._list.reverse()
2647 else:
2646 else:
2648 self._ascending = not self._ascending
2647 self._ascending = not self._ascending
2649
2648
2650 def __len__(self):
2649 def __len__(self):
2651 return len(self._list)
2650 return len(self._list)
2652
2651
2653 def isascending(self):
2652 def isascending(self):
2654 """Returns True if the collection is ascending order, False if not.
2653 """Returns True if the collection is ascending order, False if not.
2655
2654
2656 This is part of the mandatory API for smartset."""
2655 This is part of the mandatory API for smartset."""
2657 if len(self) <= 1:
2656 if len(self) <= 1:
2658 return True
2657 return True
2659 return self._ascending is not None and self._ascending
2658 return self._ascending is not None and self._ascending
2660
2659
2661 def isdescending(self):
2660 def isdescending(self):
2662 """Returns True if the collection is descending order, False if not.
2661 """Returns True if the collection is descending order, False if not.
2663
2662
2664 This is part of the mandatory API for smartset."""
2663 This is part of the mandatory API for smartset."""
2665 if len(self) <= 1:
2664 if len(self) <= 1:
2666 return True
2665 return True
2667 return self._ascending is not None and not self._ascending
2666 return self._ascending is not None and not self._ascending
2668
2667
2669 def first(self):
2668 def first(self):
2670 if self:
2669 if self:
2671 if self._ascending is None:
2670 if self._ascending is None:
2672 return self._list[0]
2671 return self._list[0]
2673 elif self._ascending:
2672 elif self._ascending:
2674 return self._asclist[0]
2673 return self._asclist[0]
2675 else:
2674 else:
2676 return self._asclist[-1]
2675 return self._asclist[-1]
2677 return None
2676 return None
2678
2677
2679 def last(self):
2678 def last(self):
2680 if self:
2679 if self:
2681 if self._ascending is None:
2680 if self._ascending is None:
2682 return self._list[-1]
2681 return self._list[-1]
2683 elif self._ascending:
2682 elif self._ascending:
2684 return self._asclist[-1]
2683 return self._asclist[-1]
2685 else:
2684 else:
2686 return self._asclist[0]
2685 return self._asclist[0]
2687 return None
2686 return None
2688
2687
2689 def __repr__(self):
2688 def __repr__(self):
2690 d = {None: '', False: '-', True: '+'}[self._ascending]
2689 d = {None: '', False: '-', True: '+'}[self._ascending]
2691 s = _formatsetrepr(self._datarepr)
2690 s = _formatsetrepr(self._datarepr)
2692 if not s:
2691 if not s:
2693 l = self._list
2692 l = self._list
2694 # if _list has been built from a set, it might have a different
2693 # if _list has been built from a set, it might have a different
2695 # order from one python implementation to another.
2694 # order from one python implementation to another.
2696 # We fallback to the sorted version for a stable output.
2695 # We fallback to the sorted version for a stable output.
2697 if self._ascending is not None:
2696 if self._ascending is not None:
2698 l = self._asclist
2697 l = self._asclist
2699 s = repr(l)
2698 s = repr(l)
2700 return '<%s%s %s>' % (type(self).__name__, d, s)
2699 return '<%s%s %s>' % (type(self).__name__, d, s)
2701
2700
2702 class filteredset(abstractsmartset):
2701 class filteredset(abstractsmartset):
2703 """Duck type for baseset class which iterates lazily over the revisions in
2702 """Duck type for baseset class which iterates lazily over the revisions in
2704 the subset and contains a function which tests for membership in the
2703 the subset and contains a function which tests for membership in the
2705 revset
2704 revset
2706 """
2705 """
2707 def __init__(self, subset, condition=lambda x: True, condrepr=None):
2706 def __init__(self, subset, condition=lambda x: True, condrepr=None):
2708 """
2707 """
2709 condition: a function that decide whether a revision in the subset
2708 condition: a function that decide whether a revision in the subset
2710 belongs to the revset or not.
2709 belongs to the revset or not.
2711 condrepr: a tuple of (format, obj, ...), a function or an object that
2710 condrepr: a tuple of (format, obj, ...), a function or an object that
2712 provides a printable representation of the given condition.
2711 provides a printable representation of the given condition.
2713 """
2712 """
2714 self._subset = subset
2713 self._subset = subset
2715 self._condition = condition
2714 self._condition = condition
2716 self._condrepr = condrepr
2715 self._condrepr = condrepr
2717
2716
2718 def __contains__(self, x):
2717 def __contains__(self, x):
2719 return x in self._subset and self._condition(x)
2718 return x in self._subset and self._condition(x)
2720
2719
2721 def __iter__(self):
2720 def __iter__(self):
2722 return self._iterfilter(self._subset)
2721 return self._iterfilter(self._subset)
2723
2722
2724 def _iterfilter(self, it):
2723 def _iterfilter(self, it):
2725 cond = self._condition
2724 cond = self._condition
2726 for x in it:
2725 for x in it:
2727 if cond(x):
2726 if cond(x):
2728 yield x
2727 yield x
2729
2728
2730 @property
2729 @property
2731 def fastasc(self):
2730 def fastasc(self):
2732 it = self._subset.fastasc
2731 it = self._subset.fastasc
2733 if it is None:
2732 if it is None:
2734 return None
2733 return None
2735 return lambda: self._iterfilter(it())
2734 return lambda: self._iterfilter(it())
2736
2735
2737 @property
2736 @property
2738 def fastdesc(self):
2737 def fastdesc(self):
2739 it = self._subset.fastdesc
2738 it = self._subset.fastdesc
2740 if it is None:
2739 if it is None:
2741 return None
2740 return None
2742 return lambda: self._iterfilter(it())
2741 return lambda: self._iterfilter(it())
2743
2742
2744 def __nonzero__(self):
2743 def __nonzero__(self):
2745 fast = self.fastasc
2744 fast = self.fastasc
2746 if fast is None:
2745 if fast is None:
2747 fast = self.fastdesc
2746 fast = self.fastdesc
2748 if fast is not None:
2747 if fast is not None:
2749 it = fast()
2748 it = fast()
2750 else:
2749 else:
2751 it = self
2750 it = self
2752
2751
2753 for r in it:
2752 for r in it:
2754 return True
2753 return True
2755 return False
2754 return False
2756
2755
2757 def __len__(self):
2756 def __len__(self):
2758 # Basic implementation to be changed in future patches.
2757 # Basic implementation to be changed in future patches.
2759 # until this gets improved, we use generator expression
2758 # until this gets improved, we use generator expression
2760 # here, since list compr is free to call __len__ again
2759 # here, since list compr is free to call __len__ again
2761 # causing infinite recursion
2760 # causing infinite recursion
2762 l = baseset(r for r in self)
2761 l = baseset(r for r in self)
2763 return len(l)
2762 return len(l)
2764
2763
2765 def sort(self, reverse=False):
2764 def sort(self, reverse=False):
2766 self._subset.sort(reverse=reverse)
2765 self._subset.sort(reverse=reverse)
2767
2766
2768 def reverse(self):
2767 def reverse(self):
2769 self._subset.reverse()
2768 self._subset.reverse()
2770
2769
2771 def isascending(self):
2770 def isascending(self):
2772 return self._subset.isascending()
2771 return self._subset.isascending()
2773
2772
2774 def isdescending(self):
2773 def isdescending(self):
2775 return self._subset.isdescending()
2774 return self._subset.isdescending()
2776
2775
2777 def first(self):
2776 def first(self):
2778 for x in self:
2777 for x in self:
2779 return x
2778 return x
2780 return None
2779 return None
2781
2780
2782 def last(self):
2781 def last(self):
2783 it = None
2782 it = None
2784 if self.isascending():
2783 if self.isascending():
2785 it = self.fastdesc
2784 it = self.fastdesc
2786 elif self.isdescending():
2785 elif self.isdescending():
2787 it = self.fastasc
2786 it = self.fastasc
2788 if it is not None:
2787 if it is not None:
2789 for x in it():
2788 for x in it():
2790 return x
2789 return x
2791 return None #empty case
2790 return None #empty case
2792 else:
2791 else:
2793 x = None
2792 x = None
2794 for x in self:
2793 for x in self:
2795 pass
2794 pass
2796 return x
2795 return x
2797
2796
2798 def __repr__(self):
2797 def __repr__(self):
2799 xs = [repr(self._subset)]
2798 xs = [repr(self._subset)]
2800 s = _formatsetrepr(self._condrepr)
2799 s = _formatsetrepr(self._condrepr)
2801 if s:
2800 if s:
2802 xs.append(s)
2801 xs.append(s)
2803 return '<%s %s>' % (type(self).__name__, ', '.join(xs))
2802 return '<%s %s>' % (type(self).__name__, ', '.join(xs))
2804
2803
2805 def _iterordered(ascending, iter1, iter2):
2804 def _iterordered(ascending, iter1, iter2):
2806 """produce an ordered iteration from two iterators with the same order
2805 """produce an ordered iteration from two iterators with the same order
2807
2806
2808 The ascending is used to indicated the iteration direction.
2807 The ascending is used to indicated the iteration direction.
2809 """
2808 """
2810 choice = max
2809 choice = max
2811 if ascending:
2810 if ascending:
2812 choice = min
2811 choice = min
2813
2812
2814 val1 = None
2813 val1 = None
2815 val2 = None
2814 val2 = None
2816 try:
2815 try:
2817 # Consume both iterators in an ordered way until one is empty
2816 # Consume both iterators in an ordered way until one is empty
2818 while True:
2817 while True:
2819 if val1 is None:
2818 if val1 is None:
2820 val1 = iter1.next()
2819 val1 = iter1.next()
2821 if val2 is None:
2820 if val2 is None:
2822 val2 = iter2.next()
2821 val2 = iter2.next()
2823 next = choice(val1, val2)
2822 next = choice(val1, val2)
2824 yield next
2823 yield next
2825 if val1 == next:
2824 if val1 == next:
2826 val1 = None
2825 val1 = None
2827 if val2 == next:
2826 if val2 == next:
2828 val2 = None
2827 val2 = None
2829 except StopIteration:
2828 except StopIteration:
2830 # Flush any remaining values and consume the other one
2829 # Flush any remaining values and consume the other one
2831 it = iter2
2830 it = iter2
2832 if val1 is not None:
2831 if val1 is not None:
2833 yield val1
2832 yield val1
2834 it = iter1
2833 it = iter1
2835 elif val2 is not None:
2834 elif val2 is not None:
2836 # might have been equality and both are empty
2835 # might have been equality and both are empty
2837 yield val2
2836 yield val2
2838 for val in it:
2837 for val in it:
2839 yield val
2838 yield val
2840
2839
2841 class addset(abstractsmartset):
2840 class addset(abstractsmartset):
2842 """Represent the addition of two sets
2841 """Represent the addition of two sets
2843
2842
2844 Wrapper structure for lazily adding two structures without losing much
2843 Wrapper structure for lazily adding two structures without losing much
2845 performance on the __contains__ method
2844 performance on the __contains__ method
2846
2845
2847 If the ascending attribute is set, that means the two structures are
2846 If the ascending attribute is set, that means the two structures are
2848 ordered in either an ascending or descending way. Therefore, we can add
2847 ordered in either an ascending or descending way. Therefore, we can add
2849 them maintaining the order by iterating over both at the same time
2848 them maintaining the order by iterating over both at the same time
2850
2849
2851 >>> xs = baseset([0, 3, 2])
2850 >>> xs = baseset([0, 3, 2])
2852 >>> ys = baseset([5, 2, 4])
2851 >>> ys = baseset([5, 2, 4])
2853
2852
2854 >>> rs = addset(xs, ys)
2853 >>> rs = addset(xs, ys)
2855 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
2854 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
2856 (True, True, False, True, 0, 4)
2855 (True, True, False, True, 0, 4)
2857 >>> rs = addset(xs, baseset([]))
2856 >>> rs = addset(xs, baseset([]))
2858 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
2857 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
2859 (True, True, False, 0, 2)
2858 (True, True, False, 0, 2)
2860 >>> rs = addset(baseset([]), baseset([]))
2859 >>> rs = addset(baseset([]), baseset([]))
2861 >>> bool(rs), 0 in rs, rs.first(), rs.last()
2860 >>> bool(rs), 0 in rs, rs.first(), rs.last()
2862 (False, False, None, None)
2861 (False, False, None, None)
2863
2862
2864 iterate unsorted:
2863 iterate unsorted:
2865 >>> rs = addset(xs, ys)
2864 >>> rs = addset(xs, ys)
2866 >>> # (use generator because pypy could call len())
2865 >>> # (use generator because pypy could call len())
2867 >>> list(x for x in rs) # without _genlist
2866 >>> list(x for x in rs) # without _genlist
2868 [0, 3, 2, 5, 4]
2867 [0, 3, 2, 5, 4]
2869 >>> assert not rs._genlist
2868 >>> assert not rs._genlist
2870 >>> len(rs)
2869 >>> len(rs)
2871 5
2870 5
2872 >>> [x for x in rs] # with _genlist
2871 >>> [x for x in rs] # with _genlist
2873 [0, 3, 2, 5, 4]
2872 [0, 3, 2, 5, 4]
2874 >>> assert rs._genlist
2873 >>> assert rs._genlist
2875
2874
2876 iterate ascending:
2875 iterate ascending:
2877 >>> rs = addset(xs, ys, ascending=True)
2876 >>> rs = addset(xs, ys, ascending=True)
2878 >>> # (use generator because pypy could call len())
2877 >>> # (use generator because pypy could call len())
2879 >>> list(x for x in rs), list(x for x in rs.fastasc()) # without _asclist
2878 >>> list(x for x in rs), list(x for x in rs.fastasc()) # without _asclist
2880 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
2879 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
2881 >>> assert not rs._asclist
2880 >>> assert not rs._asclist
2882 >>> len(rs)
2881 >>> len(rs)
2883 5
2882 5
2884 >>> [x for x in rs], [x for x in rs.fastasc()]
2883 >>> [x for x in rs], [x for x in rs.fastasc()]
2885 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
2884 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
2886 >>> assert rs._asclist
2885 >>> assert rs._asclist
2887
2886
2888 iterate descending:
2887 iterate descending:
2889 >>> rs = addset(xs, ys, ascending=False)
2888 >>> rs = addset(xs, ys, ascending=False)
2890 >>> # (use generator because pypy could call len())
2889 >>> # (use generator because pypy could call len())
2891 >>> list(x for x in rs), list(x for x in rs.fastdesc()) # without _asclist
2890 >>> list(x for x in rs), list(x for x in rs.fastdesc()) # without _asclist
2892 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
2891 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
2893 >>> assert not rs._asclist
2892 >>> assert not rs._asclist
2894 >>> len(rs)
2893 >>> len(rs)
2895 5
2894 5
2896 >>> [x for x in rs], [x for x in rs.fastdesc()]
2895 >>> [x for x in rs], [x for x in rs.fastdesc()]
2897 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
2896 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
2898 >>> assert rs._asclist
2897 >>> assert rs._asclist
2899
2898
2900 iterate ascending without fastasc:
2899 iterate ascending without fastasc:
2901 >>> rs = addset(xs, generatorset(ys), ascending=True)
2900 >>> rs = addset(xs, generatorset(ys), ascending=True)
2902 >>> assert rs.fastasc is None
2901 >>> assert rs.fastasc is None
2903 >>> [x for x in rs]
2902 >>> [x for x in rs]
2904 [0, 2, 3, 4, 5]
2903 [0, 2, 3, 4, 5]
2905
2904
2906 iterate descending without fastdesc:
2905 iterate descending without fastdesc:
2907 >>> rs = addset(generatorset(xs), ys, ascending=False)
2906 >>> rs = addset(generatorset(xs), ys, ascending=False)
2908 >>> assert rs.fastdesc is None
2907 >>> assert rs.fastdesc is None
2909 >>> [x for x in rs]
2908 >>> [x for x in rs]
2910 [5, 4, 3, 2, 0]
2909 [5, 4, 3, 2, 0]
2911 """
2910 """
2912 def __init__(self, revs1, revs2, ascending=None):
2911 def __init__(self, revs1, revs2, ascending=None):
2913 self._r1 = revs1
2912 self._r1 = revs1
2914 self._r2 = revs2
2913 self._r2 = revs2
2915 self._iter = None
2914 self._iter = None
2916 self._ascending = ascending
2915 self._ascending = ascending
2917 self._genlist = None
2916 self._genlist = None
2918 self._asclist = None
2917 self._asclist = None
2919
2918
2920 def __len__(self):
2919 def __len__(self):
2921 return len(self._list)
2920 return len(self._list)
2922
2921
2923 def __nonzero__(self):
2922 def __nonzero__(self):
2924 return bool(self._r1) or bool(self._r2)
2923 return bool(self._r1) or bool(self._r2)
2925
2924
2926 @util.propertycache
2925 @util.propertycache
2927 def _list(self):
2926 def _list(self):
2928 if not self._genlist:
2927 if not self._genlist:
2929 self._genlist = baseset(iter(self))
2928 self._genlist = baseset(iter(self))
2930 return self._genlist
2929 return self._genlist
2931
2930
2932 def __iter__(self):
2931 def __iter__(self):
2933 """Iterate over both collections without repeating elements
2932 """Iterate over both collections without repeating elements
2934
2933
2935 If the ascending attribute is not set, iterate over the first one and
2934 If the ascending attribute is not set, iterate over the first one and
2936 then over the second one checking for membership on the first one so we
2935 then over the second one checking for membership on the first one so we
2937 dont yield any duplicates.
2936 dont yield any duplicates.
2938
2937
2939 If the ascending attribute is set, iterate over both collections at the
2938 If the ascending attribute is set, iterate over both collections at the
2940 same time, yielding only one value at a time in the given order.
2939 same time, yielding only one value at a time in the given order.
2941 """
2940 """
2942 if self._ascending is None:
2941 if self._ascending is None:
2943 if self._genlist:
2942 if self._genlist:
2944 return iter(self._genlist)
2943 return iter(self._genlist)
2945 def arbitraryordergen():
2944 def arbitraryordergen():
2946 for r in self._r1:
2945 for r in self._r1:
2947 yield r
2946 yield r
2948 inr1 = self._r1.__contains__
2947 inr1 = self._r1.__contains__
2949 for r in self._r2:
2948 for r in self._r2:
2950 if not inr1(r):
2949 if not inr1(r):
2951 yield r
2950 yield r
2952 return arbitraryordergen()
2951 return arbitraryordergen()
2953 # try to use our own fast iterator if it exists
2952 # try to use our own fast iterator if it exists
2954 self._trysetasclist()
2953 self._trysetasclist()
2955 if self._ascending:
2954 if self._ascending:
2956 attr = 'fastasc'
2955 attr = 'fastasc'
2957 else:
2956 else:
2958 attr = 'fastdesc'
2957 attr = 'fastdesc'
2959 it = getattr(self, attr)
2958 it = getattr(self, attr)
2960 if it is not None:
2959 if it is not None:
2961 return it()
2960 return it()
2962 # maybe half of the component supports fast
2961 # maybe half of the component supports fast
2963 # get iterator for _r1
2962 # get iterator for _r1
2964 iter1 = getattr(self._r1, attr)
2963 iter1 = getattr(self._r1, attr)
2965 if iter1 is None:
2964 if iter1 is None:
2966 # let's avoid side effect (not sure it matters)
2965 # let's avoid side effect (not sure it matters)
2967 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
2966 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
2968 else:
2967 else:
2969 iter1 = iter1()
2968 iter1 = iter1()
2970 # get iterator for _r2
2969 # get iterator for _r2
2971 iter2 = getattr(self._r2, attr)
2970 iter2 = getattr(self._r2, attr)
2972 if iter2 is None:
2971 if iter2 is None:
2973 # let's avoid side effect (not sure it matters)
2972 # let's avoid side effect (not sure it matters)
2974 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
2973 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
2975 else:
2974 else:
2976 iter2 = iter2()
2975 iter2 = iter2()
2977 return _iterordered(self._ascending, iter1, iter2)
2976 return _iterordered(self._ascending, iter1, iter2)
2978
2977
2979 def _trysetasclist(self):
2978 def _trysetasclist(self):
2980 """populate the _asclist attribute if possible and necessary"""
2979 """populate the _asclist attribute if possible and necessary"""
2981 if self._genlist is not None and self._asclist is None:
2980 if self._genlist is not None and self._asclist is None:
2982 self._asclist = sorted(self._genlist)
2981 self._asclist = sorted(self._genlist)
2983
2982
2984 @property
2983 @property
2985 def fastasc(self):
2984 def fastasc(self):
2986 self._trysetasclist()
2985 self._trysetasclist()
2987 if self._asclist is not None:
2986 if self._asclist is not None:
2988 return self._asclist.__iter__
2987 return self._asclist.__iter__
2989 iter1 = self._r1.fastasc
2988 iter1 = self._r1.fastasc
2990 iter2 = self._r2.fastasc
2989 iter2 = self._r2.fastasc
2991 if None in (iter1, iter2):
2990 if None in (iter1, iter2):
2992 return None
2991 return None
2993 return lambda: _iterordered(True, iter1(), iter2())
2992 return lambda: _iterordered(True, iter1(), iter2())
2994
2993
2995 @property
2994 @property
2996 def fastdesc(self):
2995 def fastdesc(self):
2997 self._trysetasclist()
2996 self._trysetasclist()
2998 if self._asclist is not None:
2997 if self._asclist is not None:
2999 return self._asclist.__reversed__
2998 return self._asclist.__reversed__
3000 iter1 = self._r1.fastdesc
2999 iter1 = self._r1.fastdesc
3001 iter2 = self._r2.fastdesc
3000 iter2 = self._r2.fastdesc
3002 if None in (iter1, iter2):
3001 if None in (iter1, iter2):
3003 return None
3002 return None
3004 return lambda: _iterordered(False, iter1(), iter2())
3003 return lambda: _iterordered(False, iter1(), iter2())
3005
3004
3006 def __contains__(self, x):
3005 def __contains__(self, x):
3007 return x in self._r1 or x in self._r2
3006 return x in self._r1 or x in self._r2
3008
3007
3009 def sort(self, reverse=False):
3008 def sort(self, reverse=False):
3010 """Sort the added set
3009 """Sort the added set
3011
3010
3012 For this we use the cached list with all the generated values and if we
3011 For this we use the cached list with all the generated values and if we
3013 know they are ascending or descending we can sort them in a smart way.
3012 know they are ascending or descending we can sort them in a smart way.
3014 """
3013 """
3015 self._ascending = not reverse
3014 self._ascending = not reverse
3016
3015
3017 def isascending(self):
3016 def isascending(self):
3018 return self._ascending is not None and self._ascending
3017 return self._ascending is not None and self._ascending
3019
3018
3020 def isdescending(self):
3019 def isdescending(self):
3021 return self._ascending is not None and not self._ascending
3020 return self._ascending is not None and not self._ascending
3022
3021
3023 def reverse(self):
3022 def reverse(self):
3024 if self._ascending is None:
3023 if self._ascending is None:
3025 self._list.reverse()
3024 self._list.reverse()
3026 else:
3025 else:
3027 self._ascending = not self._ascending
3026 self._ascending = not self._ascending
3028
3027
3029 def first(self):
3028 def first(self):
3030 for x in self:
3029 for x in self:
3031 return x
3030 return x
3032 return None
3031 return None
3033
3032
3034 def last(self):
3033 def last(self):
3035 self.reverse()
3034 self.reverse()
3036 val = self.first()
3035 val = self.first()
3037 self.reverse()
3036 self.reverse()
3038 return val
3037 return val
3039
3038
3040 def __repr__(self):
3039 def __repr__(self):
3041 d = {None: '', False: '-', True: '+'}[self._ascending]
3040 d = {None: '', False: '-', True: '+'}[self._ascending]
3042 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
3041 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
3043
3042
3044 class generatorset(abstractsmartset):
3043 class generatorset(abstractsmartset):
3045 """Wrap a generator for lazy iteration
3044 """Wrap a generator for lazy iteration
3046
3045
3047 Wrapper structure for generators that provides lazy membership and can
3046 Wrapper structure for generators that provides lazy membership and can
3048 be iterated more than once.
3047 be iterated more than once.
3049 When asked for membership it generates values until either it finds the
3048 When asked for membership it generates values until either it finds the
3050 requested one or has gone through all the elements in the generator
3049 requested one or has gone through all the elements in the generator
3051 """
3050 """
3052 def __init__(self, gen, iterasc=None):
3051 def __init__(self, gen, iterasc=None):
3053 """
3052 """
3054 gen: a generator producing the values for the generatorset.
3053 gen: a generator producing the values for the generatorset.
3055 """
3054 """
3056 self._gen = gen
3055 self._gen = gen
3057 self._asclist = None
3056 self._asclist = None
3058 self._cache = {}
3057 self._cache = {}
3059 self._genlist = []
3058 self._genlist = []
3060 self._finished = False
3059 self._finished = False
3061 self._ascending = True
3060 self._ascending = True
3062 if iterasc is not None:
3061 if iterasc is not None:
3063 if iterasc:
3062 if iterasc:
3064 self.fastasc = self._iterator
3063 self.fastasc = self._iterator
3065 self.__contains__ = self._asccontains
3064 self.__contains__ = self._asccontains
3066 else:
3065 else:
3067 self.fastdesc = self._iterator
3066 self.fastdesc = self._iterator
3068 self.__contains__ = self._desccontains
3067 self.__contains__ = self._desccontains
3069
3068
3070 def __nonzero__(self):
3069 def __nonzero__(self):
3071 # Do not use 'for r in self' because it will enforce the iteration
3070 # Do not use 'for r in self' because it will enforce the iteration
3072 # order (default ascending), possibly unrolling a whole descending
3071 # order (default ascending), possibly unrolling a whole descending
3073 # iterator.
3072 # iterator.
3074 if self._genlist:
3073 if self._genlist:
3075 return True
3074 return True
3076 for r in self._consumegen():
3075 for r in self._consumegen():
3077 return True
3076 return True
3078 return False
3077 return False
3079
3078
3080 def __contains__(self, x):
3079 def __contains__(self, x):
3081 if x in self._cache:
3080 if x in self._cache:
3082 return self._cache[x]
3081 return self._cache[x]
3083
3082
3084 # Use new values only, as existing values would be cached.
3083 # Use new values only, as existing values would be cached.
3085 for l in self._consumegen():
3084 for l in self._consumegen():
3086 if l == x:
3085 if l == x:
3087 return True
3086 return True
3088
3087
3089 self._cache[x] = False
3088 self._cache[x] = False
3090 return False
3089 return False
3091
3090
3092 def _asccontains(self, x):
3091 def _asccontains(self, x):
3093 """version of contains optimised for ascending generator"""
3092 """version of contains optimised for ascending generator"""
3094 if x in self._cache:
3093 if x in self._cache:
3095 return self._cache[x]
3094 return self._cache[x]
3096
3095
3097 # Use new values only, as existing values would be cached.
3096 # Use new values only, as existing values would be cached.
3098 for l in self._consumegen():
3097 for l in self._consumegen():
3099 if l == x:
3098 if l == x:
3100 return True
3099 return True
3101 if l > x:
3100 if l > x:
3102 break
3101 break
3103
3102
3104 self._cache[x] = False
3103 self._cache[x] = False
3105 return False
3104 return False
3106
3105
3107 def _desccontains(self, x):
3106 def _desccontains(self, x):
3108 """version of contains optimised for descending generator"""
3107 """version of contains optimised for descending generator"""
3109 if x in self._cache:
3108 if x in self._cache:
3110 return self._cache[x]
3109 return self._cache[x]
3111
3110
3112 # Use new values only, as existing values would be cached.
3111 # Use new values only, as existing values would be cached.
3113 for l in self._consumegen():
3112 for l in self._consumegen():
3114 if l == x:
3113 if l == x:
3115 return True
3114 return True
3116 if l < x:
3115 if l < x:
3117 break
3116 break
3118
3117
3119 self._cache[x] = False
3118 self._cache[x] = False
3120 return False
3119 return False
3121
3120
3122 def __iter__(self):
3121 def __iter__(self):
3123 if self._ascending:
3122 if self._ascending:
3124 it = self.fastasc
3123 it = self.fastasc
3125 else:
3124 else:
3126 it = self.fastdesc
3125 it = self.fastdesc
3127 if it is not None:
3126 if it is not None:
3128 return it()
3127 return it()
3129 # we need to consume the iterator
3128 # we need to consume the iterator
3130 for x in self._consumegen():
3129 for x in self._consumegen():
3131 pass
3130 pass
3132 # recall the same code
3131 # recall the same code
3133 return iter(self)
3132 return iter(self)
3134
3133
3135 def _iterator(self):
3134 def _iterator(self):
3136 if self._finished:
3135 if self._finished:
3137 return iter(self._genlist)
3136 return iter(self._genlist)
3138
3137
3139 # We have to use this complex iteration strategy to allow multiple
3138 # We have to use this complex iteration strategy to allow multiple
3140 # iterations at the same time. We need to be able to catch revision
3139 # iterations at the same time. We need to be able to catch revision
3141 # removed from _consumegen and added to genlist in another instance.
3140 # removed from _consumegen and added to genlist in another instance.
3142 #
3141 #
3143 # Getting rid of it would provide an about 15% speed up on this
3142 # Getting rid of it would provide an about 15% speed up on this
3144 # iteration.
3143 # iteration.
3145 genlist = self._genlist
3144 genlist = self._genlist
3146 nextrev = self._consumegen().next
3145 nextrev = self._consumegen().next
3147 _len = len # cache global lookup
3146 _len = len # cache global lookup
3148 def gen():
3147 def gen():
3149 i = 0
3148 i = 0
3150 while True:
3149 while True:
3151 if i < _len(genlist):
3150 if i < _len(genlist):
3152 yield genlist[i]
3151 yield genlist[i]
3153 else:
3152 else:
3154 yield nextrev()
3153 yield nextrev()
3155 i += 1
3154 i += 1
3156 return gen()
3155 return gen()
3157
3156
3158 def _consumegen(self):
3157 def _consumegen(self):
3159 cache = self._cache
3158 cache = self._cache
3160 genlist = self._genlist.append
3159 genlist = self._genlist.append
3161 for item in self._gen:
3160 for item in self._gen:
3162 cache[item] = True
3161 cache[item] = True
3163 genlist(item)
3162 genlist(item)
3164 yield item
3163 yield item
3165 if not self._finished:
3164 if not self._finished:
3166 self._finished = True
3165 self._finished = True
3167 asc = self._genlist[:]
3166 asc = self._genlist[:]
3168 asc.sort()
3167 asc.sort()
3169 self._asclist = asc
3168 self._asclist = asc
3170 self.fastasc = asc.__iter__
3169 self.fastasc = asc.__iter__
3171 self.fastdesc = asc.__reversed__
3170 self.fastdesc = asc.__reversed__
3172
3171
3173 def __len__(self):
3172 def __len__(self):
3174 for x in self._consumegen():
3173 for x in self._consumegen():
3175 pass
3174 pass
3176 return len(self._genlist)
3175 return len(self._genlist)
3177
3176
3178 def sort(self, reverse=False):
3177 def sort(self, reverse=False):
3179 self._ascending = not reverse
3178 self._ascending = not reverse
3180
3179
3181 def reverse(self):
3180 def reverse(self):
3182 self._ascending = not self._ascending
3181 self._ascending = not self._ascending
3183
3182
3184 def isascending(self):
3183 def isascending(self):
3185 return self._ascending
3184 return self._ascending
3186
3185
3187 def isdescending(self):
3186 def isdescending(self):
3188 return not self._ascending
3187 return not self._ascending
3189
3188
3190 def first(self):
3189 def first(self):
3191 if self._ascending:
3190 if self._ascending:
3192 it = self.fastasc
3191 it = self.fastasc
3193 else:
3192 else:
3194 it = self.fastdesc
3193 it = self.fastdesc
3195 if it is None:
3194 if it is None:
3196 # we need to consume all and try again
3195 # we need to consume all and try again
3197 for x in self._consumegen():
3196 for x in self._consumegen():
3198 pass
3197 pass
3199 return self.first()
3198 return self.first()
3200 return next(it(), None)
3199 return next(it(), None)
3201
3200
3202 def last(self):
3201 def last(self):
3203 if self._ascending:
3202 if self._ascending:
3204 it = self.fastdesc
3203 it = self.fastdesc
3205 else:
3204 else:
3206 it = self.fastasc
3205 it = self.fastasc
3207 if it is None:
3206 if it is None:
3208 # we need to consume all and try again
3207 # we need to consume all and try again
3209 for x in self._consumegen():
3208 for x in self._consumegen():
3210 pass
3209 pass
3211 return self.first()
3210 return self.first()
3212 return next(it(), None)
3211 return next(it(), None)
3213
3212
3214 def __repr__(self):
3213 def __repr__(self):
3215 d = {False: '-', True: '+'}[self._ascending]
3214 d = {False: '-', True: '+'}[self._ascending]
3216 return '<%s%s>' % (type(self).__name__, d)
3215 return '<%s%s>' % (type(self).__name__, d)
3217
3216
3218 class spanset(abstractsmartset):
3217 class spanset(abstractsmartset):
3219 """Duck type for baseset class which represents a range of revisions and
3218 """Duck type for baseset class which represents a range of revisions and
3220 can work lazily and without having all the range in memory
3219 can work lazily and without having all the range in memory
3221
3220
3222 Note that spanset(x, y) behave almost like xrange(x, y) except for two
3221 Note that spanset(x, y) behave almost like xrange(x, y) except for two
3223 notable points:
3222 notable points:
3224 - when x < y it will be automatically descending,
3223 - when x < y it will be automatically descending,
3225 - revision filtered with this repoview will be skipped.
3224 - revision filtered with this repoview will be skipped.
3226
3225
3227 """
3226 """
3228 def __init__(self, repo, start=0, end=None):
3227 def __init__(self, repo, start=0, end=None):
3229 """
3228 """
3230 start: first revision included the set
3229 start: first revision included the set
3231 (default to 0)
3230 (default to 0)
3232 end: first revision excluded (last+1)
3231 end: first revision excluded (last+1)
3233 (default to len(repo)
3232 (default to len(repo)
3234
3233
3235 Spanset will be descending if `end` < `start`.
3234 Spanset will be descending if `end` < `start`.
3236 """
3235 """
3237 if end is None:
3236 if end is None:
3238 end = len(repo)
3237 end = len(repo)
3239 self._ascending = start <= end
3238 self._ascending = start <= end
3240 if not self._ascending:
3239 if not self._ascending:
3241 start, end = end + 1, start +1
3240 start, end = end + 1, start +1
3242 self._start = start
3241 self._start = start
3243 self._end = end
3242 self._end = end
3244 self._hiddenrevs = repo.changelog.filteredrevs
3243 self._hiddenrevs = repo.changelog.filteredrevs
3245
3244
3246 def sort(self, reverse=False):
3245 def sort(self, reverse=False):
3247 self._ascending = not reverse
3246 self._ascending = not reverse
3248
3247
3249 def reverse(self):
3248 def reverse(self):
3250 self._ascending = not self._ascending
3249 self._ascending = not self._ascending
3251
3250
3252 def _iterfilter(self, iterrange):
3251 def _iterfilter(self, iterrange):
3253 s = self._hiddenrevs
3252 s = self._hiddenrevs
3254 for r in iterrange:
3253 for r in iterrange:
3255 if r not in s:
3254 if r not in s:
3256 yield r
3255 yield r
3257
3256
3258 def __iter__(self):
3257 def __iter__(self):
3259 if self._ascending:
3258 if self._ascending:
3260 return self.fastasc()
3259 return self.fastasc()
3261 else:
3260 else:
3262 return self.fastdesc()
3261 return self.fastdesc()
3263
3262
3264 def fastasc(self):
3263 def fastasc(self):
3265 iterrange = xrange(self._start, self._end)
3264 iterrange = xrange(self._start, self._end)
3266 if self._hiddenrevs:
3265 if self._hiddenrevs:
3267 return self._iterfilter(iterrange)
3266 return self._iterfilter(iterrange)
3268 return iter(iterrange)
3267 return iter(iterrange)
3269
3268
3270 def fastdesc(self):
3269 def fastdesc(self):
3271 iterrange = xrange(self._end - 1, self._start - 1, -1)
3270 iterrange = xrange(self._end - 1, self._start - 1, -1)
3272 if self._hiddenrevs:
3271 if self._hiddenrevs:
3273 return self._iterfilter(iterrange)
3272 return self._iterfilter(iterrange)
3274 return iter(iterrange)
3273 return iter(iterrange)
3275
3274
3276 def __contains__(self, rev):
3275 def __contains__(self, rev):
3277 hidden = self._hiddenrevs
3276 hidden = self._hiddenrevs
3278 return ((self._start <= rev < self._end)
3277 return ((self._start <= rev < self._end)
3279 and not (hidden and rev in hidden))
3278 and not (hidden and rev in hidden))
3280
3279
3281 def __nonzero__(self):
3280 def __nonzero__(self):
3282 for r in self:
3281 for r in self:
3283 return True
3282 return True
3284 return False
3283 return False
3285
3284
3286 def __len__(self):
3285 def __len__(self):
3287 if not self._hiddenrevs:
3286 if not self._hiddenrevs:
3288 return abs(self._end - self._start)
3287 return abs(self._end - self._start)
3289 else:
3288 else:
3290 count = 0
3289 count = 0
3291 start = self._start
3290 start = self._start
3292 end = self._end
3291 end = self._end
3293 for rev in self._hiddenrevs:
3292 for rev in self._hiddenrevs:
3294 if (end < rev <= start) or (start <= rev < end):
3293 if (end < rev <= start) or (start <= rev < end):
3295 count += 1
3294 count += 1
3296 return abs(self._end - self._start) - count
3295 return abs(self._end - self._start) - count
3297
3296
3298 def isascending(self):
3297 def isascending(self):
3299 return self._ascending
3298 return self._ascending
3300
3299
3301 def isdescending(self):
3300 def isdescending(self):
3302 return not self._ascending
3301 return not self._ascending
3303
3302
3304 def first(self):
3303 def first(self):
3305 if self._ascending:
3304 if self._ascending:
3306 it = self.fastasc
3305 it = self.fastasc
3307 else:
3306 else:
3308 it = self.fastdesc
3307 it = self.fastdesc
3309 for x in it():
3308 for x in it():
3310 return x
3309 return x
3311 return None
3310 return None
3312
3311
3313 def last(self):
3312 def last(self):
3314 if self._ascending:
3313 if self._ascending:
3315 it = self.fastdesc
3314 it = self.fastdesc
3316 else:
3315 else:
3317 it = self.fastasc
3316 it = self.fastasc
3318 for x in it():
3317 for x in it():
3319 return x
3318 return x
3320 return None
3319 return None
3321
3320
3322 def __repr__(self):
3321 def __repr__(self):
3323 d = {False: '-', True: '+'}[self._ascending]
3322 d = {False: '-', True: '+'}[self._ascending]
3324 return '<%s%s %d:%d>' % (type(self).__name__, d,
3323 return '<%s%s %d:%d>' % (type(self).__name__, d,
3325 self._start, self._end - 1)
3324 self._start, self._end - 1)
3326
3325
3327 class fullreposet(spanset):
3326 class fullreposet(spanset):
3328 """a set containing all revisions in the repo
3327 """a set containing all revisions in the repo
3329
3328
3330 This class exists to host special optimization and magic to handle virtual
3329 This class exists to host special optimization and magic to handle virtual
3331 revisions such as "null".
3330 revisions such as "null".
3332 """
3331 """
3333
3332
3334 def __init__(self, repo):
3333 def __init__(self, repo):
3335 super(fullreposet, self).__init__(repo)
3334 super(fullreposet, self).__init__(repo)
3336
3335
3337 def __and__(self, other):
3336 def __and__(self, other):
3338 """As self contains the whole repo, all of the other set should also be
3337 """As self contains the whole repo, all of the other set should also be
3339 in self. Therefore `self & other = other`.
3338 in self. Therefore `self & other = other`.
3340
3339
3341 This boldly assumes the other contains valid revs only.
3340 This boldly assumes the other contains valid revs only.
3342 """
3341 """
3343 # other not a smartset, make is so
3342 # other not a smartset, make is so
3344 if not util.safehasattr(other, 'isascending'):
3343 if not util.safehasattr(other, 'isascending'):
3345 # filter out hidden revision
3344 # filter out hidden revision
3346 # (this boldly assumes all smartset are pure)
3345 # (this boldly assumes all smartset are pure)
3347 #
3346 #
3348 # `other` was used with "&", let's assume this is a set like
3347 # `other` was used with "&", let's assume this is a set like
3349 # object.
3348 # object.
3350 other = baseset(other - self._hiddenrevs)
3349 other = baseset(other - self._hiddenrevs)
3351
3350
3352 # XXX As fullreposet is also used as bootstrap, this is wrong.
3351 # XXX As fullreposet is also used as bootstrap, this is wrong.
3353 #
3352 #
3354 # With a giveme312() revset returning [3,1,2], this makes
3353 # With a giveme312() revset returning [3,1,2], this makes
3355 # 'hg log -r "giveme312()"' -> 1, 2, 3 (wrong)
3354 # 'hg log -r "giveme312()"' -> 1, 2, 3 (wrong)
3356 # We cannot just drop it because other usage still need to sort it:
3355 # We cannot just drop it because other usage still need to sort it:
3357 # 'hg log -r "all() and giveme312()"' -> 1, 2, 3 (right)
3356 # 'hg log -r "all() and giveme312()"' -> 1, 2, 3 (right)
3358 #
3357 #
3359 # There is also some faulty revset implementations that rely on it
3358 # There is also some faulty revset implementations that rely on it
3360 # (eg: children as of its state in e8075329c5fb)
3359 # (eg: children as of its state in e8075329c5fb)
3361 #
3360 #
3362 # When we fix the two points above we can move this into the if clause
3361 # When we fix the two points above we can move this into the if clause
3363 other.sort(reverse=self.isdescending())
3362 other.sort(reverse=self.isdescending())
3364 return other
3363 return other
3365
3364
3366 def prettyformatset(revs):
3365 def prettyformatset(revs):
3367 lines = []
3366 lines = []
3368 rs = repr(revs)
3367 rs = repr(revs)
3369 p = 0
3368 p = 0
3370 while p < len(rs):
3369 while p < len(rs):
3371 q = rs.find('<', p + 1)
3370 q = rs.find('<', p + 1)
3372 if q < 0:
3371 if q < 0:
3373 q = len(rs)
3372 q = len(rs)
3374 l = rs.count('<', 0, p) - rs.count('>', 0, p)
3373 l = rs.count('<', 0, p) - rs.count('>', 0, p)
3375 assert l >= 0
3374 assert l >= 0
3376 lines.append((l, rs[p:q].rstrip()))
3375 lines.append((l, rs[p:q].rstrip()))
3377 p = q
3376 p = q
3378 return '\n'.join(' ' * l + s for l, s in lines)
3377 return '\n'.join(' ' * l + s for l, s in lines)
3379
3378
3380 def loadpredicate(ui, extname, registrarobj):
3379 def loadpredicate(ui, extname, registrarobj):
3381 """Load revset predicates from specified registrarobj
3380 """Load revset predicates from specified registrarobj
3382 """
3381 """
3383 for name, func in registrarobj._table.iteritems():
3382 for name, func in registrarobj._table.iteritems():
3384 symbols[name] = func
3383 symbols[name] = func
3385 if func._safe:
3384 if func._safe:
3386 safesymbols.add(name)
3385 safesymbols.add(name)
3387
3386
3388 # load built-in predicates explicitly to setup safesymbols
3387 # load built-in predicates explicitly to setup safesymbols
3389 loadpredicate(None, None, predicate)
3388 loadpredicate(None, None, predicate)
3390
3389
3391 # tell hggettext to extract docstrings from these functions:
3390 # tell hggettext to extract docstrings from these functions:
3392 i18nfunctions = symbols.values()
3391 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now