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