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