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