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