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