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