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