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