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