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