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