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