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