##// END OF EJS Templates
revset: remove existence check from min() and max()...
Durham Goode -
r26305:ade5c488 default
parent child Browse files
Show More
@@ -1,3838 +1,3846 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 if os:
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:
1395 # os.max() throws a ValueError when the collection is empty.
1396 # Same as python's max().
1397 pass
1394 return baseset()
1398 return baseset()
1395
1399
1396 def merge(repo, subset, x):
1400 def merge(repo, subset, x):
1397 """``merge()``
1401 """``merge()``
1398 Changeset is a merge changeset.
1402 Changeset is a merge changeset.
1399 """
1403 """
1400 # i18n: "merge" is a keyword
1404 # i18n: "merge" is a keyword
1401 getargs(x, 0, 0, _("merge takes no arguments"))
1405 getargs(x, 0, 0, _("merge takes no arguments"))
1402 cl = repo.changelog
1406 cl = repo.changelog
1403 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
1407 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
1404
1408
1405 def branchpoint(repo, subset, x):
1409 def branchpoint(repo, subset, x):
1406 """``branchpoint()``
1410 """``branchpoint()``
1407 Changesets with more than one child.
1411 Changesets with more than one child.
1408 """
1412 """
1409 # i18n: "branchpoint" is a keyword
1413 # i18n: "branchpoint" is a keyword
1410 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1414 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1411 cl = repo.changelog
1415 cl = repo.changelog
1412 if not subset:
1416 if not subset:
1413 return baseset()
1417 return baseset()
1414 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1418 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1415 # (and if it is not, it should.)
1419 # (and if it is not, it should.)
1416 baserev = min(subset)
1420 baserev = min(subset)
1417 parentscount = [0]*(len(repo) - baserev)
1421 parentscount = [0]*(len(repo) - baserev)
1418 for r in cl.revs(start=baserev + 1):
1422 for r in cl.revs(start=baserev + 1):
1419 for p in cl.parentrevs(r):
1423 for p in cl.parentrevs(r):
1420 if p >= baserev:
1424 if p >= baserev:
1421 parentscount[p - baserev] += 1
1425 parentscount[p - baserev] += 1
1422 return subset.filter(lambda r: parentscount[r - baserev] > 1)
1426 return subset.filter(lambda r: parentscount[r - baserev] > 1)
1423
1427
1424 def minrev(repo, subset, x):
1428 def minrev(repo, subset, x):
1425 """``min(set)``
1429 """``min(set)``
1426 Changeset with lowest revision number in set.
1430 Changeset with lowest revision number in set.
1427 """
1431 """
1428 os = getset(repo, fullreposet(repo), x)
1432 os = getset(repo, fullreposet(repo), x)
1429 if os:
1433 try:
1430 m = os.min()
1434 m = os.min()
1431 if m in subset:
1435 if m in subset:
1432 return baseset([m])
1436 return baseset([m])
1437 except ValueError:
1438 # os.min() throws a ValueError when the collection is empty.
1439 # Same as python's min().
1440 pass
1433 return baseset()
1441 return baseset()
1434
1442
1435 def modifies(repo, subset, x):
1443 def modifies(repo, subset, x):
1436 """``modifies(pattern)``
1444 """``modifies(pattern)``
1437 Changesets modifying files matched by pattern.
1445 Changesets modifying files matched by pattern.
1438
1446
1439 The pattern without explicit kind like ``glob:`` is expected to be
1447 The pattern without explicit kind like ``glob:`` is expected to be
1440 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
1441 directory.
1449 directory.
1442 """
1450 """
1443 # i18n: "modifies" is a keyword
1451 # i18n: "modifies" is a keyword
1444 pat = getstring(x, _("modifies requires a pattern"))
1452 pat = getstring(x, _("modifies requires a pattern"))
1445 return checkstatus(repo, subset, pat, 0)
1453 return checkstatus(repo, subset, pat, 0)
1446
1454
1447 def named(repo, subset, x):
1455 def named(repo, subset, x):
1448 """``named(namespace)``
1456 """``named(namespace)``
1449 The changesets in a given namespace.
1457 The changesets in a given namespace.
1450
1458
1451 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
1452 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:`,
1453 use the prefix `literal:`.
1461 use the prefix `literal:`.
1454 """
1462 """
1455 # i18n: "named" is a keyword
1463 # i18n: "named" is a keyword
1456 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1464 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1457
1465
1458 ns = getstring(args[0],
1466 ns = getstring(args[0],
1459 # i18n: "named" is a keyword
1467 # i18n: "named" is a keyword
1460 _('the argument to named must be a string'))
1468 _('the argument to named must be a string'))
1461 kind, pattern, matcher = _stringmatcher(ns)
1469 kind, pattern, matcher = _stringmatcher(ns)
1462 namespaces = set()
1470 namespaces = set()
1463 if kind == 'literal':
1471 if kind == 'literal':
1464 if pattern not in repo.names:
1472 if pattern not in repo.names:
1465 raise error.RepoLookupError(_("namespace '%s' does not exist")
1473 raise error.RepoLookupError(_("namespace '%s' does not exist")
1466 % ns)
1474 % ns)
1467 namespaces.add(repo.names[pattern])
1475 namespaces.add(repo.names[pattern])
1468 else:
1476 else:
1469 for name, ns in repo.names.iteritems():
1477 for name, ns in repo.names.iteritems():
1470 if matcher(name):
1478 if matcher(name):
1471 namespaces.add(ns)
1479 namespaces.add(ns)
1472 if not namespaces:
1480 if not namespaces:
1473 raise error.RepoLookupError(_("no namespace exists"
1481 raise error.RepoLookupError(_("no namespace exists"
1474 " that match '%s'") % pattern)
1482 " that match '%s'") % pattern)
1475
1483
1476 names = set()
1484 names = set()
1477 for ns in namespaces:
1485 for ns in namespaces:
1478 for name in ns.listnames(repo):
1486 for name in ns.listnames(repo):
1479 if name not in ns.deprecated:
1487 if name not in ns.deprecated:
1480 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))
1481
1489
1482 names -= set([node.nullrev])
1490 names -= set([node.nullrev])
1483 return subset & names
1491 return subset & names
1484
1492
1485 def node_(repo, subset, x):
1493 def node_(repo, subset, x):
1486 """``id(string)``
1494 """``id(string)``
1487 Revision non-ambiguously specified by the given hex string prefix.
1495 Revision non-ambiguously specified by the given hex string prefix.
1488 """
1496 """
1489 # i18n: "id" is a keyword
1497 # i18n: "id" is a keyword
1490 l = getargs(x, 1, 1, _("id requires one argument"))
1498 l = getargs(x, 1, 1, _("id requires one argument"))
1491 # i18n: "id" is a keyword
1499 # i18n: "id" is a keyword
1492 n = getstring(l[0], _("id requires a string"))
1500 n = getstring(l[0], _("id requires a string"))
1493 if len(n) == 40:
1501 if len(n) == 40:
1494 try:
1502 try:
1495 rn = repo.changelog.rev(node.bin(n))
1503 rn = repo.changelog.rev(node.bin(n))
1496 except (LookupError, TypeError):
1504 except (LookupError, TypeError):
1497 rn = None
1505 rn = None
1498 else:
1506 else:
1499 rn = None
1507 rn = None
1500 pm = repo.changelog._partialmatch(n)
1508 pm = repo.changelog._partialmatch(n)
1501 if pm is not None:
1509 if pm is not None:
1502 rn = repo.changelog.rev(pm)
1510 rn = repo.changelog.rev(pm)
1503
1511
1504 if rn is None:
1512 if rn is None:
1505 return baseset()
1513 return baseset()
1506 result = baseset([rn])
1514 result = baseset([rn])
1507 return result & subset
1515 return result & subset
1508
1516
1509 def obsolete(repo, subset, x):
1517 def obsolete(repo, subset, x):
1510 """``obsolete()``
1518 """``obsolete()``
1511 Mutable changeset with a newer version."""
1519 Mutable changeset with a newer version."""
1512 # i18n: "obsolete" is a keyword
1520 # i18n: "obsolete" is a keyword
1513 getargs(x, 0, 0, _("obsolete takes no arguments"))
1521 getargs(x, 0, 0, _("obsolete takes no arguments"))
1514 obsoletes = obsmod.getrevs(repo, 'obsolete')
1522 obsoletes = obsmod.getrevs(repo, 'obsolete')
1515 return subset & obsoletes
1523 return subset & obsoletes
1516
1524
1517 def only(repo, subset, x):
1525 def only(repo, subset, x):
1518 """``only(set, [set])``
1526 """``only(set, [set])``
1519 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
1520 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
1521 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
1522 (i.e. ::<set1> - ::<set2>).
1530 (i.e. ::<set1> - ::<set2>).
1523 """
1531 """
1524 cl = repo.changelog
1532 cl = repo.changelog
1525 # i18n: "only" is a keyword
1533 # i18n: "only" is a keyword
1526 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1534 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1527 include = getset(repo, fullreposet(repo), args[0])
1535 include = getset(repo, fullreposet(repo), args[0])
1528 if len(args) == 1:
1536 if len(args) == 1:
1529 if not include:
1537 if not include:
1530 return baseset()
1538 return baseset()
1531
1539
1532 descendants = set(_revdescendants(repo, include, False))
1540 descendants = set(_revdescendants(repo, include, False))
1533 exclude = [rev for rev in cl.headrevs()
1541 exclude = [rev for rev in cl.headrevs()
1534 if not rev in descendants and not rev in include]
1542 if not rev in descendants and not rev in include]
1535 else:
1543 else:
1536 exclude = getset(repo, fullreposet(repo), args[1])
1544 exclude = getset(repo, fullreposet(repo), args[1])
1537
1545
1538 results = set(cl.findmissingrevs(common=exclude, heads=include))
1546 results = set(cl.findmissingrevs(common=exclude, heads=include))
1539 # 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
1540 # some optimisations from the fact this is a baseset.
1548 # some optimisations from the fact this is a baseset.
1541 return subset & results
1549 return subset & results
1542
1550
1543 def origin(repo, subset, x):
1551 def origin(repo, subset, x):
1544 """``origin([set])``
1552 """``origin([set])``
1545 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
1546 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
1547 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
1548 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
1549 for the first operation is selected.
1557 for the first operation is selected.
1550 """
1558 """
1551 if x is not None:
1559 if x is not None:
1552 dests = getset(repo, fullreposet(repo), x)
1560 dests = getset(repo, fullreposet(repo), x)
1553 else:
1561 else:
1554 dests = fullreposet(repo)
1562 dests = fullreposet(repo)
1555
1563
1556 def _firstsrc(rev):
1564 def _firstsrc(rev):
1557 src = _getrevsource(repo, rev)
1565 src = _getrevsource(repo, rev)
1558 if src is None:
1566 if src is None:
1559 return None
1567 return None
1560
1568
1561 while True:
1569 while True:
1562 prev = _getrevsource(repo, src)
1570 prev = _getrevsource(repo, src)
1563
1571
1564 if prev is None:
1572 if prev is None:
1565 return src
1573 return src
1566 src = prev
1574 src = prev
1567
1575
1568 o = set([_firstsrc(r) for r in dests])
1576 o = set([_firstsrc(r) for r in dests])
1569 o -= set([None])
1577 o -= set([None])
1570 # 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
1571 # some optimisations from the fact this is a baseset.
1579 # some optimisations from the fact this is a baseset.
1572 return subset & o
1580 return subset & o
1573
1581
1574 def outgoing(repo, subset, x):
1582 def outgoing(repo, subset, x):
1575 """``outgoing([path])``
1583 """``outgoing([path])``
1576 Changesets not found in the specified destination repository, or the
1584 Changesets not found in the specified destination repository, or the
1577 default push location.
1585 default push location.
1578 """
1586 """
1579 # Avoid cycles.
1587 # Avoid cycles.
1580 from . import (
1588 from . import (
1581 discovery,
1589 discovery,
1582 hg,
1590 hg,
1583 )
1591 )
1584 # i18n: "outgoing" is a keyword
1592 # i18n: "outgoing" is a keyword
1585 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1593 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1586 # i18n: "outgoing" is a keyword
1594 # i18n: "outgoing" is a keyword
1587 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 ''
1588 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1596 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1589 dest, branches = hg.parseurl(dest)
1597 dest, branches = hg.parseurl(dest)
1590 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1598 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1591 if revs:
1599 if revs:
1592 revs = [repo.lookup(rev) for rev in revs]
1600 revs = [repo.lookup(rev) for rev in revs]
1593 other = hg.peer(repo, {}, dest)
1601 other = hg.peer(repo, {}, dest)
1594 repo.ui.pushbuffer()
1602 repo.ui.pushbuffer()
1595 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1603 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1596 repo.ui.popbuffer()
1604 repo.ui.popbuffer()
1597 cl = repo.changelog
1605 cl = repo.changelog
1598 o = set([cl.rev(r) for r in outgoing.missing])
1606 o = set([cl.rev(r) for r in outgoing.missing])
1599 return subset & o
1607 return subset & o
1600
1608
1601 def p1(repo, subset, x):
1609 def p1(repo, subset, x):
1602 """``p1([set])``
1610 """``p1([set])``
1603 First parent of changesets in set, or the working directory.
1611 First parent of changesets in set, or the working directory.
1604 """
1612 """
1605 if x is None:
1613 if x is None:
1606 p = repo[x].p1().rev()
1614 p = repo[x].p1().rev()
1607 if p >= 0:
1615 if p >= 0:
1608 return subset & baseset([p])
1616 return subset & baseset([p])
1609 return baseset()
1617 return baseset()
1610
1618
1611 ps = set()
1619 ps = set()
1612 cl = repo.changelog
1620 cl = repo.changelog
1613 for r in getset(repo, fullreposet(repo), x):
1621 for r in getset(repo, fullreposet(repo), x):
1614 ps.add(cl.parentrevs(r)[0])
1622 ps.add(cl.parentrevs(r)[0])
1615 ps -= set([node.nullrev])
1623 ps -= set([node.nullrev])
1616 # 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
1617 # some optimisations from the fact this is a baseset.
1625 # some optimisations from the fact this is a baseset.
1618 return subset & ps
1626 return subset & ps
1619
1627
1620 def p2(repo, subset, x):
1628 def p2(repo, subset, x):
1621 """``p2([set])``
1629 """``p2([set])``
1622 Second parent of changesets in set, or the working directory.
1630 Second parent of changesets in set, or the working directory.
1623 """
1631 """
1624 if x is None:
1632 if x is None:
1625 ps = repo[x].parents()
1633 ps = repo[x].parents()
1626 try:
1634 try:
1627 p = ps[1].rev()
1635 p = ps[1].rev()
1628 if p >= 0:
1636 if p >= 0:
1629 return subset & baseset([p])
1637 return subset & baseset([p])
1630 return baseset()
1638 return baseset()
1631 except IndexError:
1639 except IndexError:
1632 return baseset()
1640 return baseset()
1633
1641
1634 ps = set()
1642 ps = set()
1635 cl = repo.changelog
1643 cl = repo.changelog
1636 for r in getset(repo, fullreposet(repo), x):
1644 for r in getset(repo, fullreposet(repo), x):
1637 ps.add(cl.parentrevs(r)[1])
1645 ps.add(cl.parentrevs(r)[1])
1638 ps -= set([node.nullrev])
1646 ps -= set([node.nullrev])
1639 # 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
1640 # some optimisations from the fact this is a baseset.
1648 # some optimisations from the fact this is a baseset.
1641 return subset & ps
1649 return subset & ps
1642
1650
1643 def parents(repo, subset, x):
1651 def parents(repo, subset, x):
1644 """``parents([set])``
1652 """``parents([set])``
1645 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.
1646 """
1654 """
1647 if x is None:
1655 if x is None:
1648 ps = set(p.rev() for p in repo[x].parents())
1656 ps = set(p.rev() for p in repo[x].parents())
1649 else:
1657 else:
1650 ps = set()
1658 ps = set()
1651 cl = repo.changelog
1659 cl = repo.changelog
1652 up = ps.update
1660 up = ps.update
1653 parentrevs = cl.parentrevs
1661 parentrevs = cl.parentrevs
1654 for r in getset(repo, fullreposet(repo), x):
1662 for r in getset(repo, fullreposet(repo), x):
1655 if r == node.wdirrev:
1663 if r == node.wdirrev:
1656 up(p.rev() for p in repo[r].parents())
1664 up(p.rev() for p in repo[r].parents())
1657 else:
1665 else:
1658 up(parentrevs(r))
1666 up(parentrevs(r))
1659 ps -= set([node.nullrev])
1667 ps -= set([node.nullrev])
1660 return subset & ps
1668 return subset & ps
1661
1669
1662 def _phase(repo, subset, target):
1670 def _phase(repo, subset, target):
1663 """helper to select all rev in phase <target>"""
1671 """helper to select all rev in phase <target>"""
1664 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1672 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1665 if repo._phasecache._phasesets:
1673 if repo._phasecache._phasesets:
1666 s = repo._phasecache._phasesets[target] - repo.changelog.filteredrevs
1674 s = repo._phasecache._phasesets[target] - repo.changelog.filteredrevs
1667 s = baseset(s)
1675 s = baseset(s)
1668 s.sort() # set are non ordered, so we enforce ascending
1676 s.sort() # set are non ordered, so we enforce ascending
1669 return subset & s
1677 return subset & s
1670 else:
1678 else:
1671 phase = repo._phasecache.phase
1679 phase = repo._phasecache.phase
1672 condition = lambda r: phase(repo, r) == target
1680 condition = lambda r: phase(repo, r) == target
1673 return subset.filter(condition, cache=False)
1681 return subset.filter(condition, cache=False)
1674
1682
1675 def draft(repo, subset, x):
1683 def draft(repo, subset, x):
1676 """``draft()``
1684 """``draft()``
1677 Changeset in draft phase."""
1685 Changeset in draft phase."""
1678 # i18n: "draft" is a keyword
1686 # i18n: "draft" is a keyword
1679 getargs(x, 0, 0, _("draft takes no arguments"))
1687 getargs(x, 0, 0, _("draft takes no arguments"))
1680 target = phases.draft
1688 target = phases.draft
1681 return _phase(repo, subset, target)
1689 return _phase(repo, subset, target)
1682
1690
1683 def secret(repo, subset, x):
1691 def secret(repo, subset, x):
1684 """``secret()``
1692 """``secret()``
1685 Changeset in secret phase."""
1693 Changeset in secret phase."""
1686 # i18n: "secret" is a keyword
1694 # i18n: "secret" is a keyword
1687 getargs(x, 0, 0, _("secret takes no arguments"))
1695 getargs(x, 0, 0, _("secret takes no arguments"))
1688 target = phases.secret
1696 target = phases.secret
1689 return _phase(repo, subset, target)
1697 return _phase(repo, subset, target)
1690
1698
1691 def parentspec(repo, subset, x, n):
1699 def parentspec(repo, subset, x, n):
1692 """``set^0``
1700 """``set^0``
1693 The set.
1701 The set.
1694 ``set^1`` (or ``set^``), ``set^2``
1702 ``set^1`` (or ``set^``), ``set^2``
1695 First or second parent, respectively, of all changesets in set.
1703 First or second parent, respectively, of all changesets in set.
1696 """
1704 """
1697 try:
1705 try:
1698 n = int(n[1])
1706 n = int(n[1])
1699 if n not in (0, 1, 2):
1707 if n not in (0, 1, 2):
1700 raise ValueError
1708 raise ValueError
1701 except (TypeError, ValueError):
1709 except (TypeError, ValueError):
1702 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1710 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1703 ps = set()
1711 ps = set()
1704 cl = repo.changelog
1712 cl = repo.changelog
1705 for r in getset(repo, fullreposet(repo), x):
1713 for r in getset(repo, fullreposet(repo), x):
1706 if n == 0:
1714 if n == 0:
1707 ps.add(r)
1715 ps.add(r)
1708 elif n == 1:
1716 elif n == 1:
1709 ps.add(cl.parentrevs(r)[0])
1717 ps.add(cl.parentrevs(r)[0])
1710 elif n == 2:
1718 elif n == 2:
1711 parents = cl.parentrevs(r)
1719 parents = cl.parentrevs(r)
1712 if len(parents) > 1:
1720 if len(parents) > 1:
1713 ps.add(parents[1])
1721 ps.add(parents[1])
1714 return subset & ps
1722 return subset & ps
1715
1723
1716 def present(repo, subset, x):
1724 def present(repo, subset, x):
1717 """``present(set)``
1725 """``present(set)``
1718 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,
1719 all revisions in set.
1727 all revisions in set.
1720
1728
1721 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,
1722 the query is normally aborted. But this predicate allows the query
1730 the query is normally aborted. But this predicate allows the query
1723 to continue even in such cases.
1731 to continue even in such cases.
1724 """
1732 """
1725 try:
1733 try:
1726 return getset(repo, subset, x)
1734 return getset(repo, subset, x)
1727 except error.RepoLookupError:
1735 except error.RepoLookupError:
1728 return baseset()
1736 return baseset()
1729
1737
1730 # for internal use
1738 # for internal use
1731 def _notpublic(repo, subset, x):
1739 def _notpublic(repo, subset, x):
1732 getargs(x, 0, 0, "_notpublic takes no arguments")
1740 getargs(x, 0, 0, "_notpublic takes no arguments")
1733 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1741 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1734 if repo._phasecache._phasesets:
1742 if repo._phasecache._phasesets:
1735 s = set()
1743 s = set()
1736 for u in repo._phasecache._phasesets[1:]:
1744 for u in repo._phasecache._phasesets[1:]:
1737 s.update(u)
1745 s.update(u)
1738 s = baseset(s - repo.changelog.filteredrevs)
1746 s = baseset(s - repo.changelog.filteredrevs)
1739 s.sort()
1747 s.sort()
1740 return subset & s
1748 return subset & s
1741 else:
1749 else:
1742 phase = repo._phasecache.phase
1750 phase = repo._phasecache.phase
1743 target = phases.public
1751 target = phases.public
1744 condition = lambda r: phase(repo, r) != target
1752 condition = lambda r: phase(repo, r) != target
1745 return subset.filter(condition, cache=False)
1753 return subset.filter(condition, cache=False)
1746
1754
1747 def public(repo, subset, x):
1755 def public(repo, subset, x):
1748 """``public()``
1756 """``public()``
1749 Changeset in public phase."""
1757 Changeset in public phase."""
1750 # i18n: "public" is a keyword
1758 # i18n: "public" is a keyword
1751 getargs(x, 0, 0, _("public takes no arguments"))
1759 getargs(x, 0, 0, _("public takes no arguments"))
1752 phase = repo._phasecache.phase
1760 phase = repo._phasecache.phase
1753 target = phases.public
1761 target = phases.public
1754 condition = lambda r: phase(repo, r) == target
1762 condition = lambda r: phase(repo, r) == target
1755 return subset.filter(condition, cache=False)
1763 return subset.filter(condition, cache=False)
1756
1764
1757 def remote(repo, subset, x):
1765 def remote(repo, subset, x):
1758 """``remote([id [,path]])``
1766 """``remote([id [,path]])``
1759 Local revision that corresponds to the given identifier in a
1767 Local revision that corresponds to the given identifier in a
1760 remote repository, if present. Here, the '.' identifier is a
1768 remote repository, if present. Here, the '.' identifier is a
1761 synonym for the current local branch.
1769 synonym for the current local branch.
1762 """
1770 """
1763
1771
1764 from . import hg # avoid start-up nasties
1772 from . import hg # avoid start-up nasties
1765 # i18n: "remote" is a keyword
1773 # i18n: "remote" is a keyword
1766 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"))
1767
1775
1768 q = '.'
1776 q = '.'
1769 if len(l) > 0:
1777 if len(l) > 0:
1770 # i18n: "remote" is a keyword
1778 # i18n: "remote" is a keyword
1771 q = getstring(l[0], _("remote requires a string id"))
1779 q = getstring(l[0], _("remote requires a string id"))
1772 if q == '.':
1780 if q == '.':
1773 q = repo['.'].branch()
1781 q = repo['.'].branch()
1774
1782
1775 dest = ''
1783 dest = ''
1776 if len(l) > 1:
1784 if len(l) > 1:
1777 # i18n: "remote" is a keyword
1785 # i18n: "remote" is a keyword
1778 dest = getstring(l[1], _("remote requires a repository path"))
1786 dest = getstring(l[1], _("remote requires a repository path"))
1779 dest = repo.ui.expandpath(dest or 'default')
1787 dest = repo.ui.expandpath(dest or 'default')
1780 dest, branches = hg.parseurl(dest)
1788 dest, branches = hg.parseurl(dest)
1781 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1789 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1782 if revs:
1790 if revs:
1783 revs = [repo.lookup(rev) for rev in revs]
1791 revs = [repo.lookup(rev) for rev in revs]
1784 other = hg.peer(repo, {}, dest)
1792 other = hg.peer(repo, {}, dest)
1785 n = other.lookup(q)
1793 n = other.lookup(q)
1786 if n in repo:
1794 if n in repo:
1787 r = repo[n].rev()
1795 r = repo[n].rev()
1788 if r in subset:
1796 if r in subset:
1789 return baseset([r])
1797 return baseset([r])
1790 return baseset()
1798 return baseset()
1791
1799
1792 def removes(repo, subset, x):
1800 def removes(repo, subset, x):
1793 """``removes(pattern)``
1801 """``removes(pattern)``
1794 Changesets which remove files matching pattern.
1802 Changesets which remove files matching pattern.
1795
1803
1796 The pattern without explicit kind like ``glob:`` is expected to be
1804 The pattern without explicit kind like ``glob:`` is expected to be
1797 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
1798 directory.
1806 directory.
1799 """
1807 """
1800 # i18n: "removes" is a keyword
1808 # i18n: "removes" is a keyword
1801 pat = getstring(x, _("removes requires a pattern"))
1809 pat = getstring(x, _("removes requires a pattern"))
1802 return checkstatus(repo, subset, pat, 2)
1810 return checkstatus(repo, subset, pat, 2)
1803
1811
1804 def rev(repo, subset, x):
1812 def rev(repo, subset, x):
1805 """``rev(number)``
1813 """``rev(number)``
1806 Revision with the given numeric identifier.
1814 Revision with the given numeric identifier.
1807 """
1815 """
1808 # i18n: "rev" is a keyword
1816 # i18n: "rev" is a keyword
1809 l = getargs(x, 1, 1, _("rev requires one argument"))
1817 l = getargs(x, 1, 1, _("rev requires one argument"))
1810 try:
1818 try:
1811 # i18n: "rev" is a keyword
1819 # i18n: "rev" is a keyword
1812 l = int(getstring(l[0], _("rev requires a number")))
1820 l = int(getstring(l[0], _("rev requires a number")))
1813 except (TypeError, ValueError):
1821 except (TypeError, ValueError):
1814 # i18n: "rev" is a keyword
1822 # i18n: "rev" is a keyword
1815 raise error.ParseError(_("rev expects a number"))
1823 raise error.ParseError(_("rev expects a number"))
1816 if l not in repo.changelog and l != node.nullrev:
1824 if l not in repo.changelog and l != node.nullrev:
1817 return baseset()
1825 return baseset()
1818 return subset & baseset([l])
1826 return subset & baseset([l])
1819
1827
1820 def matching(repo, subset, x):
1828 def matching(repo, subset, x):
1821 """``matching(revision [, field])``
1829 """``matching(revision [, field])``
1822 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
1823 selected revision or set.
1831 selected revision or set.
1824
1832
1825 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
1826 by spaces (e.g. ``author description``).
1834 by spaces (e.g. ``author description``).
1827
1835
1828 Valid fields are most regular revision fields and some special fields.
1836 Valid fields are most regular revision fields and some special fields.
1829
1837
1830 Regular revision fields are ``description``, ``author``, ``branch``,
1838 Regular revision fields are ``description``, ``author``, ``branch``,
1831 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1839 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1832 and ``diff``.
1840 and ``diff``.
1833 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1841 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1834 contents of the revision. Two revisions matching their ``diff`` will
1842 contents of the revision. Two revisions matching their ``diff`` will
1835 also match their ``files``.
1843 also match their ``files``.
1836
1844
1837 Special fields are ``summary`` and ``metadata``:
1845 Special fields are ``summary`` and ``metadata``:
1838 ``summary`` matches the first line of the description.
1846 ``summary`` matches the first line of the description.
1839 ``metadata`` is equivalent to matching ``description user date``
1847 ``metadata`` is equivalent to matching ``description user date``
1840 (i.e. it matches the main metadata fields).
1848 (i.e. it matches the main metadata fields).
1841
1849
1842 ``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
1843 specified. You can match more than one field at a time.
1851 specified. You can match more than one field at a time.
1844 """
1852 """
1845 # i18n: "matching" is a keyword
1853 # i18n: "matching" is a keyword
1846 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1854 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1847
1855
1848 revs = getset(repo, fullreposet(repo), l[0])
1856 revs = getset(repo, fullreposet(repo), l[0])
1849
1857
1850 fieldlist = ['metadata']
1858 fieldlist = ['metadata']
1851 if len(l) > 1:
1859 if len(l) > 1:
1852 fieldlist = getstring(l[1],
1860 fieldlist = getstring(l[1],
1853 # i18n: "matching" is a keyword
1861 # i18n: "matching" is a keyword
1854 _("matching requires a string "
1862 _("matching requires a string "
1855 "as its second argument")).split()
1863 "as its second argument")).split()
1856
1864
1857 # Make sure that there are no repeated fields,
1865 # Make sure that there are no repeated fields,
1858 # expand the 'special' 'metadata' field type
1866 # expand the 'special' 'metadata' field type
1859 # and check the 'files' whenever we check the 'diff'
1867 # and check the 'files' whenever we check the 'diff'
1860 fields = []
1868 fields = []
1861 for field in fieldlist:
1869 for field in fieldlist:
1862 if field == 'metadata':
1870 if field == 'metadata':
1863 fields += ['user', 'description', 'date']
1871 fields += ['user', 'description', 'date']
1864 elif field == 'diff':
1872 elif field == 'diff':
1865 # a revision matching the diff must also match the files
1873 # a revision matching the diff must also match the files
1866 # since matching the diff is very costly, make sure to
1874 # since matching the diff is very costly, make sure to
1867 # also match the files first
1875 # also match the files first
1868 fields += ['files', 'diff']
1876 fields += ['files', 'diff']
1869 else:
1877 else:
1870 if field == 'author':
1878 if field == 'author':
1871 field = 'user'
1879 field = 'user'
1872 fields.append(field)
1880 fields.append(field)
1873 fields = set(fields)
1881 fields = set(fields)
1874 if 'summary' in fields and 'description' in fields:
1882 if 'summary' in fields and 'description' in fields:
1875 # If a revision matches its description it also matches its summary
1883 # If a revision matches its description it also matches its summary
1876 fields.discard('summary')
1884 fields.discard('summary')
1877
1885
1878 # We may want to match more than one field
1886 # We may want to match more than one field
1879 # 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
1880 # Sort the selected fields in order of increasing matching cost
1888 # Sort the selected fields in order of increasing matching cost
1881 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1889 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1882 'files', 'description', 'substate', 'diff']
1890 'files', 'description', 'substate', 'diff']
1883 def fieldkeyfunc(f):
1891 def fieldkeyfunc(f):
1884 try:
1892 try:
1885 return fieldorder.index(f)
1893 return fieldorder.index(f)
1886 except ValueError:
1894 except ValueError:
1887 # assume an unknown field is very costly
1895 # assume an unknown field is very costly
1888 return len(fieldorder)
1896 return len(fieldorder)
1889 fields = list(fields)
1897 fields = list(fields)
1890 fields.sort(key=fieldkeyfunc)
1898 fields.sort(key=fieldkeyfunc)
1891
1899
1892 # Each field will be matched with its own "getfield" function
1900 # Each field will be matched with its own "getfield" function
1893 # which will be added to the getfieldfuncs array of functions
1901 # which will be added to the getfieldfuncs array of functions
1894 getfieldfuncs = []
1902 getfieldfuncs = []
1895 _funcs = {
1903 _funcs = {
1896 'user': lambda r: repo[r].user(),
1904 'user': lambda r: repo[r].user(),
1897 'branch': lambda r: repo[r].branch(),
1905 'branch': lambda r: repo[r].branch(),
1898 'date': lambda r: repo[r].date(),
1906 'date': lambda r: repo[r].date(),
1899 'description': lambda r: repo[r].description(),
1907 'description': lambda r: repo[r].description(),
1900 'files': lambda r: repo[r].files(),
1908 'files': lambda r: repo[r].files(),
1901 'parents': lambda r: repo[r].parents(),
1909 'parents': lambda r: repo[r].parents(),
1902 'phase': lambda r: repo[r].phase(),
1910 'phase': lambda r: repo[r].phase(),
1903 'substate': lambda r: repo[r].substate,
1911 'substate': lambda r: repo[r].substate,
1904 'summary': lambda r: repo[r].description().splitlines()[0],
1912 'summary': lambda r: repo[r].description().splitlines()[0],
1905 'diff': lambda r: list(repo[r].diff(git=True),)
1913 'diff': lambda r: list(repo[r].diff(git=True),)
1906 }
1914 }
1907 for info in fields:
1915 for info in fields:
1908 getfield = _funcs.get(info, None)
1916 getfield = _funcs.get(info, None)
1909 if getfield is None:
1917 if getfield is None:
1910 raise error.ParseError(
1918 raise error.ParseError(
1911 # i18n: "matching" is a keyword
1919 # i18n: "matching" is a keyword
1912 _("unexpected field name passed to matching: %s") % info)
1920 _("unexpected field name passed to matching: %s") % info)
1913 getfieldfuncs.append(getfield)
1921 getfieldfuncs.append(getfield)
1914 # convert the getfield array of functions into a "getinfo" function
1922 # convert the getfield array of functions into a "getinfo" function
1915 # 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
1916 # is only one field to match)
1924 # is only one field to match)
1917 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1925 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1918
1926
1919 def matches(x):
1927 def matches(x):
1920 for rev in revs:
1928 for rev in revs:
1921 target = getinfo(rev)
1929 target = getinfo(rev)
1922 match = True
1930 match = True
1923 for n, f in enumerate(getfieldfuncs):
1931 for n, f in enumerate(getfieldfuncs):
1924 if target[n] != f(x):
1932 if target[n] != f(x):
1925 match = False
1933 match = False
1926 if match:
1934 if match:
1927 return True
1935 return True
1928 return False
1936 return False
1929
1937
1930 return subset.filter(matches)
1938 return subset.filter(matches)
1931
1939
1932 def reverse(repo, subset, x):
1940 def reverse(repo, subset, x):
1933 """``reverse(set)``
1941 """``reverse(set)``
1934 Reverse order of set.
1942 Reverse order of set.
1935 """
1943 """
1936 l = getset(repo, subset, x)
1944 l = getset(repo, subset, x)
1937 l.reverse()
1945 l.reverse()
1938 return l
1946 return l
1939
1947
1940 def roots(repo, subset, x):
1948 def roots(repo, subset, x):
1941 """``roots(set)``
1949 """``roots(set)``
1942 Changesets in set with no parent changeset in set.
1950 Changesets in set with no parent changeset in set.
1943 """
1951 """
1944 s = getset(repo, fullreposet(repo), x)
1952 s = getset(repo, fullreposet(repo), x)
1945 parents = repo.changelog.parentrevs
1953 parents = repo.changelog.parentrevs
1946 def filter(r):
1954 def filter(r):
1947 for p in parents(r):
1955 for p in parents(r):
1948 if 0 <= p and p in s:
1956 if 0 <= p and p in s:
1949 return False
1957 return False
1950 return True
1958 return True
1951 return subset & s.filter(filter)
1959 return subset & s.filter(filter)
1952
1960
1953 def sort(repo, subset, x):
1961 def sort(repo, subset, x):
1954 """``sort(set[, [-]key...])``
1962 """``sort(set[, [-]key...])``
1955 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
1956 as ``-key`` to sort in descending order.
1964 as ``-key`` to sort in descending order.
1957
1965
1958 The keys can be:
1966 The keys can be:
1959
1967
1960 - ``rev`` for the revision number,
1968 - ``rev`` for the revision number,
1961 - ``branch`` for the branch name,
1969 - ``branch`` for the branch name,
1962 - ``desc`` for the commit message (description),
1970 - ``desc`` for the commit message (description),
1963 - ``user`` for user name (``author`` can be used as an alias),
1971 - ``user`` for user name (``author`` can be used as an alias),
1964 - ``date`` for the commit date
1972 - ``date`` for the commit date
1965 """
1973 """
1966 # i18n: "sort" is a keyword
1974 # i18n: "sort" is a keyword
1967 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1975 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1968 keys = "rev"
1976 keys = "rev"
1969 if len(l) == 2:
1977 if len(l) == 2:
1970 # i18n: "sort" is a keyword
1978 # i18n: "sort" is a keyword
1971 keys = getstring(l[1], _("sort spec must be a string"))
1979 keys = getstring(l[1], _("sort spec must be a string"))
1972
1980
1973 s = l[0]
1981 s = l[0]
1974 keys = keys.split()
1982 keys = keys.split()
1975 l = []
1983 l = []
1976 def invert(s):
1984 def invert(s):
1977 return "".join(chr(255 - ord(c)) for c in s)
1985 return "".join(chr(255 - ord(c)) for c in s)
1978 revs = getset(repo, subset, s)
1986 revs = getset(repo, subset, s)
1979 if keys == ["rev"]:
1987 if keys == ["rev"]:
1980 revs.sort()
1988 revs.sort()
1981 return revs
1989 return revs
1982 elif keys == ["-rev"]:
1990 elif keys == ["-rev"]:
1983 revs.sort(reverse=True)
1991 revs.sort(reverse=True)
1984 return revs
1992 return revs
1985 for r in revs:
1993 for r in revs:
1986 c = repo[r]
1994 c = repo[r]
1987 e = []
1995 e = []
1988 for k in keys:
1996 for k in keys:
1989 if k == 'rev':
1997 if k == 'rev':
1990 e.append(r)
1998 e.append(r)
1991 elif k == '-rev':
1999 elif k == '-rev':
1992 e.append(-r)
2000 e.append(-r)
1993 elif k == 'branch':
2001 elif k == 'branch':
1994 e.append(c.branch())
2002 e.append(c.branch())
1995 elif k == '-branch':
2003 elif k == '-branch':
1996 e.append(invert(c.branch()))
2004 e.append(invert(c.branch()))
1997 elif k == 'desc':
2005 elif k == 'desc':
1998 e.append(c.description())
2006 e.append(c.description())
1999 elif k == '-desc':
2007 elif k == '-desc':
2000 e.append(invert(c.description()))
2008 e.append(invert(c.description()))
2001 elif k in 'user author':
2009 elif k in 'user author':
2002 e.append(c.user())
2010 e.append(c.user())
2003 elif k in '-user -author':
2011 elif k in '-user -author':
2004 e.append(invert(c.user()))
2012 e.append(invert(c.user()))
2005 elif k == 'date':
2013 elif k == 'date':
2006 e.append(c.date()[0])
2014 e.append(c.date()[0])
2007 elif k == '-date':
2015 elif k == '-date':
2008 e.append(-c.date()[0])
2016 e.append(-c.date()[0])
2009 else:
2017 else:
2010 raise error.ParseError(_("unknown sort key %r") % k)
2018 raise error.ParseError(_("unknown sort key %r") % k)
2011 e.append(r)
2019 e.append(r)
2012 l.append(e)
2020 l.append(e)
2013 l.sort()
2021 l.sort()
2014 return baseset([e[-1] for e in l])
2022 return baseset([e[-1] for e in l])
2015
2023
2016 def subrepo(repo, subset, x):
2024 def subrepo(repo, subset, x):
2017 """``subrepo([pattern])``
2025 """``subrepo([pattern])``
2018 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
2019 pattern is named, any subrepo changes are returned.
2027 pattern is named, any subrepo changes are returned.
2020 """
2028 """
2021 # i18n: "subrepo" is a keyword
2029 # i18n: "subrepo" is a keyword
2022 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
2030 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
2023 if len(args) != 0:
2031 if len(args) != 0:
2024 pat = getstring(args[0], _("subrepo requires a pattern"))
2032 pat = getstring(args[0], _("subrepo requires a pattern"))
2025
2033
2026 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
2034 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
2027
2035
2028 def submatches(names):
2036 def submatches(names):
2029 k, p, m = _stringmatcher(pat)
2037 k, p, m = _stringmatcher(pat)
2030 for name in names:
2038 for name in names:
2031 if m(name):
2039 if m(name):
2032 yield name
2040 yield name
2033
2041
2034 def matches(x):
2042 def matches(x):
2035 c = repo[x]
2043 c = repo[x]
2036 s = repo.status(c.p1().node(), c.node(), match=m)
2044 s = repo.status(c.p1().node(), c.node(), match=m)
2037
2045
2038 if len(args) == 0:
2046 if len(args) == 0:
2039 return s.added or s.modified or s.removed
2047 return s.added or s.modified or s.removed
2040
2048
2041 if s.added:
2049 if s.added:
2042 return any(submatches(c.substate.keys()))
2050 return any(submatches(c.substate.keys()))
2043
2051
2044 if s.modified:
2052 if s.modified:
2045 subs = set(c.p1().substate.keys())
2053 subs = set(c.p1().substate.keys())
2046 subs.update(c.substate.keys())
2054 subs.update(c.substate.keys())
2047
2055
2048 for path in submatches(subs):
2056 for path in submatches(subs):
2049 if c.p1().substate.get(path) != c.substate.get(path):
2057 if c.p1().substate.get(path) != c.substate.get(path):
2050 return True
2058 return True
2051
2059
2052 if s.removed:
2060 if s.removed:
2053 return any(submatches(c.p1().substate.keys()))
2061 return any(submatches(c.p1().substate.keys()))
2054
2062
2055 return False
2063 return False
2056
2064
2057 return subset.filter(matches)
2065 return subset.filter(matches)
2058
2066
2059 def _stringmatcher(pattern):
2067 def _stringmatcher(pattern):
2060 """
2068 """
2061 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2069 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2062 returns the matcher name, pattern, and matcher function.
2070 returns the matcher name, pattern, and matcher function.
2063 missing or unknown prefixes are treated as literal matches.
2071 missing or unknown prefixes are treated as literal matches.
2064
2072
2065 helper for tests:
2073 helper for tests:
2066 >>> def test(pattern, *tests):
2074 >>> def test(pattern, *tests):
2067 ... kind, pattern, matcher = _stringmatcher(pattern)
2075 ... kind, pattern, matcher = _stringmatcher(pattern)
2068 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2076 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2069
2077
2070 exact matching (no prefix):
2078 exact matching (no prefix):
2071 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2079 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2072 ('literal', 'abcdefg', [False, False, True])
2080 ('literal', 'abcdefg', [False, False, True])
2073
2081
2074 regex matching ('re:' prefix)
2082 regex matching ('re:' prefix)
2075 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2083 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2076 ('re', 'a.+b', [False, False, True])
2084 ('re', 'a.+b', [False, False, True])
2077
2085
2078 force exact matches ('literal:' prefix)
2086 force exact matches ('literal:' prefix)
2079 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2087 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2080 ('literal', 're:foobar', [False, True])
2088 ('literal', 're:foobar', [False, True])
2081
2089
2082 unknown prefixes are ignored and treated as literals
2090 unknown prefixes are ignored and treated as literals
2083 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2091 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2084 ('literal', 'foo:bar', [False, False, True])
2092 ('literal', 'foo:bar', [False, False, True])
2085 """
2093 """
2086 if pattern.startswith('re:'):
2094 if pattern.startswith('re:'):
2087 pattern = pattern[3:]
2095 pattern = pattern[3:]
2088 try:
2096 try:
2089 regex = re.compile(pattern)
2097 regex = re.compile(pattern)
2090 except re.error as e:
2098 except re.error as e:
2091 raise error.ParseError(_('invalid regular expression: %s')
2099 raise error.ParseError(_('invalid regular expression: %s')
2092 % e)
2100 % e)
2093 return 're', pattern, regex.search
2101 return 're', pattern, regex.search
2094 elif pattern.startswith('literal:'):
2102 elif pattern.startswith('literal:'):
2095 pattern = pattern[8:]
2103 pattern = pattern[8:]
2096 return 'literal', pattern, pattern.__eq__
2104 return 'literal', pattern, pattern.__eq__
2097
2105
2098 def _substringmatcher(pattern):
2106 def _substringmatcher(pattern):
2099 kind, pattern, matcher = _stringmatcher(pattern)
2107 kind, pattern, matcher = _stringmatcher(pattern)
2100 if kind == 'literal':
2108 if kind == 'literal':
2101 matcher = lambda s: pattern in s
2109 matcher = lambda s: pattern in s
2102 return kind, pattern, matcher
2110 return kind, pattern, matcher
2103
2111
2104 def tag(repo, subset, x):
2112 def tag(repo, subset, x):
2105 """``tag([name])``
2113 """``tag([name])``
2106 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.
2107
2115
2108 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
2109 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:`,
2110 use the prefix `literal:`.
2118 use the prefix `literal:`.
2111 """
2119 """
2112 # i18n: "tag" is a keyword
2120 # i18n: "tag" is a keyword
2113 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
2121 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
2114 cl = repo.changelog
2122 cl = repo.changelog
2115 if args:
2123 if args:
2116 pattern = getstring(args[0],
2124 pattern = getstring(args[0],
2117 # i18n: "tag" is a keyword
2125 # i18n: "tag" is a keyword
2118 _('the argument to tag must be a string'))
2126 _('the argument to tag must be a string'))
2119 kind, pattern, matcher = _stringmatcher(pattern)
2127 kind, pattern, matcher = _stringmatcher(pattern)
2120 if kind == 'literal':
2128 if kind == 'literal':
2121 # avoid resolving all tags
2129 # avoid resolving all tags
2122 tn = repo._tagscache.tags.get(pattern, None)
2130 tn = repo._tagscache.tags.get(pattern, None)
2123 if tn is None:
2131 if tn is None:
2124 raise error.RepoLookupError(_("tag '%s' does not exist")
2132 raise error.RepoLookupError(_("tag '%s' does not exist")
2125 % pattern)
2133 % pattern)
2126 s = set([repo[tn].rev()])
2134 s = set([repo[tn].rev()])
2127 else:
2135 else:
2128 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)])
2129 else:
2137 else:
2130 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'])
2131 return subset & s
2139 return subset & s
2132
2140
2133 def tagged(repo, subset, x):
2141 def tagged(repo, subset, x):
2134 return tag(repo, subset, x)
2142 return tag(repo, subset, x)
2135
2143
2136 def unstable(repo, subset, x):
2144 def unstable(repo, subset, x):
2137 """``unstable()``
2145 """``unstable()``
2138 Non-obsolete changesets with obsolete ancestors.
2146 Non-obsolete changesets with obsolete ancestors.
2139 """
2147 """
2140 # i18n: "unstable" is a keyword
2148 # i18n: "unstable" is a keyword
2141 getargs(x, 0, 0, _("unstable takes no arguments"))
2149 getargs(x, 0, 0, _("unstable takes no arguments"))
2142 unstables = obsmod.getrevs(repo, 'unstable')
2150 unstables = obsmod.getrevs(repo, 'unstable')
2143 return subset & unstables
2151 return subset & unstables
2144
2152
2145
2153
2146 def user(repo, subset, x):
2154 def user(repo, subset, x):
2147 """``user(string)``
2155 """``user(string)``
2148 User name contains string. The match is case-insensitive.
2156 User name contains string. The match is case-insensitive.
2149
2157
2150 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
2151 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
2152 the prefix `literal:`.
2160 the prefix `literal:`.
2153 """
2161 """
2154 return author(repo, subset, x)
2162 return author(repo, subset, x)
2155
2163
2156 # experimental
2164 # experimental
2157 def wdir(repo, subset, x):
2165 def wdir(repo, subset, x):
2158 # i18n: "wdir" is a keyword
2166 # i18n: "wdir" is a keyword
2159 getargs(x, 0, 0, _("wdir takes no arguments"))
2167 getargs(x, 0, 0, _("wdir takes no arguments"))
2160 if node.wdirrev in subset or isinstance(subset, fullreposet):
2168 if node.wdirrev in subset or isinstance(subset, fullreposet):
2161 return baseset([node.wdirrev])
2169 return baseset([node.wdirrev])
2162 return baseset()
2170 return baseset()
2163
2171
2164 # for internal use
2172 # for internal use
2165 def _list(repo, subset, x):
2173 def _list(repo, subset, x):
2166 s = getstring(x, "internal error")
2174 s = getstring(x, "internal error")
2167 if not s:
2175 if not s:
2168 return baseset()
2176 return baseset()
2169 # remove duplicates here. it's difficult for caller to deduplicate sets
2177 # remove duplicates here. it's difficult for caller to deduplicate sets
2170 # because different symbols can point to the same rev.
2178 # because different symbols can point to the same rev.
2171 cl = repo.changelog
2179 cl = repo.changelog
2172 ls = []
2180 ls = []
2173 seen = set()
2181 seen = set()
2174 for t in s.split('\0'):
2182 for t in s.split('\0'):
2175 try:
2183 try:
2176 # fast path for integer revision
2184 # fast path for integer revision
2177 r = int(t)
2185 r = int(t)
2178 if str(r) != t or r not in cl:
2186 if str(r) != t or r not in cl:
2179 raise ValueError
2187 raise ValueError
2180 revs = [r]
2188 revs = [r]
2181 except ValueError:
2189 except ValueError:
2182 revs = stringset(repo, subset, t)
2190 revs = stringset(repo, subset, t)
2183
2191
2184 for r in revs:
2192 for r in revs:
2185 if r in seen:
2193 if r in seen:
2186 continue
2194 continue
2187 if (r in subset
2195 if (r in subset
2188 or r == node.nullrev and isinstance(subset, fullreposet)):
2196 or r == node.nullrev and isinstance(subset, fullreposet)):
2189 ls.append(r)
2197 ls.append(r)
2190 seen.add(r)
2198 seen.add(r)
2191 return baseset(ls)
2199 return baseset(ls)
2192
2200
2193 # for internal use
2201 # for internal use
2194 def _intlist(repo, subset, x):
2202 def _intlist(repo, subset, x):
2195 s = getstring(x, "internal error")
2203 s = getstring(x, "internal error")
2196 if not s:
2204 if not s:
2197 return baseset()
2205 return baseset()
2198 ls = [int(r) for r in s.split('\0')]
2206 ls = [int(r) for r in s.split('\0')]
2199 s = subset
2207 s = subset
2200 return baseset([r for r in ls if r in s])
2208 return baseset([r for r in ls if r in s])
2201
2209
2202 # for internal use
2210 # for internal use
2203 def _hexlist(repo, subset, x):
2211 def _hexlist(repo, subset, x):
2204 s = getstring(x, "internal error")
2212 s = getstring(x, "internal error")
2205 if not s:
2213 if not s:
2206 return baseset()
2214 return baseset()
2207 cl = repo.changelog
2215 cl = repo.changelog
2208 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')]
2209 s = subset
2217 s = subset
2210 return baseset([r for r in ls if r in s])
2218 return baseset([r for r in ls if r in s])
2211
2219
2212 symbols = {
2220 symbols = {
2213 "_mergedefaultdest": _mergedefaultdest,
2221 "_mergedefaultdest": _mergedefaultdest,
2214 "_updatedefaultdest": _updatedefaultdest,
2222 "_updatedefaultdest": _updatedefaultdest,
2215 "adds": adds,
2223 "adds": adds,
2216 "all": getall,
2224 "all": getall,
2217 "ancestor": ancestor,
2225 "ancestor": ancestor,
2218 "ancestors": ancestors,
2226 "ancestors": ancestors,
2219 "_firstancestors": _firstancestors,
2227 "_firstancestors": _firstancestors,
2220 "author": author,
2228 "author": author,
2221 "bisect": bisect,
2229 "bisect": bisect,
2222 "bisected": bisected,
2230 "bisected": bisected,
2223 "bookmark": bookmark,
2231 "bookmark": bookmark,
2224 "branch": branch,
2232 "branch": branch,
2225 "branchpoint": branchpoint,
2233 "branchpoint": branchpoint,
2226 "bumped": bumped,
2234 "bumped": bumped,
2227 "bundle": bundle,
2235 "bundle": bundle,
2228 "children": children,
2236 "children": children,
2229 "closed": closed,
2237 "closed": closed,
2230 "contains": contains,
2238 "contains": contains,
2231 "converted": converted,
2239 "converted": converted,
2232 "date": date,
2240 "date": date,
2233 "desc": desc,
2241 "desc": desc,
2234 "descendants": descendants,
2242 "descendants": descendants,
2235 "_firstdescendants": _firstdescendants,
2243 "_firstdescendants": _firstdescendants,
2236 "destination": destination,
2244 "destination": destination,
2237 "divergent": divergent,
2245 "divergent": divergent,
2238 "draft": draft,
2246 "draft": draft,
2239 "extinct": extinct,
2247 "extinct": extinct,
2240 "extra": extra,
2248 "extra": extra,
2241 "file": hasfile,
2249 "file": hasfile,
2242 "filelog": filelog,
2250 "filelog": filelog,
2243 "first": first,
2251 "first": first,
2244 "follow": follow,
2252 "follow": follow,
2245 "_followfirst": _followfirst,
2253 "_followfirst": _followfirst,
2246 "grep": grep,
2254 "grep": grep,
2247 "head": head,
2255 "head": head,
2248 "heads": heads,
2256 "heads": heads,
2249 "hidden": hidden,
2257 "hidden": hidden,
2250 "id": node_,
2258 "id": node_,
2251 "keyword": keyword,
2259 "keyword": keyword,
2252 "last": last,
2260 "last": last,
2253 "limit": limit,
2261 "limit": limit,
2254 "_matchfiles": _matchfiles,
2262 "_matchfiles": _matchfiles,
2255 "max": maxrev,
2263 "max": maxrev,
2256 "merge": merge,
2264 "merge": merge,
2257 "min": minrev,
2265 "min": minrev,
2258 "modifies": modifies,
2266 "modifies": modifies,
2259 "named": named,
2267 "named": named,
2260 "obsolete": obsolete,
2268 "obsolete": obsolete,
2261 "only": only,
2269 "only": only,
2262 "origin": origin,
2270 "origin": origin,
2263 "outgoing": outgoing,
2271 "outgoing": outgoing,
2264 "p1": p1,
2272 "p1": p1,
2265 "p2": p2,
2273 "p2": p2,
2266 "parents": parents,
2274 "parents": parents,
2267 "present": present,
2275 "present": present,
2268 "public": public,
2276 "public": public,
2269 "_notpublic": _notpublic,
2277 "_notpublic": _notpublic,
2270 "remote": remote,
2278 "remote": remote,
2271 "removes": removes,
2279 "removes": removes,
2272 "rev": rev,
2280 "rev": rev,
2273 "reverse": reverse,
2281 "reverse": reverse,
2274 "roots": roots,
2282 "roots": roots,
2275 "sort": sort,
2283 "sort": sort,
2276 "secret": secret,
2284 "secret": secret,
2277 "subrepo": subrepo,
2285 "subrepo": subrepo,
2278 "matching": matching,
2286 "matching": matching,
2279 "tag": tag,
2287 "tag": tag,
2280 "tagged": tagged,
2288 "tagged": tagged,
2281 "user": user,
2289 "user": user,
2282 "unstable": unstable,
2290 "unstable": unstable,
2283 "wdir": wdir,
2291 "wdir": wdir,
2284 "_list": _list,
2292 "_list": _list,
2285 "_intlist": _intlist,
2293 "_intlist": _intlist,
2286 "_hexlist": _hexlist,
2294 "_hexlist": _hexlist,
2287 }
2295 }
2288
2296
2289 # 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
2290 # (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)
2291 # 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
2292 safesymbols = set([
2300 safesymbols = set([
2293 "adds",
2301 "adds",
2294 "all",
2302 "all",
2295 "ancestor",
2303 "ancestor",
2296 "ancestors",
2304 "ancestors",
2297 "_firstancestors",
2305 "_firstancestors",
2298 "author",
2306 "author",
2299 "bisect",
2307 "bisect",
2300 "bisected",
2308 "bisected",
2301 "bookmark",
2309 "bookmark",
2302 "branch",
2310 "branch",
2303 "branchpoint",
2311 "branchpoint",
2304 "bumped",
2312 "bumped",
2305 "bundle",
2313 "bundle",
2306 "children",
2314 "children",
2307 "closed",
2315 "closed",
2308 "converted",
2316 "converted",
2309 "date",
2317 "date",
2310 "desc",
2318 "desc",
2311 "descendants",
2319 "descendants",
2312 "_firstdescendants",
2320 "_firstdescendants",
2313 "destination",
2321 "destination",
2314 "divergent",
2322 "divergent",
2315 "draft",
2323 "draft",
2316 "extinct",
2324 "extinct",
2317 "extra",
2325 "extra",
2318 "file",
2326 "file",
2319 "filelog",
2327 "filelog",
2320 "first",
2328 "first",
2321 "follow",
2329 "follow",
2322 "_followfirst",
2330 "_followfirst",
2323 "head",
2331 "head",
2324 "heads",
2332 "heads",
2325 "hidden",
2333 "hidden",
2326 "id",
2334 "id",
2327 "keyword",
2335 "keyword",
2328 "last",
2336 "last",
2329 "limit",
2337 "limit",
2330 "_matchfiles",
2338 "_matchfiles",
2331 "max",
2339 "max",
2332 "merge",
2340 "merge",
2333 "min",
2341 "min",
2334 "modifies",
2342 "modifies",
2335 "obsolete",
2343 "obsolete",
2336 "only",
2344 "only",
2337 "origin",
2345 "origin",
2338 "outgoing",
2346 "outgoing",
2339 "p1",
2347 "p1",
2340 "p2",
2348 "p2",
2341 "parents",
2349 "parents",
2342 "present",
2350 "present",
2343 "public",
2351 "public",
2344 "_notpublic",
2352 "_notpublic",
2345 "remote",
2353 "remote",
2346 "removes",
2354 "removes",
2347 "rev",
2355 "rev",
2348 "reverse",
2356 "reverse",
2349 "roots",
2357 "roots",
2350 "sort",
2358 "sort",
2351 "secret",
2359 "secret",
2352 "matching",
2360 "matching",
2353 "tag",
2361 "tag",
2354 "tagged",
2362 "tagged",
2355 "user",
2363 "user",
2356 "unstable",
2364 "unstable",
2357 "wdir",
2365 "wdir",
2358 "_list",
2366 "_list",
2359 "_intlist",
2367 "_intlist",
2360 "_hexlist",
2368 "_hexlist",
2361 ])
2369 ])
2362
2370
2363 methods = {
2371 methods = {
2364 "range": rangeset,
2372 "range": rangeset,
2365 "dagrange": dagrange,
2373 "dagrange": dagrange,
2366 "string": stringset,
2374 "string": stringset,
2367 "symbol": stringset,
2375 "symbol": stringset,
2368 "and": andset,
2376 "and": andset,
2369 "or": orset,
2377 "or": orset,
2370 "not": notset,
2378 "not": notset,
2371 "list": listset,
2379 "list": listset,
2372 "keyvalue": keyvaluepair,
2380 "keyvalue": keyvaluepair,
2373 "func": func,
2381 "func": func,
2374 "ancestor": ancestorspec,
2382 "ancestor": ancestorspec,
2375 "parent": parentspec,
2383 "parent": parentspec,
2376 "parentpost": p1,
2384 "parentpost": p1,
2377 }
2385 }
2378
2386
2379 def optimize(x, small):
2387 def optimize(x, small):
2380 if x is None:
2388 if x is None:
2381 return 0, x
2389 return 0, x
2382
2390
2383 smallbonus = 1
2391 smallbonus = 1
2384 if small:
2392 if small:
2385 smallbonus = .5
2393 smallbonus = .5
2386
2394
2387 op = x[0]
2395 op = x[0]
2388 if op == 'minus':
2396 if op == 'minus':
2389 return optimize(('and', x[1], ('not', x[2])), small)
2397 return optimize(('and', x[1], ('not', x[2])), small)
2390 elif op == 'only':
2398 elif op == 'only':
2391 return optimize(('func', ('symbol', 'only'),
2399 return optimize(('func', ('symbol', 'only'),
2392 ('list', x[1], x[2])), small)
2400 ('list', x[1], x[2])), small)
2393 elif op == 'onlypost':
2401 elif op == 'onlypost':
2394 return optimize(('func', ('symbol', 'only'), x[1]), small)
2402 return optimize(('func', ('symbol', 'only'), x[1]), small)
2395 elif op == 'dagrangepre':
2403 elif op == 'dagrangepre':
2396 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
2404 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
2397 elif op == 'dagrangepost':
2405 elif op == 'dagrangepost':
2398 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
2406 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
2399 elif op == 'rangeall':
2407 elif op == 'rangeall':
2400 return optimize(('range', ('string', '0'), ('string', 'tip')), small)
2408 return optimize(('range', ('string', '0'), ('string', 'tip')), small)
2401 elif op == 'rangepre':
2409 elif op == 'rangepre':
2402 return optimize(('range', ('string', '0'), x[1]), small)
2410 return optimize(('range', ('string', '0'), x[1]), small)
2403 elif op == 'rangepost':
2411 elif op == 'rangepost':
2404 return optimize(('range', x[1], ('string', 'tip')), small)
2412 return optimize(('range', x[1], ('string', 'tip')), small)
2405 elif op == 'negate':
2413 elif op == 'negate':
2406 return optimize(('string',
2414 return optimize(('string',
2407 '-' + getstring(x[1], _("can't negate that"))), small)
2415 '-' + getstring(x[1], _("can't negate that"))), small)
2408 elif op in 'string symbol negate':
2416 elif op in 'string symbol negate':
2409 return smallbonus, x # single revisions are small
2417 return smallbonus, x # single revisions are small
2410 elif op == 'and':
2418 elif op == 'and':
2411 wa, ta = optimize(x[1], True)
2419 wa, ta = optimize(x[1], True)
2412 wb, tb = optimize(x[2], True)
2420 wb, tb = optimize(x[2], True)
2413
2421
2414 # (::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
2415 def isonly(revs, bases):
2423 def isonly(revs, bases):
2416 return (
2424 return (
2417 revs is not None
2425 revs is not None
2418 and revs[0] == 'func'
2426 and revs[0] == 'func'
2419 and getstring(revs[1], _('not a symbol')) == 'ancestors'
2427 and getstring(revs[1], _('not a symbol')) == 'ancestors'
2420 and bases is not None
2428 and bases is not None
2421 and bases[0] == 'not'
2429 and bases[0] == 'not'
2422 and bases[1][0] == 'func'
2430 and bases[1][0] == 'func'
2423 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
2431 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
2424
2432
2425 w = min(wa, wb)
2433 w = min(wa, wb)
2426 if isonly(ta, tb):
2434 if isonly(ta, tb):
2427 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
2435 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
2428 if isonly(tb, ta):
2436 if isonly(tb, ta):
2429 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
2437 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
2430
2438
2431 if wa > wb:
2439 if wa > wb:
2432 return w, (op, tb, ta)
2440 return w, (op, tb, ta)
2433 return w, (op, ta, tb)
2441 return w, (op, ta, tb)
2434 elif op == 'or':
2442 elif op == 'or':
2435 # fast path for machine-generated expression, that is likely to have
2443 # fast path for machine-generated expression, that is likely to have
2436 # 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()'
2437 ws, ts, ss = [], [], []
2445 ws, ts, ss = [], [], []
2438 def flushss():
2446 def flushss():
2439 if not ss:
2447 if not ss:
2440 return
2448 return
2441 if len(ss) == 1:
2449 if len(ss) == 1:
2442 w, t = ss[0]
2450 w, t = ss[0]
2443 else:
2451 else:
2444 s = '\0'.join(t[1] for w, t in ss)
2452 s = '\0'.join(t[1] for w, t in ss)
2445 y = ('func', ('symbol', '_list'), ('string', s))
2453 y = ('func', ('symbol', '_list'), ('string', s))
2446 w, t = optimize(y, False)
2454 w, t = optimize(y, False)
2447 ws.append(w)
2455 ws.append(w)
2448 ts.append(t)
2456 ts.append(t)
2449 del ss[:]
2457 del ss[:]
2450 for y in x[1:]:
2458 for y in x[1:]:
2451 w, t = optimize(y, False)
2459 w, t = optimize(y, False)
2452 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'):
2453 ss.append((w, t))
2461 ss.append((w, t))
2454 continue
2462 continue
2455 flushss()
2463 flushss()
2456 ws.append(w)
2464 ws.append(w)
2457 ts.append(t)
2465 ts.append(t)
2458 flushss()
2466 flushss()
2459 if len(ts) == 1:
2467 if len(ts) == 1:
2460 return ws[0], ts[0] # 'or' operation is fully optimized out
2468 return ws[0], ts[0] # 'or' operation is fully optimized out
2461 # 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.
2462 # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
2470 # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
2463 # 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]))
2464 return max(ws), (op,) + tuple(ts)
2472 return max(ws), (op,) + tuple(ts)
2465 elif op == 'not':
2473 elif op == 'not':
2466 # Optimize not public() to _notpublic() because we have a fast version
2474 # Optimize not public() to _notpublic() because we have a fast version
2467 if x[1] == ('func', ('symbol', 'public'), None):
2475 if x[1] == ('func', ('symbol', 'public'), None):
2468 newsym = ('func', ('symbol', '_notpublic'), None)
2476 newsym = ('func', ('symbol', '_notpublic'), None)
2469 o = optimize(newsym, not small)
2477 o = optimize(newsym, not small)
2470 return o[0], o[1]
2478 return o[0], o[1]
2471 else:
2479 else:
2472 o = optimize(x[1], not small)
2480 o = optimize(x[1], not small)
2473 return o[0], (op, o[1])
2481 return o[0], (op, o[1])
2474 elif op == 'parentpost':
2482 elif op == 'parentpost':
2475 o = optimize(x[1], small)
2483 o = optimize(x[1], small)
2476 return o[0], (op, o[1])
2484 return o[0], (op, o[1])
2477 elif op == 'group':
2485 elif op == 'group':
2478 return optimize(x[1], small)
2486 return optimize(x[1], small)
2479 elif op in 'dagrange range list parent ancestorspec':
2487 elif op in 'dagrange range list parent ancestorspec':
2480 if op == 'parent':
2488 if op == 'parent':
2481 # x^:y means (x^) : y, not x ^ (:y)
2489 # x^:y means (x^) : y, not x ^ (:y)
2482 post = ('parentpost', x[1])
2490 post = ('parentpost', x[1])
2483 if x[2][0] == 'dagrangepre':
2491 if x[2][0] == 'dagrangepre':
2484 return optimize(('dagrange', post, x[2][1]), small)
2492 return optimize(('dagrange', post, x[2][1]), small)
2485 elif x[2][0] == 'rangepre':
2493 elif x[2][0] == 'rangepre':
2486 return optimize(('range', post, x[2][1]), small)
2494 return optimize(('range', post, x[2][1]), small)
2487
2495
2488 wa, ta = optimize(x[1], small)
2496 wa, ta = optimize(x[1], small)
2489 wb, tb = optimize(x[2], small)
2497 wb, tb = optimize(x[2], small)
2490 return wa + wb, (op, ta, tb)
2498 return wa + wb, (op, ta, tb)
2491 elif op == 'func':
2499 elif op == 'func':
2492 f = getstring(x[1], _("not a symbol"))
2500 f = getstring(x[1], _("not a symbol"))
2493 wa, ta = optimize(x[2], small)
2501 wa, ta = optimize(x[2], small)
2494 if f in ("author branch closed date desc file grep keyword "
2502 if f in ("author branch closed date desc file grep keyword "
2495 "outgoing user"):
2503 "outgoing user"):
2496 w = 10 # slow
2504 w = 10 # slow
2497 elif f in "modifies adds removes":
2505 elif f in "modifies adds removes":
2498 w = 30 # slower
2506 w = 30 # slower
2499 elif f == "contains":
2507 elif f == "contains":
2500 w = 100 # very slow
2508 w = 100 # very slow
2501 elif f == "ancestor":
2509 elif f == "ancestor":
2502 w = 1 * smallbonus
2510 w = 1 * smallbonus
2503 elif f in "reverse limit first _intlist":
2511 elif f in "reverse limit first _intlist":
2504 w = 0
2512 w = 0
2505 elif f in "sort":
2513 elif f in "sort":
2506 w = 10 # assume most sorts look at changelog
2514 w = 10 # assume most sorts look at changelog
2507 else:
2515 else:
2508 w = 1
2516 w = 1
2509 return w + wa, (op, x[1], ta)
2517 return w + wa, (op, x[1], ta)
2510 return 1, x
2518 return 1, x
2511
2519
2512 _aliasarg = ('func', ('symbol', '_aliasarg'))
2520 _aliasarg = ('func', ('symbol', '_aliasarg'))
2513 def _getaliasarg(tree):
2521 def _getaliasarg(tree):
2514 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
2522 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
2515 return X, None otherwise.
2523 return X, None otherwise.
2516 """
2524 """
2517 if (len(tree) == 3 and tree[:2] == _aliasarg
2525 if (len(tree) == 3 and tree[:2] == _aliasarg
2518 and tree[2][0] == 'string'):
2526 and tree[2][0] == 'string'):
2519 return tree[2][1]
2527 return tree[2][1]
2520 return None
2528 return None
2521
2529
2522 def _checkaliasarg(tree, known=None):
2530 def _checkaliasarg(tree, known=None):
2523 """Check tree contains no _aliasarg construct or only ones which
2531 """Check tree contains no _aliasarg construct or only ones which
2524 value is in known. Used to avoid alias placeholders injection.
2532 value is in known. Used to avoid alias placeholders injection.
2525 """
2533 """
2526 if isinstance(tree, tuple):
2534 if isinstance(tree, tuple):
2527 arg = _getaliasarg(tree)
2535 arg = _getaliasarg(tree)
2528 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):
2529 raise error.UnknownIdentifier('_aliasarg', [])
2537 raise error.UnknownIdentifier('_aliasarg', [])
2530 for t in tree:
2538 for t in tree:
2531 _checkaliasarg(t, known)
2539 _checkaliasarg(t, known)
2532
2540
2533 # 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
2534 # alias declarations and definitions
2542 # alias declarations and definitions
2535 _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)]
2536 if c.isalnum() or c in '._@$' or ord(c) > 127)
2544 if c.isalnum() or c in '._@$' or ord(c) > 127)
2537
2545
2538 def _tokenizealias(program, lookup=None):
2546 def _tokenizealias(program, lookup=None):
2539 """Parse alias declaration/definition into a stream of tokens
2547 """Parse alias declaration/definition into a stream of tokens
2540
2548
2541 This allows symbol names to use also ``$`` as an initial letter
2549 This allows symbol names to use also ``$`` as an initial letter
2542 (for backward compatibility), and callers of this function should
2550 (for backward compatibility), and callers of this function should
2543 examine whether ``$`` is used also for unexpected symbols or not.
2551 examine whether ``$`` is used also for unexpected symbols or not.
2544 """
2552 """
2545 return tokenize(program, lookup=lookup,
2553 return tokenize(program, lookup=lookup,
2546 syminitletters=_aliassyminitletters)
2554 syminitletters=_aliassyminitletters)
2547
2555
2548 def _parsealiasdecl(decl):
2556 def _parsealiasdecl(decl):
2549 """Parse alias declaration ``decl``
2557 """Parse alias declaration ``decl``
2550
2558
2551 This returns ``(name, tree, args, errorstr)`` tuple:
2559 This returns ``(name, tree, args, errorstr)`` tuple:
2552
2560
2553 - ``name``: of declared alias (may be ``decl`` itself at error)
2561 - ``name``: of declared alias (may be ``decl`` itself at error)
2554 - ``tree``: parse result (or ``None`` at error)
2562 - ``tree``: parse result (or ``None`` at error)
2555 - ``args``: list of alias argument names (or None for symbol declaration)
2563 - ``args``: list of alias argument names (or None for symbol declaration)
2556 - ``errorstr``: detail about detected error (or None)
2564 - ``errorstr``: detail about detected error (or None)
2557
2565
2558 >>> _parsealiasdecl('foo')
2566 >>> _parsealiasdecl('foo')
2559 ('foo', ('symbol', 'foo'), None, None)
2567 ('foo', ('symbol', 'foo'), None, None)
2560 >>> _parsealiasdecl('$foo')
2568 >>> _parsealiasdecl('$foo')
2561 ('$foo', None, None, "'$' not for alias arguments")
2569 ('$foo', None, None, "'$' not for alias arguments")
2562 >>> _parsealiasdecl('foo::bar')
2570 >>> _parsealiasdecl('foo::bar')
2563 ('foo::bar', None, None, 'invalid format')
2571 ('foo::bar', None, None, 'invalid format')
2564 >>> _parsealiasdecl('foo bar')
2572 >>> _parsealiasdecl('foo bar')
2565 ('foo bar', None, None, 'at 4: invalid token')
2573 ('foo bar', None, None, 'at 4: invalid token')
2566 >>> _parsealiasdecl('foo()')
2574 >>> _parsealiasdecl('foo()')
2567 ('foo', ('func', ('symbol', 'foo')), [], None)
2575 ('foo', ('func', ('symbol', 'foo')), [], None)
2568 >>> _parsealiasdecl('$foo()')
2576 >>> _parsealiasdecl('$foo()')
2569 ('$foo()', None, None, "'$' not for alias arguments")
2577 ('$foo()', None, None, "'$' not for alias arguments")
2570 >>> _parsealiasdecl('foo($1, $2)')
2578 >>> _parsealiasdecl('foo($1, $2)')
2571 ('foo', ('func', ('symbol', 'foo')), ['$1', '$2'], None)
2579 ('foo', ('func', ('symbol', 'foo')), ['$1', '$2'], None)
2572 >>> _parsealiasdecl('foo(bar_bar, baz.baz)')
2580 >>> _parsealiasdecl('foo(bar_bar, baz.baz)')
2573 ('foo', ('func', ('symbol', 'foo')), ['bar_bar', 'baz.baz'], None)
2581 ('foo', ('func', ('symbol', 'foo')), ['bar_bar', 'baz.baz'], None)
2574 >>> _parsealiasdecl('foo($1, $2, nested($1, $2))')
2582 >>> _parsealiasdecl('foo($1, $2, nested($1, $2))')
2575 ('foo($1, $2, nested($1, $2))', None, None, 'invalid argument list')
2583 ('foo($1, $2, nested($1, $2))', None, None, 'invalid argument list')
2576 >>> _parsealiasdecl('foo(bar($1, $2))')
2584 >>> _parsealiasdecl('foo(bar($1, $2))')
2577 ('foo(bar($1, $2))', None, None, 'invalid argument list')
2585 ('foo(bar($1, $2))', None, None, 'invalid argument list')
2578 >>> _parsealiasdecl('foo("string")')
2586 >>> _parsealiasdecl('foo("string")')
2579 ('foo("string")', None, None, 'invalid argument list')
2587 ('foo("string")', None, None, 'invalid argument list')
2580 >>> _parsealiasdecl('foo($1, $2')
2588 >>> _parsealiasdecl('foo($1, $2')
2581 ('foo($1, $2', None, None, 'at 10: unexpected token: end')
2589 ('foo($1, $2', None, None, 'at 10: unexpected token: end')
2582 >>> _parsealiasdecl('foo("string')
2590 >>> _parsealiasdecl('foo("string')
2583 ('foo("string', None, None, 'at 5: unterminated string')
2591 ('foo("string', None, None, 'at 5: unterminated string')
2584 >>> _parsealiasdecl('foo($1, $2, $1)')
2592 >>> _parsealiasdecl('foo($1, $2, $1)')
2585 ('foo', None, None, 'argument names collide with each other')
2593 ('foo', None, None, 'argument names collide with each other')
2586 """
2594 """
2587 p = parser.parser(elements)
2595 p = parser.parser(elements)
2588 try:
2596 try:
2589 tree, pos = p.parse(_tokenizealias(decl))
2597 tree, pos = p.parse(_tokenizealias(decl))
2590 if (pos != len(decl)):
2598 if (pos != len(decl)):
2591 raise error.ParseError(_('invalid token'), pos)
2599 raise error.ParseError(_('invalid token'), pos)
2592
2600
2593 if isvalidsymbol(tree):
2601 if isvalidsymbol(tree):
2594 # "name = ...." style
2602 # "name = ...." style
2595 name = getsymbol(tree)
2603 name = getsymbol(tree)
2596 if name.startswith('$'):
2604 if name.startswith('$'):
2597 return (decl, None, None, _("'$' not for alias arguments"))
2605 return (decl, None, None, _("'$' not for alias arguments"))
2598 return (name, ('symbol', name), None, None)
2606 return (name, ('symbol', name), None, None)
2599
2607
2600 if isvalidfunc(tree):
2608 if isvalidfunc(tree):
2601 # "name(arg, ....) = ...." style
2609 # "name(arg, ....) = ...." style
2602 name = getfuncname(tree)
2610 name = getfuncname(tree)
2603 if name.startswith('$'):
2611 if name.startswith('$'):
2604 return (decl, None, None, _("'$' not for alias arguments"))
2612 return (decl, None, None, _("'$' not for alias arguments"))
2605 args = []
2613 args = []
2606 for arg in getfuncargs(tree):
2614 for arg in getfuncargs(tree):
2607 if not isvalidsymbol(arg):
2615 if not isvalidsymbol(arg):
2608 return (decl, None, None, _("invalid argument list"))
2616 return (decl, None, None, _("invalid argument list"))
2609 args.append(getsymbol(arg))
2617 args.append(getsymbol(arg))
2610 if len(args) != len(set(args)):
2618 if len(args) != len(set(args)):
2611 return (name, None, None,
2619 return (name, None, None,
2612 _("argument names collide with each other"))
2620 _("argument names collide with each other"))
2613 return (name, ('func', ('symbol', name)), args, None)
2621 return (name, ('func', ('symbol', name)), args, None)
2614
2622
2615 return (decl, None, None, _("invalid format"))
2623 return (decl, None, None, _("invalid format"))
2616 except error.ParseError as inst:
2624 except error.ParseError as inst:
2617 return (decl, None, None, parseerrordetail(inst))
2625 return (decl, None, None, parseerrordetail(inst))
2618
2626
2619 def _parsealiasdefn(defn, args):
2627 def _parsealiasdefn(defn, args):
2620 """Parse alias definition ``defn``
2628 """Parse alias definition ``defn``
2621
2629
2622 This function also replaces alias argument references in the
2630 This function also replaces alias argument references in the
2623 specified definition by ``_aliasarg(ARGNAME)``.
2631 specified definition by ``_aliasarg(ARGNAME)``.
2624
2632
2625 ``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
2626 is declared as a symbol.
2634 is declared as a symbol.
2627
2635
2628 This returns "tree" as parsing result.
2636 This returns "tree" as parsing result.
2629
2637
2630 >>> args = ['$1', '$2', 'foo']
2638 >>> args = ['$1', '$2', 'foo']
2631 >>> print prettyformat(_parsealiasdefn('$1 or foo', args))
2639 >>> print prettyformat(_parsealiasdefn('$1 or foo', args))
2632 (or
2640 (or
2633 (func
2641 (func
2634 ('symbol', '_aliasarg')
2642 ('symbol', '_aliasarg')
2635 ('string', '$1'))
2643 ('string', '$1'))
2636 (func
2644 (func
2637 ('symbol', '_aliasarg')
2645 ('symbol', '_aliasarg')
2638 ('string', 'foo')))
2646 ('string', 'foo')))
2639 >>> try:
2647 >>> try:
2640 ... _parsealiasdefn('$1 or $bar', args)
2648 ... _parsealiasdefn('$1 or $bar', args)
2641 ... except error.ParseError, inst:
2649 ... except error.ParseError, inst:
2642 ... print parseerrordetail(inst)
2650 ... print parseerrordetail(inst)
2643 at 6: '$' not for alias arguments
2651 at 6: '$' not for alias arguments
2644 >>> args = ['$1', '$10', 'foo']
2652 >>> args = ['$1', '$10', 'foo']
2645 >>> print prettyformat(_parsealiasdefn('$10 or foobar', args))
2653 >>> print prettyformat(_parsealiasdefn('$10 or foobar', args))
2646 (or
2654 (or
2647 (func
2655 (func
2648 ('symbol', '_aliasarg')
2656 ('symbol', '_aliasarg')
2649 ('string', '$10'))
2657 ('string', '$10'))
2650 ('symbol', 'foobar'))
2658 ('symbol', 'foobar'))
2651 >>> print prettyformat(_parsealiasdefn('"$1" or "foo"', args))
2659 >>> print prettyformat(_parsealiasdefn('"$1" or "foo"', args))
2652 (or
2660 (or
2653 ('string', '$1')
2661 ('string', '$1')
2654 ('string', 'foo'))
2662 ('string', 'foo'))
2655 """
2663 """
2656 def tokenizedefn(program, lookup=None):
2664 def tokenizedefn(program, lookup=None):
2657 if args:
2665 if args:
2658 argset = set(args)
2666 argset = set(args)
2659 else:
2667 else:
2660 argset = set()
2668 argset = set()
2661
2669
2662 for t, value, pos in _tokenizealias(program, lookup=lookup):
2670 for t, value, pos in _tokenizealias(program, lookup=lookup):
2663 if t == 'symbol':
2671 if t == 'symbol':
2664 if value in argset:
2672 if value in argset:
2665 # emulate tokenization of "_aliasarg('ARGNAME')":
2673 # emulate tokenization of "_aliasarg('ARGNAME')":
2666 # "_aliasarg()" is an unknown symbol only used separate
2674 # "_aliasarg()" is an unknown symbol only used separate
2667 # alias argument placeholders from regular strings.
2675 # alias argument placeholders from regular strings.
2668 yield ('symbol', '_aliasarg', pos)
2676 yield ('symbol', '_aliasarg', pos)
2669 yield ('(', None, pos)
2677 yield ('(', None, pos)
2670 yield ('string', value, pos)
2678 yield ('string', value, pos)
2671 yield (')', None, pos)
2679 yield (')', None, pos)
2672 continue
2680 continue
2673 elif value.startswith('$'):
2681 elif value.startswith('$'):
2674 raise error.ParseError(_("'$' not for alias arguments"),
2682 raise error.ParseError(_("'$' not for alias arguments"),
2675 pos)
2683 pos)
2676 yield (t, value, pos)
2684 yield (t, value, pos)
2677
2685
2678 p = parser.parser(elements)
2686 p = parser.parser(elements)
2679 tree, pos = p.parse(tokenizedefn(defn))
2687 tree, pos = p.parse(tokenizedefn(defn))
2680 if pos != len(defn):
2688 if pos != len(defn):
2681 raise error.ParseError(_('invalid token'), pos)
2689 raise error.ParseError(_('invalid token'), pos)
2682 return parser.simplifyinfixops(tree, ('or',))
2690 return parser.simplifyinfixops(tree, ('or',))
2683
2691
2684 class revsetalias(object):
2692 class revsetalias(object):
2685 # whether own `error` information is already shown or not.
2693 # whether own `error` information is already shown or not.
2686 # this avoids showing same warning multiple times at each `findaliases`.
2694 # this avoids showing same warning multiple times at each `findaliases`.
2687 warned = False
2695 warned = False
2688
2696
2689 def __init__(self, name, value):
2697 def __init__(self, name, value):
2690 '''Aliases like:
2698 '''Aliases like:
2691
2699
2692 h = heads(default)
2700 h = heads(default)
2693 b($1) = ancestors($1) - ancestors(default)
2701 b($1) = ancestors($1) - ancestors(default)
2694 '''
2702 '''
2695 self.name, self.tree, self.args, self.error = _parsealiasdecl(name)
2703 self.name, self.tree, self.args, self.error = _parsealiasdecl(name)
2696 if self.error:
2704 if self.error:
2697 self.error = _('failed to parse the declaration of revset alias'
2705 self.error = _('failed to parse the declaration of revset alias'
2698 ' "%s": %s') % (self.name, self.error)
2706 ' "%s": %s') % (self.name, self.error)
2699 return
2707 return
2700
2708
2701 try:
2709 try:
2702 self.replacement = _parsealiasdefn(value, self.args)
2710 self.replacement = _parsealiasdefn(value, self.args)
2703 # Check for placeholder injection
2711 # Check for placeholder injection
2704 _checkaliasarg(self.replacement, self.args)
2712 _checkaliasarg(self.replacement, self.args)
2705 except error.ParseError as inst:
2713 except error.ParseError as inst:
2706 self.error = _('failed to parse the definition of revset alias'
2714 self.error = _('failed to parse the definition of revset alias'
2707 ' "%s": %s') % (self.name, parseerrordetail(inst))
2715 ' "%s": %s') % (self.name, parseerrordetail(inst))
2708
2716
2709 def _getalias(aliases, tree):
2717 def _getalias(aliases, tree):
2710 """If tree looks like an unexpanded alias, return it. Return None
2718 """If tree looks like an unexpanded alias, return it. Return None
2711 otherwise.
2719 otherwise.
2712 """
2720 """
2713 if isinstance(tree, tuple) and tree:
2721 if isinstance(tree, tuple) and tree:
2714 if tree[0] == 'symbol' and len(tree) == 2:
2722 if tree[0] == 'symbol' and len(tree) == 2:
2715 name = tree[1]
2723 name = tree[1]
2716 alias = aliases.get(name)
2724 alias = aliases.get(name)
2717 if alias and alias.args is None and alias.tree == tree:
2725 if alias and alias.args is None and alias.tree == tree:
2718 return alias
2726 return alias
2719 if tree[0] == 'func' and len(tree) > 1:
2727 if tree[0] == 'func' and len(tree) > 1:
2720 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
2728 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
2721 name = tree[1][1]
2729 name = tree[1][1]
2722 alias = aliases.get(name)
2730 alias = aliases.get(name)
2723 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]:
2724 return alias
2732 return alias
2725 return None
2733 return None
2726
2734
2727 def _expandargs(tree, args):
2735 def _expandargs(tree, args):
2728 """Replace _aliasarg instances with the substitution value of the
2736 """Replace _aliasarg instances with the substitution value of the
2729 same name in args, recursively.
2737 same name in args, recursively.
2730 """
2738 """
2731 if not tree or not isinstance(tree, tuple):
2739 if not tree or not isinstance(tree, tuple):
2732 return tree
2740 return tree
2733 arg = _getaliasarg(tree)
2741 arg = _getaliasarg(tree)
2734 if arg is not None:
2742 if arg is not None:
2735 return args[arg]
2743 return args[arg]
2736 return tuple(_expandargs(t, args) for t in tree)
2744 return tuple(_expandargs(t, args) for t in tree)
2737
2745
2738 def _expandaliases(aliases, tree, expanding, cache):
2746 def _expandaliases(aliases, tree, expanding, cache):
2739 """Expand aliases in tree, recursively.
2747 """Expand aliases in tree, recursively.
2740
2748
2741 'aliases' is a dictionary mapping user defined aliases to
2749 'aliases' is a dictionary mapping user defined aliases to
2742 revsetalias objects.
2750 revsetalias objects.
2743 """
2751 """
2744 if not isinstance(tree, tuple):
2752 if not isinstance(tree, tuple):
2745 # Do not expand raw strings
2753 # Do not expand raw strings
2746 return tree
2754 return tree
2747 alias = _getalias(aliases, tree)
2755 alias = _getalias(aliases, tree)
2748 if alias is not None:
2756 if alias is not None:
2749 if alias.error:
2757 if alias.error:
2750 raise util.Abort(alias.error)
2758 raise util.Abort(alias.error)
2751 if alias in expanding:
2759 if alias in expanding:
2752 raise error.ParseError(_('infinite expansion of revset alias "%s" '
2760 raise error.ParseError(_('infinite expansion of revset alias "%s" '
2753 'detected') % alias.name)
2761 'detected') % alias.name)
2754 expanding.append(alias)
2762 expanding.append(alias)
2755 if alias.name not in cache:
2763 if alias.name not in cache:
2756 cache[alias.name] = _expandaliases(aliases, alias.replacement,
2764 cache[alias.name] = _expandaliases(aliases, alias.replacement,
2757 expanding, cache)
2765 expanding, cache)
2758 result = cache[alias.name]
2766 result = cache[alias.name]
2759 expanding.pop()
2767 expanding.pop()
2760 if alias.args is not None:
2768 if alias.args is not None:
2761 l = getlist(tree[2])
2769 l = getlist(tree[2])
2762 if len(l) != len(alias.args):
2770 if len(l) != len(alias.args):
2763 raise error.ParseError(
2771 raise error.ParseError(
2764 _('invalid number of arguments: %s') % len(l))
2772 _('invalid number of arguments: %s') % len(l))
2765 l = [_expandaliases(aliases, a, [], cache) for a in l]
2773 l = [_expandaliases(aliases, a, [], cache) for a in l]
2766 result = _expandargs(result, dict(zip(alias.args, l)))
2774 result = _expandargs(result, dict(zip(alias.args, l)))
2767 else:
2775 else:
2768 result = tuple(_expandaliases(aliases, t, expanding, cache)
2776 result = tuple(_expandaliases(aliases, t, expanding, cache)
2769 for t in tree)
2777 for t in tree)
2770 return result
2778 return result
2771
2779
2772 def findaliases(ui, tree, showwarning=None):
2780 def findaliases(ui, tree, showwarning=None):
2773 _checkaliasarg(tree)
2781 _checkaliasarg(tree)
2774 aliases = {}
2782 aliases = {}
2775 for k, v in ui.configitems('revsetalias'):
2783 for k, v in ui.configitems('revsetalias'):
2776 alias = revsetalias(k, v)
2784 alias = revsetalias(k, v)
2777 aliases[alias.name] = alias
2785 aliases[alias.name] = alias
2778 tree = _expandaliases(aliases, tree, [], {})
2786 tree = _expandaliases(aliases, tree, [], {})
2779 if showwarning:
2787 if showwarning:
2780 # warn about problematic (but not referred) aliases
2788 # warn about problematic (but not referred) aliases
2781 for name, alias in sorted(aliases.iteritems()):
2789 for name, alias in sorted(aliases.iteritems()):
2782 if alias.error and not alias.warned:
2790 if alias.error and not alias.warned:
2783 showwarning(_('warning: %s\n') % (alias.error))
2791 showwarning(_('warning: %s\n') % (alias.error))
2784 alias.warned = True
2792 alias.warned = True
2785 return tree
2793 return tree
2786
2794
2787 def foldconcat(tree):
2795 def foldconcat(tree):
2788 """Fold elements to be concatenated by `##`
2796 """Fold elements to be concatenated by `##`
2789 """
2797 """
2790 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2798 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2791 return tree
2799 return tree
2792 if tree[0] == '_concat':
2800 if tree[0] == '_concat':
2793 pending = [tree]
2801 pending = [tree]
2794 l = []
2802 l = []
2795 while pending:
2803 while pending:
2796 e = pending.pop()
2804 e = pending.pop()
2797 if e[0] == '_concat':
2805 if e[0] == '_concat':
2798 pending.extend(reversed(e[1:]))
2806 pending.extend(reversed(e[1:]))
2799 elif e[0] in ('string', 'symbol'):
2807 elif e[0] in ('string', 'symbol'):
2800 l.append(e[1])
2808 l.append(e[1])
2801 else:
2809 else:
2802 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2810 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2803 raise error.ParseError(msg)
2811 raise error.ParseError(msg)
2804 return ('string', ''.join(l))
2812 return ('string', ''.join(l))
2805 else:
2813 else:
2806 return tuple(foldconcat(t) for t in tree)
2814 return tuple(foldconcat(t) for t in tree)
2807
2815
2808 def parse(spec, lookup=None):
2816 def parse(spec, lookup=None):
2809 p = parser.parser(elements)
2817 p = parser.parser(elements)
2810 tree, pos = p.parse(tokenize(spec, lookup=lookup))
2818 tree, pos = p.parse(tokenize(spec, lookup=lookup))
2811 if pos != len(spec):
2819 if pos != len(spec):
2812 raise error.ParseError(_("invalid token"), pos)
2820 raise error.ParseError(_("invalid token"), pos)
2813 return parser.simplifyinfixops(tree, ('or',))
2821 return parser.simplifyinfixops(tree, ('or',))
2814
2822
2815 def posttreebuilthook(tree, repo):
2823 def posttreebuilthook(tree, repo):
2816 # hook for extensions to execute code on the optimized tree
2824 # hook for extensions to execute code on the optimized tree
2817 pass
2825 pass
2818
2826
2819 def match(ui, spec, repo=None):
2827 def match(ui, spec, repo=None):
2820 if not spec:
2828 if not spec:
2821 raise error.ParseError(_("empty query"))
2829 raise error.ParseError(_("empty query"))
2822 lookup = None
2830 lookup = None
2823 if repo:
2831 if repo:
2824 lookup = repo.__contains__
2832 lookup = repo.__contains__
2825 tree = parse(spec, lookup)
2833 tree = parse(spec, lookup)
2826 return _makematcher(ui, tree, repo)
2834 return _makematcher(ui, tree, repo)
2827
2835
2828 def matchany(ui, specs, repo=None):
2836 def matchany(ui, specs, repo=None):
2829 """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
2830 given specs"""
2838 given specs"""
2831 if not specs:
2839 if not specs:
2832 def mfunc(repo, subset=None):
2840 def mfunc(repo, subset=None):
2833 return baseset()
2841 return baseset()
2834 return mfunc
2842 return mfunc
2835 if not all(specs):
2843 if not all(specs):
2836 raise error.ParseError(_("empty query"))
2844 raise error.ParseError(_("empty query"))
2837 lookup = None
2845 lookup = None
2838 if repo:
2846 if repo:
2839 lookup = repo.__contains__
2847 lookup = repo.__contains__
2840 if len(specs) == 1:
2848 if len(specs) == 1:
2841 tree = parse(specs[0], lookup)
2849 tree = parse(specs[0], lookup)
2842 else:
2850 else:
2843 tree = ('or',) + tuple(parse(s, lookup) for s in specs)
2851 tree = ('or',) + tuple(parse(s, lookup) for s in specs)
2844 return _makematcher(ui, tree, repo)
2852 return _makematcher(ui, tree, repo)
2845
2853
2846 def _makematcher(ui, tree, repo):
2854 def _makematcher(ui, tree, repo):
2847 if ui:
2855 if ui:
2848 tree = findaliases(ui, tree, showwarning=ui.warn)
2856 tree = findaliases(ui, tree, showwarning=ui.warn)
2849 tree = foldconcat(tree)
2857 tree = foldconcat(tree)
2850 weight, tree = optimize(tree, True)
2858 weight, tree = optimize(tree, True)
2851 posttreebuilthook(tree, repo)
2859 posttreebuilthook(tree, repo)
2852 def mfunc(repo, subset=None):
2860 def mfunc(repo, subset=None):
2853 if subset is None:
2861 if subset is None:
2854 subset = fullreposet(repo)
2862 subset = fullreposet(repo)
2855 if util.safehasattr(subset, 'isascending'):
2863 if util.safehasattr(subset, 'isascending'):
2856 result = getset(repo, subset, tree)
2864 result = getset(repo, subset, tree)
2857 else:
2865 else:
2858 result = getset(repo, baseset(subset), tree)
2866 result = getset(repo, baseset(subset), tree)
2859 return result
2867 return result
2860 return mfunc
2868 return mfunc
2861
2869
2862 def formatspec(expr, *args):
2870 def formatspec(expr, *args):
2863 '''
2871 '''
2864 This is a convenience function for using revsets internally, and
2872 This is a convenience function for using revsets internally, and
2865 escapes arguments appropriately. Aliases are intentionally ignored
2873 escapes arguments appropriately. Aliases are intentionally ignored
2866 so that intended expression behavior isn't accidentally subverted.
2874 so that intended expression behavior isn't accidentally subverted.
2867
2875
2868 Supported arguments:
2876 Supported arguments:
2869
2877
2870 %r = revset expression, parenthesized
2878 %r = revset expression, parenthesized
2871 %d = int(arg), no quoting
2879 %d = int(arg), no quoting
2872 %s = string(arg), escaped and single-quoted
2880 %s = string(arg), escaped and single-quoted
2873 %b = arg.branch(), escaped and single-quoted
2881 %b = arg.branch(), escaped and single-quoted
2874 %n = hex(arg), single-quoted
2882 %n = hex(arg), single-quoted
2875 %% = a literal '%'
2883 %% = a literal '%'
2876
2884
2877 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.
2878
2886
2879 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2887 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2880 '(10 or 11):: and ((this()) or (that()))'
2888 '(10 or 11):: and ((this()) or (that()))'
2881 >>> formatspec('%d:: and not %d::', 10, 20)
2889 >>> formatspec('%d:: and not %d::', 10, 20)
2882 '10:: and not 20::'
2890 '10:: and not 20::'
2883 >>> formatspec('%ld or %ld', [], [1])
2891 >>> formatspec('%ld or %ld', [], [1])
2884 "_list('') or 1"
2892 "_list('') or 1"
2885 >>> formatspec('keyword(%s)', 'foo\\xe9')
2893 >>> formatspec('keyword(%s)', 'foo\\xe9')
2886 "keyword('foo\\\\xe9')"
2894 "keyword('foo\\\\xe9')"
2887 >>> b = lambda: 'default'
2895 >>> b = lambda: 'default'
2888 >>> b.branch = b
2896 >>> b.branch = b
2889 >>> formatspec('branch(%b)', b)
2897 >>> formatspec('branch(%b)', b)
2890 "branch('default')"
2898 "branch('default')"
2891 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2899 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2892 "root(_list('a\\x00b\\x00c\\x00d'))"
2900 "root(_list('a\\x00b\\x00c\\x00d'))"
2893 '''
2901 '''
2894
2902
2895 def quote(s):
2903 def quote(s):
2896 return repr(str(s))
2904 return repr(str(s))
2897
2905
2898 def argtype(c, arg):
2906 def argtype(c, arg):
2899 if c == 'd':
2907 if c == 'd':
2900 return str(int(arg))
2908 return str(int(arg))
2901 elif c == 's':
2909 elif c == 's':
2902 return quote(arg)
2910 return quote(arg)
2903 elif c == 'r':
2911 elif c == 'r':
2904 parse(arg) # make sure syntax errors are confined
2912 parse(arg) # make sure syntax errors are confined
2905 return '(%s)' % arg
2913 return '(%s)' % arg
2906 elif c == 'n':
2914 elif c == 'n':
2907 return quote(node.hex(arg))
2915 return quote(node.hex(arg))
2908 elif c == 'b':
2916 elif c == 'b':
2909 return quote(arg.branch())
2917 return quote(arg.branch())
2910
2918
2911 def listexp(s, t):
2919 def listexp(s, t):
2912 l = len(s)
2920 l = len(s)
2913 if l == 0:
2921 if l == 0:
2914 return "_list('')"
2922 return "_list('')"
2915 elif l == 1:
2923 elif l == 1:
2916 return argtype(t, s[0])
2924 return argtype(t, s[0])
2917 elif t == 'd':
2925 elif t == 'd':
2918 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)
2919 elif t == 's':
2927 elif t == 's':
2920 return "_list('%s')" % "\0".join(s)
2928 return "_list('%s')" % "\0".join(s)
2921 elif t == 'n':
2929 elif t == 'n':
2922 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)
2923 elif t == 'b':
2931 elif t == 'b':
2924 return "_list('%s')" % "\0".join(a.branch() for a in s)
2932 return "_list('%s')" % "\0".join(a.branch() for a in s)
2925
2933
2926 m = l // 2
2934 m = l // 2
2927 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))
2928
2936
2929 ret = ''
2937 ret = ''
2930 pos = 0
2938 pos = 0
2931 arg = 0
2939 arg = 0
2932 while pos < len(expr):
2940 while pos < len(expr):
2933 c = expr[pos]
2941 c = expr[pos]
2934 if c == '%':
2942 if c == '%':
2935 pos += 1
2943 pos += 1
2936 d = expr[pos]
2944 d = expr[pos]
2937 if d == '%':
2945 if d == '%':
2938 ret += d
2946 ret += d
2939 elif d in 'dsnbr':
2947 elif d in 'dsnbr':
2940 ret += argtype(d, args[arg])
2948 ret += argtype(d, args[arg])
2941 arg += 1
2949 arg += 1
2942 elif d == 'l':
2950 elif d == 'l':
2943 # a list of some type
2951 # a list of some type
2944 pos += 1
2952 pos += 1
2945 d = expr[pos]
2953 d = expr[pos]
2946 ret += listexp(list(args[arg]), d)
2954 ret += listexp(list(args[arg]), d)
2947 arg += 1
2955 arg += 1
2948 else:
2956 else:
2949 raise util.Abort('unexpected revspec format character %s' % d)
2957 raise util.Abort('unexpected revspec format character %s' % d)
2950 else:
2958 else:
2951 ret += c
2959 ret += c
2952 pos += 1
2960 pos += 1
2953
2961
2954 return ret
2962 return ret
2955
2963
2956 def prettyformat(tree):
2964 def prettyformat(tree):
2957 return parser.prettyformat(tree, ('string', 'symbol'))
2965 return parser.prettyformat(tree, ('string', 'symbol'))
2958
2966
2959 def depth(tree):
2967 def depth(tree):
2960 if isinstance(tree, tuple):
2968 if isinstance(tree, tuple):
2961 return max(map(depth, tree)) + 1
2969 return max(map(depth, tree)) + 1
2962 else:
2970 else:
2963 return 0
2971 return 0
2964
2972
2965 def funcsused(tree):
2973 def funcsused(tree):
2966 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2974 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2967 return set()
2975 return set()
2968 else:
2976 else:
2969 funcs = set()
2977 funcs = set()
2970 for s in tree[1:]:
2978 for s in tree[1:]:
2971 funcs |= funcsused(s)
2979 funcs |= funcsused(s)
2972 if tree[0] == 'func':
2980 if tree[0] == 'func':
2973 funcs.add(tree[1][1])
2981 funcs.add(tree[1][1])
2974 return funcs
2982 return funcs
2975
2983
2976 class abstractsmartset(object):
2984 class abstractsmartset(object):
2977
2985
2978 def __nonzero__(self):
2986 def __nonzero__(self):
2979 """True if the smartset is not empty"""
2987 """True if the smartset is not empty"""
2980 raise NotImplementedError()
2988 raise NotImplementedError()
2981
2989
2982 def __contains__(self, rev):
2990 def __contains__(self, rev):
2983 """provide fast membership testing"""
2991 """provide fast membership testing"""
2984 raise NotImplementedError()
2992 raise NotImplementedError()
2985
2993
2986 def __iter__(self):
2994 def __iter__(self):
2987 """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"""
2988 raise NotImplementedError()
2996 raise NotImplementedError()
2989
2997
2990 # 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
2991 # direction. A smartset can have none, one, or both defined.
2999 # direction. A smartset can have none, one, or both defined.
2992 #
3000 #
2993 # 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
2994 # initializing an iterator just for testing if a fast method exists.
3002 # initializing an iterator just for testing if a fast method exists.
2995 fastasc = None
3003 fastasc = None
2996 fastdesc = None
3004 fastdesc = None
2997
3005
2998 def isascending(self):
3006 def isascending(self):
2999 """True if the set will iterate in ascending order"""
3007 """True if the set will iterate in ascending order"""
3000 raise NotImplementedError()
3008 raise NotImplementedError()
3001
3009
3002 def isdescending(self):
3010 def isdescending(self):
3003 """True if the set will iterate in descending order"""
3011 """True if the set will iterate in descending order"""
3004 raise NotImplementedError()
3012 raise NotImplementedError()
3005
3013
3006 @util.cachefunc
3014 @util.cachefunc
3007 def min(self):
3015 def min(self):
3008 """return the minimum element in the set"""
3016 """return the minimum element in the set"""
3009 if self.fastasc is not None:
3017 if self.fastasc is not None:
3010 for r in self.fastasc():
3018 for r in self.fastasc():
3011 return r
3019 return r
3012 raise ValueError('arg is an empty sequence')
3020 raise ValueError('arg is an empty sequence')
3013 return min(self)
3021 return min(self)
3014
3022
3015 @util.cachefunc
3023 @util.cachefunc
3016 def max(self):
3024 def max(self):
3017 """return the maximum element in the set"""
3025 """return the maximum element in the set"""
3018 if self.fastdesc is not None:
3026 if self.fastdesc is not None:
3019 for r in self.fastdesc():
3027 for r in self.fastdesc():
3020 return r
3028 return r
3021 raise ValueError('arg is an empty sequence')
3029 raise ValueError('arg is an empty sequence')
3022 return max(self)
3030 return max(self)
3023
3031
3024 def first(self):
3032 def first(self):
3025 """return the first element in the set (user iteration perspective)
3033 """return the first element in the set (user iteration perspective)
3026
3034
3027 Return None if the set is empty"""
3035 Return None if the set is empty"""
3028 raise NotImplementedError()
3036 raise NotImplementedError()
3029
3037
3030 def last(self):
3038 def last(self):
3031 """return the last element in the set (user iteration perspective)
3039 """return the last element in the set (user iteration perspective)
3032
3040
3033 Return None if the set is empty"""
3041 Return None if the set is empty"""
3034 raise NotImplementedError()
3042 raise NotImplementedError()
3035
3043
3036 def __len__(self):
3044 def __len__(self):
3037 """return the length of the smartsets
3045 """return the length of the smartsets
3038
3046
3039 This can be expensive on smartset that could be lazy otherwise."""
3047 This can be expensive on smartset that could be lazy otherwise."""
3040 raise NotImplementedError()
3048 raise NotImplementedError()
3041
3049
3042 def reverse(self):
3050 def reverse(self):
3043 """reverse the expected iteration order"""
3051 """reverse the expected iteration order"""
3044 raise NotImplementedError()
3052 raise NotImplementedError()
3045
3053
3046 def sort(self, reverse=True):
3054 def sort(self, reverse=True):
3047 """get the set to iterate in an ascending or descending order"""
3055 """get the set to iterate in an ascending or descending order"""
3048 raise NotImplementedError()
3056 raise NotImplementedError()
3049
3057
3050 def __and__(self, other):
3058 def __and__(self, other):
3051 """Returns a new object with the intersection of the two collections.
3059 """Returns a new object with the intersection of the two collections.
3052
3060
3053 This is part of the mandatory API for smartset."""
3061 This is part of the mandatory API for smartset."""
3054 if isinstance(other, fullreposet):
3062 if isinstance(other, fullreposet):
3055 return self
3063 return self
3056 return self.filter(other.__contains__, cache=False)
3064 return self.filter(other.__contains__, cache=False)
3057
3065
3058 def __add__(self, other):
3066 def __add__(self, other):
3059 """Returns a new object with the union of the two collections.
3067 """Returns a new object with the union of the two collections.
3060
3068
3061 This is part of the mandatory API for smartset."""
3069 This is part of the mandatory API for smartset."""
3062 return addset(self, other)
3070 return addset(self, other)
3063
3071
3064 def __sub__(self, other):
3072 def __sub__(self, other):
3065 """Returns a new object with the substraction of the two collections.
3073 """Returns a new object with the substraction of the two collections.
3066
3074
3067 This is part of the mandatory API for smartset."""
3075 This is part of the mandatory API for smartset."""
3068 c = other.__contains__
3076 c = other.__contains__
3069 return self.filter(lambda r: not c(r), cache=False)
3077 return self.filter(lambda r: not c(r), cache=False)
3070
3078
3071 def filter(self, condition, cache=True):
3079 def filter(self, condition, cache=True):
3072 """Returns this smartset filtered by condition as a new smartset.
3080 """Returns this smartset filtered by condition as a new smartset.
3073
3081
3074 `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
3075 boolean.
3083 boolean.
3076
3084
3077 This is part of the mandatory API for smartset."""
3085 This is part of the mandatory API for smartset."""
3078 # builtin cannot be cached. but do not needs to
3086 # builtin cannot be cached. but do not needs to
3079 if cache and util.safehasattr(condition, 'func_code'):
3087 if cache and util.safehasattr(condition, 'func_code'):
3080 condition = util.cachefunc(condition)
3088 condition = util.cachefunc(condition)
3081 return filteredset(self, condition)
3089 return filteredset(self, condition)
3082
3090
3083 class baseset(abstractsmartset):
3091 class baseset(abstractsmartset):
3084 """Basic data structure that represents a revset and contains the basic
3092 """Basic data structure that represents a revset and contains the basic
3085 operation that it should be able to perform.
3093 operation that it should be able to perform.
3086
3094
3087 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.
3088 """
3096 """
3089 def __init__(self, data=()):
3097 def __init__(self, data=()):
3090 if not isinstance(data, list):
3098 if not isinstance(data, list):
3091 if isinstance(data, set):
3099 if isinstance(data, set):
3092 self._set = data
3100 self._set = data
3093 data = list(data)
3101 data = list(data)
3094 self._list = data
3102 self._list = data
3095 self._ascending = None
3103 self._ascending = None
3096
3104
3097 @util.propertycache
3105 @util.propertycache
3098 def _set(self):
3106 def _set(self):
3099 return set(self._list)
3107 return set(self._list)
3100
3108
3101 @util.propertycache
3109 @util.propertycache
3102 def _asclist(self):
3110 def _asclist(self):
3103 asclist = self._list[:]
3111 asclist = self._list[:]
3104 asclist.sort()
3112 asclist.sort()
3105 return asclist
3113 return asclist
3106
3114
3107 def __iter__(self):
3115 def __iter__(self):
3108 if self._ascending is None:
3116 if self._ascending is None:
3109 return iter(self._list)
3117 return iter(self._list)
3110 elif self._ascending:
3118 elif self._ascending:
3111 return iter(self._asclist)
3119 return iter(self._asclist)
3112 else:
3120 else:
3113 return reversed(self._asclist)
3121 return reversed(self._asclist)
3114
3122
3115 def fastasc(self):
3123 def fastasc(self):
3116 return iter(self._asclist)
3124 return iter(self._asclist)
3117
3125
3118 def fastdesc(self):
3126 def fastdesc(self):
3119 return reversed(self._asclist)
3127 return reversed(self._asclist)
3120
3128
3121 @util.propertycache
3129 @util.propertycache
3122 def __contains__(self):
3130 def __contains__(self):
3123 return self._set.__contains__
3131 return self._set.__contains__
3124
3132
3125 def __nonzero__(self):
3133 def __nonzero__(self):
3126 return bool(self._list)
3134 return bool(self._list)
3127
3135
3128 def sort(self, reverse=False):
3136 def sort(self, reverse=False):
3129 self._ascending = not bool(reverse)
3137 self._ascending = not bool(reverse)
3130
3138
3131 def reverse(self):
3139 def reverse(self):
3132 if self._ascending is None:
3140 if self._ascending is None:
3133 self._list.reverse()
3141 self._list.reverse()
3134 else:
3142 else:
3135 self._ascending = not self._ascending
3143 self._ascending = not self._ascending
3136
3144
3137 def __len__(self):
3145 def __len__(self):
3138 return len(self._list)
3146 return len(self._list)
3139
3147
3140 def isascending(self):
3148 def isascending(self):
3141 """Returns True if the collection is ascending order, False if not.
3149 """Returns True if the collection is ascending order, False if not.
3142
3150
3143 This is part of the mandatory API for smartset."""
3151 This is part of the mandatory API for smartset."""
3144 if len(self) <= 1:
3152 if len(self) <= 1:
3145 return True
3153 return True
3146 return self._ascending is not None and self._ascending
3154 return self._ascending is not None and self._ascending
3147
3155
3148 def isdescending(self):
3156 def isdescending(self):
3149 """Returns True if the collection is descending order, False if not.
3157 """Returns True if the collection is descending order, False if not.
3150
3158
3151 This is part of the mandatory API for smartset."""
3159 This is part of the mandatory API for smartset."""
3152 if len(self) <= 1:
3160 if len(self) <= 1:
3153 return True
3161 return True
3154 return self._ascending is not None and not self._ascending
3162 return self._ascending is not None and not self._ascending
3155
3163
3156 def first(self):
3164 def first(self):
3157 if self:
3165 if self:
3158 if self._ascending is None:
3166 if self._ascending is None:
3159 return self._list[0]
3167 return self._list[0]
3160 elif self._ascending:
3168 elif self._ascending:
3161 return self._asclist[0]
3169 return self._asclist[0]
3162 else:
3170 else:
3163 return self._asclist[-1]
3171 return self._asclist[-1]
3164 return None
3172 return None
3165
3173
3166 def last(self):
3174 def last(self):
3167 if self:
3175 if self:
3168 if self._ascending is None:
3176 if self._ascending is None:
3169 return self._list[-1]
3177 return self._list[-1]
3170 elif self._ascending:
3178 elif self._ascending:
3171 return self._asclist[-1]
3179 return self._asclist[-1]
3172 else:
3180 else:
3173 return self._asclist[0]
3181 return self._asclist[0]
3174 return None
3182 return None
3175
3183
3176 def __repr__(self):
3184 def __repr__(self):
3177 d = {None: '', False: '-', True: '+'}[self._ascending]
3185 d = {None: '', False: '-', True: '+'}[self._ascending]
3178 return '<%s%s %r>' % (type(self).__name__, d, self._list)
3186 return '<%s%s %r>' % (type(self).__name__, d, self._list)
3179
3187
3180 class filteredset(abstractsmartset):
3188 class filteredset(abstractsmartset):
3181 """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
3182 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
3183 revset
3191 revset
3184 """
3192 """
3185 def __init__(self, subset, condition=lambda x: True):
3193 def __init__(self, subset, condition=lambda x: True):
3186 """
3194 """
3187 condition: a function that decide whether a revision in the subset
3195 condition: a function that decide whether a revision in the subset
3188 belongs to the revset or not.
3196 belongs to the revset or not.
3189 """
3197 """
3190 self._subset = subset
3198 self._subset = subset
3191 self._condition = condition
3199 self._condition = condition
3192
3200
3193 def __contains__(self, x):
3201 def __contains__(self, x):
3194 return x in self._subset and self._condition(x)
3202 return x in self._subset and self._condition(x)
3195
3203
3196 def __iter__(self):
3204 def __iter__(self):
3197 return self._iterfilter(self._subset)
3205 return self._iterfilter(self._subset)
3198
3206
3199 def _iterfilter(self, it):
3207 def _iterfilter(self, it):
3200 cond = self._condition
3208 cond = self._condition
3201 for x in it:
3209 for x in it:
3202 if cond(x):
3210 if cond(x):
3203 yield x
3211 yield x
3204
3212
3205 @property
3213 @property
3206 def fastasc(self):
3214 def fastasc(self):
3207 it = self._subset.fastasc
3215 it = self._subset.fastasc
3208 if it is None:
3216 if it is None:
3209 return None
3217 return None
3210 return lambda: self._iterfilter(it())
3218 return lambda: self._iterfilter(it())
3211
3219
3212 @property
3220 @property
3213 def fastdesc(self):
3221 def fastdesc(self):
3214 it = self._subset.fastdesc
3222 it = self._subset.fastdesc
3215 if it is None:
3223 if it is None:
3216 return None
3224 return None
3217 return lambda: self._iterfilter(it())
3225 return lambda: self._iterfilter(it())
3218
3226
3219 def __nonzero__(self):
3227 def __nonzero__(self):
3220 for r in self:
3228 for r in self:
3221 return True
3229 return True
3222 return False
3230 return False
3223
3231
3224 def __len__(self):
3232 def __len__(self):
3225 # Basic implementation to be changed in future patches.
3233 # Basic implementation to be changed in future patches.
3226 l = baseset([r for r in self])
3234 l = baseset([r for r in self])
3227 return len(l)
3235 return len(l)
3228
3236
3229 def sort(self, reverse=False):
3237 def sort(self, reverse=False):
3230 self._subset.sort(reverse=reverse)
3238 self._subset.sort(reverse=reverse)
3231
3239
3232 def reverse(self):
3240 def reverse(self):
3233 self._subset.reverse()
3241 self._subset.reverse()
3234
3242
3235 def isascending(self):
3243 def isascending(self):
3236 return self._subset.isascending()
3244 return self._subset.isascending()
3237
3245
3238 def isdescending(self):
3246 def isdescending(self):
3239 return self._subset.isdescending()
3247 return self._subset.isdescending()
3240
3248
3241 def first(self):
3249 def first(self):
3242 for x in self:
3250 for x in self:
3243 return x
3251 return x
3244 return None
3252 return None
3245
3253
3246 def last(self):
3254 def last(self):
3247 it = None
3255 it = None
3248 if self.isascending():
3256 if self.isascending():
3249 it = self.fastdesc
3257 it = self.fastdesc
3250 elif self.isdescending():
3258 elif self.isdescending():
3251 it = self.fastasc
3259 it = self.fastasc
3252 if it is not None:
3260 if it is not None:
3253 for x in it():
3261 for x in it():
3254 return x
3262 return x
3255 return None #empty case
3263 return None #empty case
3256 else:
3264 else:
3257 x = None
3265 x = None
3258 for x in self:
3266 for x in self:
3259 pass
3267 pass
3260 return x
3268 return x
3261
3269
3262 def __repr__(self):
3270 def __repr__(self):
3263 return '<%s %r>' % (type(self).__name__, self._subset)
3271 return '<%s %r>' % (type(self).__name__, self._subset)
3264
3272
3265 def _iterordered(ascending, iter1, iter2):
3273 def _iterordered(ascending, iter1, iter2):
3266 """produce an ordered iteration from two iterators with the same order
3274 """produce an ordered iteration from two iterators with the same order
3267
3275
3268 The ascending is used to indicated the iteration direction.
3276 The ascending is used to indicated the iteration direction.
3269 """
3277 """
3270 choice = max
3278 choice = max
3271 if ascending:
3279 if ascending:
3272 choice = min
3280 choice = min
3273
3281
3274 val1 = None
3282 val1 = None
3275 val2 = None
3283 val2 = None
3276 try:
3284 try:
3277 # Consume both iterators in an ordered way until one is empty
3285 # Consume both iterators in an ordered way until one is empty
3278 while True:
3286 while True:
3279 if val1 is None:
3287 if val1 is None:
3280 val1 = iter1.next()
3288 val1 = iter1.next()
3281 if val2 is None:
3289 if val2 is None:
3282 val2 = iter2.next()
3290 val2 = iter2.next()
3283 next = choice(val1, val2)
3291 next = choice(val1, val2)
3284 yield next
3292 yield next
3285 if val1 == next:
3293 if val1 == next:
3286 val1 = None
3294 val1 = None
3287 if val2 == next:
3295 if val2 == next:
3288 val2 = None
3296 val2 = None
3289 except StopIteration:
3297 except StopIteration:
3290 # Flush any remaining values and consume the other one
3298 # Flush any remaining values and consume the other one
3291 it = iter2
3299 it = iter2
3292 if val1 is not None:
3300 if val1 is not None:
3293 yield val1
3301 yield val1
3294 it = iter1
3302 it = iter1
3295 elif val2 is not None:
3303 elif val2 is not None:
3296 # might have been equality and both are empty
3304 # might have been equality and both are empty
3297 yield val2
3305 yield val2
3298 for val in it:
3306 for val in it:
3299 yield val
3307 yield val
3300
3308
3301 class addset(abstractsmartset):
3309 class addset(abstractsmartset):
3302 """Represent the addition of two sets
3310 """Represent the addition of two sets
3303
3311
3304 Wrapper structure for lazily adding two structures without losing much
3312 Wrapper structure for lazily adding two structures without losing much
3305 performance on the __contains__ method
3313 performance on the __contains__ method
3306
3314
3307 If the ascending attribute is set, that means the two structures are
3315 If the ascending attribute is set, that means the two structures are
3308 ordered in either an ascending or descending way. Therefore, we can add
3316 ordered in either an ascending or descending way. Therefore, we can add
3309 them maintaining the order by iterating over both at the same time
3317 them maintaining the order by iterating over both at the same time
3310
3318
3311 >>> xs = baseset([0, 3, 2])
3319 >>> xs = baseset([0, 3, 2])
3312 >>> ys = baseset([5, 2, 4])
3320 >>> ys = baseset([5, 2, 4])
3313
3321
3314 >>> rs = addset(xs, ys)
3322 >>> rs = addset(xs, ys)
3315 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
3323 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
3316 (True, True, False, True, 0, 4)
3324 (True, True, False, True, 0, 4)
3317 >>> rs = addset(xs, baseset([]))
3325 >>> rs = addset(xs, baseset([]))
3318 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
3326 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
3319 (True, True, False, 0, 2)
3327 (True, True, False, 0, 2)
3320 >>> rs = addset(baseset([]), baseset([]))
3328 >>> rs = addset(baseset([]), baseset([]))
3321 >>> bool(rs), 0 in rs, rs.first(), rs.last()
3329 >>> bool(rs), 0 in rs, rs.first(), rs.last()
3322 (False, False, None, None)
3330 (False, False, None, None)
3323
3331
3324 iterate unsorted:
3332 iterate unsorted:
3325 >>> rs = addset(xs, ys)
3333 >>> rs = addset(xs, ys)
3326 >>> [x for x in rs] # without _genlist
3334 >>> [x for x in rs] # without _genlist
3327 [0, 3, 2, 5, 4]
3335 [0, 3, 2, 5, 4]
3328 >>> assert not rs._genlist
3336 >>> assert not rs._genlist
3329 >>> len(rs)
3337 >>> len(rs)
3330 5
3338 5
3331 >>> [x for x in rs] # with _genlist
3339 >>> [x for x in rs] # with _genlist
3332 [0, 3, 2, 5, 4]
3340 [0, 3, 2, 5, 4]
3333 >>> assert rs._genlist
3341 >>> assert rs._genlist
3334
3342
3335 iterate ascending:
3343 iterate ascending:
3336 >>> rs = addset(xs, ys, ascending=True)
3344 >>> rs = addset(xs, ys, ascending=True)
3337 >>> [x for x in rs], [x for x in rs.fastasc()] # without _asclist
3345 >>> [x for x in rs], [x for x in rs.fastasc()] # without _asclist
3338 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3346 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3339 >>> assert not rs._asclist
3347 >>> assert not rs._asclist
3340 >>> len(rs)
3348 >>> len(rs)
3341 5
3349 5
3342 >>> [x for x in rs], [x for x in rs.fastasc()]
3350 >>> [x for x in rs], [x for x in rs.fastasc()]
3343 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3351 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3344 >>> assert rs._asclist
3352 >>> assert rs._asclist
3345
3353
3346 iterate descending:
3354 iterate descending:
3347 >>> rs = addset(xs, ys, ascending=False)
3355 >>> rs = addset(xs, ys, ascending=False)
3348 >>> [x for x in rs], [x for x in rs.fastdesc()] # without _asclist
3356 >>> [x for x in rs], [x for x in rs.fastdesc()] # without _asclist
3349 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3357 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3350 >>> assert not rs._asclist
3358 >>> assert not rs._asclist
3351 >>> len(rs)
3359 >>> len(rs)
3352 5
3360 5
3353 >>> [x for x in rs], [x for x in rs.fastdesc()]
3361 >>> [x for x in rs], [x for x in rs.fastdesc()]
3354 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3362 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3355 >>> assert rs._asclist
3363 >>> assert rs._asclist
3356
3364
3357 iterate ascending without fastasc:
3365 iterate ascending without fastasc:
3358 >>> rs = addset(xs, generatorset(ys), ascending=True)
3366 >>> rs = addset(xs, generatorset(ys), ascending=True)
3359 >>> assert rs.fastasc is None
3367 >>> assert rs.fastasc is None
3360 >>> [x for x in rs]
3368 >>> [x for x in rs]
3361 [0, 2, 3, 4, 5]
3369 [0, 2, 3, 4, 5]
3362
3370
3363 iterate descending without fastdesc:
3371 iterate descending without fastdesc:
3364 >>> rs = addset(generatorset(xs), ys, ascending=False)
3372 >>> rs = addset(generatorset(xs), ys, ascending=False)
3365 >>> assert rs.fastdesc is None
3373 >>> assert rs.fastdesc is None
3366 >>> [x for x in rs]
3374 >>> [x for x in rs]
3367 [5, 4, 3, 2, 0]
3375 [5, 4, 3, 2, 0]
3368 """
3376 """
3369 def __init__(self, revs1, revs2, ascending=None):
3377 def __init__(self, revs1, revs2, ascending=None):
3370 self._r1 = revs1
3378 self._r1 = revs1
3371 self._r2 = revs2
3379 self._r2 = revs2
3372 self._iter = None
3380 self._iter = None
3373 self._ascending = ascending
3381 self._ascending = ascending
3374 self._genlist = None
3382 self._genlist = None
3375 self._asclist = None
3383 self._asclist = None
3376
3384
3377 def __len__(self):
3385 def __len__(self):
3378 return len(self._list)
3386 return len(self._list)
3379
3387
3380 def __nonzero__(self):
3388 def __nonzero__(self):
3381 return bool(self._r1) or bool(self._r2)
3389 return bool(self._r1) or bool(self._r2)
3382
3390
3383 @util.propertycache
3391 @util.propertycache
3384 def _list(self):
3392 def _list(self):
3385 if not self._genlist:
3393 if not self._genlist:
3386 self._genlist = baseset(iter(self))
3394 self._genlist = baseset(iter(self))
3387 return self._genlist
3395 return self._genlist
3388
3396
3389 def __iter__(self):
3397 def __iter__(self):
3390 """Iterate over both collections without repeating elements
3398 """Iterate over both collections without repeating elements
3391
3399
3392 If the ascending attribute is not set, iterate over the first one and
3400 If the ascending attribute is not set, iterate over the first one and
3393 then over the second one checking for membership on the first one so we
3401 then over the second one checking for membership on the first one so we
3394 dont yield any duplicates.
3402 dont yield any duplicates.
3395
3403
3396 If the ascending attribute is set, iterate over both collections at the
3404 If the ascending attribute is set, iterate over both collections at the
3397 same time, yielding only one value at a time in the given order.
3405 same time, yielding only one value at a time in the given order.
3398 """
3406 """
3399 if self._ascending is None:
3407 if self._ascending is None:
3400 if self._genlist:
3408 if self._genlist:
3401 return iter(self._genlist)
3409 return iter(self._genlist)
3402 def arbitraryordergen():
3410 def arbitraryordergen():
3403 for r in self._r1:
3411 for r in self._r1:
3404 yield r
3412 yield r
3405 inr1 = self._r1.__contains__
3413 inr1 = self._r1.__contains__
3406 for r in self._r2:
3414 for r in self._r2:
3407 if not inr1(r):
3415 if not inr1(r):
3408 yield r
3416 yield r
3409 return arbitraryordergen()
3417 return arbitraryordergen()
3410 # try to use our own fast iterator if it exists
3418 # try to use our own fast iterator if it exists
3411 self._trysetasclist()
3419 self._trysetasclist()
3412 if self._ascending:
3420 if self._ascending:
3413 attr = 'fastasc'
3421 attr = 'fastasc'
3414 else:
3422 else:
3415 attr = 'fastdesc'
3423 attr = 'fastdesc'
3416 it = getattr(self, attr)
3424 it = getattr(self, attr)
3417 if it is not None:
3425 if it is not None:
3418 return it()
3426 return it()
3419 # maybe half of the component supports fast
3427 # maybe half of the component supports fast
3420 # get iterator for _r1
3428 # get iterator for _r1
3421 iter1 = getattr(self._r1, attr)
3429 iter1 = getattr(self._r1, attr)
3422 if iter1 is None:
3430 if iter1 is None:
3423 # let's avoid side effect (not sure it matters)
3431 # let's avoid side effect (not sure it matters)
3424 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
3432 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
3425 else:
3433 else:
3426 iter1 = iter1()
3434 iter1 = iter1()
3427 # get iterator for _r2
3435 # get iterator for _r2
3428 iter2 = getattr(self._r2, attr)
3436 iter2 = getattr(self._r2, attr)
3429 if iter2 is None:
3437 if iter2 is None:
3430 # let's avoid side effect (not sure it matters)
3438 # let's avoid side effect (not sure it matters)
3431 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
3439 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
3432 else:
3440 else:
3433 iter2 = iter2()
3441 iter2 = iter2()
3434 return _iterordered(self._ascending, iter1, iter2)
3442 return _iterordered(self._ascending, iter1, iter2)
3435
3443
3436 def _trysetasclist(self):
3444 def _trysetasclist(self):
3437 """populate the _asclist attribute if possible and necessary"""
3445 """populate the _asclist attribute if possible and necessary"""
3438 if self._genlist is not None and self._asclist is None:
3446 if self._genlist is not None and self._asclist is None:
3439 self._asclist = sorted(self._genlist)
3447 self._asclist = sorted(self._genlist)
3440
3448
3441 @property
3449 @property
3442 def fastasc(self):
3450 def fastasc(self):
3443 self._trysetasclist()
3451 self._trysetasclist()
3444 if self._asclist is not None:
3452 if self._asclist is not None:
3445 return self._asclist.__iter__
3453 return self._asclist.__iter__
3446 iter1 = self._r1.fastasc
3454 iter1 = self._r1.fastasc
3447 iter2 = self._r2.fastasc
3455 iter2 = self._r2.fastasc
3448 if None in (iter1, iter2):
3456 if None in (iter1, iter2):
3449 return None
3457 return None
3450 return lambda: _iterordered(True, iter1(), iter2())
3458 return lambda: _iterordered(True, iter1(), iter2())
3451
3459
3452 @property
3460 @property
3453 def fastdesc(self):
3461 def fastdesc(self):
3454 self._trysetasclist()
3462 self._trysetasclist()
3455 if self._asclist is not None:
3463 if self._asclist is not None:
3456 return self._asclist.__reversed__
3464 return self._asclist.__reversed__
3457 iter1 = self._r1.fastdesc
3465 iter1 = self._r1.fastdesc
3458 iter2 = self._r2.fastdesc
3466 iter2 = self._r2.fastdesc
3459 if None in (iter1, iter2):
3467 if None in (iter1, iter2):
3460 return None
3468 return None
3461 return lambda: _iterordered(False, iter1(), iter2())
3469 return lambda: _iterordered(False, iter1(), iter2())
3462
3470
3463 def __contains__(self, x):
3471 def __contains__(self, x):
3464 return x in self._r1 or x in self._r2
3472 return x in self._r1 or x in self._r2
3465
3473
3466 def sort(self, reverse=False):
3474 def sort(self, reverse=False):
3467 """Sort the added set
3475 """Sort the added set
3468
3476
3469 For this we use the cached list with all the generated values and if we
3477 For this we use the cached list with all the generated values and if we
3470 know they are ascending or descending we can sort them in a smart way.
3478 know they are ascending or descending we can sort them in a smart way.
3471 """
3479 """
3472 self._ascending = not reverse
3480 self._ascending = not reverse
3473
3481
3474 def isascending(self):
3482 def isascending(self):
3475 return self._ascending is not None and self._ascending
3483 return self._ascending is not None and self._ascending
3476
3484
3477 def isdescending(self):
3485 def isdescending(self):
3478 return self._ascending is not None and not self._ascending
3486 return self._ascending is not None and not self._ascending
3479
3487
3480 def reverse(self):
3488 def reverse(self):
3481 if self._ascending is None:
3489 if self._ascending is None:
3482 self._list.reverse()
3490 self._list.reverse()
3483 else:
3491 else:
3484 self._ascending = not self._ascending
3492 self._ascending = not self._ascending
3485
3493
3486 def first(self):
3494 def first(self):
3487 for x in self:
3495 for x in self:
3488 return x
3496 return x
3489 return None
3497 return None
3490
3498
3491 def last(self):
3499 def last(self):
3492 self.reverse()
3500 self.reverse()
3493 val = self.first()
3501 val = self.first()
3494 self.reverse()
3502 self.reverse()
3495 return val
3503 return val
3496
3504
3497 def __repr__(self):
3505 def __repr__(self):
3498 d = {None: '', False: '-', True: '+'}[self._ascending]
3506 d = {None: '', False: '-', True: '+'}[self._ascending]
3499 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
3507 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
3500
3508
3501 class generatorset(abstractsmartset):
3509 class generatorset(abstractsmartset):
3502 """Wrap a generator for lazy iteration
3510 """Wrap a generator for lazy iteration
3503
3511
3504 Wrapper structure for generators that provides lazy membership and can
3512 Wrapper structure for generators that provides lazy membership and can
3505 be iterated more than once.
3513 be iterated more than once.
3506 When asked for membership it generates values until either it finds the
3514 When asked for membership it generates values until either it finds the
3507 requested one or has gone through all the elements in the generator
3515 requested one or has gone through all the elements in the generator
3508 """
3516 """
3509 def __init__(self, gen, iterasc=None):
3517 def __init__(self, gen, iterasc=None):
3510 """
3518 """
3511 gen: a generator producing the values for the generatorset.
3519 gen: a generator producing the values for the generatorset.
3512 """
3520 """
3513 self._gen = gen
3521 self._gen = gen
3514 self._asclist = None
3522 self._asclist = None
3515 self._cache = {}
3523 self._cache = {}
3516 self._genlist = []
3524 self._genlist = []
3517 self._finished = False
3525 self._finished = False
3518 self._ascending = True
3526 self._ascending = True
3519 if iterasc is not None:
3527 if iterasc is not None:
3520 if iterasc:
3528 if iterasc:
3521 self.fastasc = self._iterator
3529 self.fastasc = self._iterator
3522 self.__contains__ = self._asccontains
3530 self.__contains__ = self._asccontains
3523 else:
3531 else:
3524 self.fastdesc = self._iterator
3532 self.fastdesc = self._iterator
3525 self.__contains__ = self._desccontains
3533 self.__contains__ = self._desccontains
3526
3534
3527 def __nonzero__(self):
3535 def __nonzero__(self):
3528 # Do not use 'for r in self' because it will enforce the iteration
3536 # Do not use 'for r in self' because it will enforce the iteration
3529 # order (default ascending), possibly unrolling a whole descending
3537 # order (default ascending), possibly unrolling a whole descending
3530 # iterator.
3538 # iterator.
3531 if self._genlist:
3539 if self._genlist:
3532 return True
3540 return True
3533 for r in self._consumegen():
3541 for r in self._consumegen():
3534 return True
3542 return True
3535 return False
3543 return False
3536
3544
3537 def __contains__(self, x):
3545 def __contains__(self, x):
3538 if x in self._cache:
3546 if x in self._cache:
3539 return self._cache[x]
3547 return self._cache[x]
3540
3548
3541 # Use new values only, as existing values would be cached.
3549 # Use new values only, as existing values would be cached.
3542 for l in self._consumegen():
3550 for l in self._consumegen():
3543 if l == x:
3551 if l == x:
3544 return True
3552 return True
3545
3553
3546 self._cache[x] = False
3554 self._cache[x] = False
3547 return False
3555 return False
3548
3556
3549 def _asccontains(self, x):
3557 def _asccontains(self, x):
3550 """version of contains optimised for ascending generator"""
3558 """version of contains optimised for ascending generator"""
3551 if x in self._cache:
3559 if x in self._cache:
3552 return self._cache[x]
3560 return self._cache[x]
3553
3561
3554 # Use new values only, as existing values would be cached.
3562 # Use new values only, as existing values would be cached.
3555 for l in self._consumegen():
3563 for l in self._consumegen():
3556 if l == x:
3564 if l == x:
3557 return True
3565 return True
3558 if l > x:
3566 if l > x:
3559 break
3567 break
3560
3568
3561 self._cache[x] = False
3569 self._cache[x] = False
3562 return False
3570 return False
3563
3571
3564 def _desccontains(self, x):
3572 def _desccontains(self, x):
3565 """version of contains optimised for descending generator"""
3573 """version of contains optimised for descending generator"""
3566 if x in self._cache:
3574 if x in self._cache:
3567 return self._cache[x]
3575 return self._cache[x]
3568
3576
3569 # Use new values only, as existing values would be cached.
3577 # Use new values only, as existing values would be cached.
3570 for l in self._consumegen():
3578 for l in self._consumegen():
3571 if l == x:
3579 if l == x:
3572 return True
3580 return True
3573 if l < x:
3581 if l < x:
3574 break
3582 break
3575
3583
3576 self._cache[x] = False
3584 self._cache[x] = False
3577 return False
3585 return False
3578
3586
3579 def __iter__(self):
3587 def __iter__(self):
3580 if self._ascending:
3588 if self._ascending:
3581 it = self.fastasc
3589 it = self.fastasc
3582 else:
3590 else:
3583 it = self.fastdesc
3591 it = self.fastdesc
3584 if it is not None:
3592 if it is not None:
3585 return it()
3593 return it()
3586 # we need to consume the iterator
3594 # we need to consume the iterator
3587 for x in self._consumegen():
3595 for x in self._consumegen():
3588 pass
3596 pass
3589 # recall the same code
3597 # recall the same code
3590 return iter(self)
3598 return iter(self)
3591
3599
3592 def _iterator(self):
3600 def _iterator(self):
3593 if self._finished:
3601 if self._finished:
3594 return iter(self._genlist)
3602 return iter(self._genlist)
3595
3603
3596 # We have to use this complex iteration strategy to allow multiple
3604 # We have to use this complex iteration strategy to allow multiple
3597 # iterations at the same time. We need to be able to catch revision
3605 # iterations at the same time. We need to be able to catch revision
3598 # removed from _consumegen and added to genlist in another instance.
3606 # removed from _consumegen and added to genlist in another instance.
3599 #
3607 #
3600 # Getting rid of it would provide an about 15% speed up on this
3608 # Getting rid of it would provide an about 15% speed up on this
3601 # iteration.
3609 # iteration.
3602 genlist = self._genlist
3610 genlist = self._genlist
3603 nextrev = self._consumegen().next
3611 nextrev = self._consumegen().next
3604 _len = len # cache global lookup
3612 _len = len # cache global lookup
3605 def gen():
3613 def gen():
3606 i = 0
3614 i = 0
3607 while True:
3615 while True:
3608 if i < _len(genlist):
3616 if i < _len(genlist):
3609 yield genlist[i]
3617 yield genlist[i]
3610 else:
3618 else:
3611 yield nextrev()
3619 yield nextrev()
3612 i += 1
3620 i += 1
3613 return gen()
3621 return gen()
3614
3622
3615 def _consumegen(self):
3623 def _consumegen(self):
3616 cache = self._cache
3624 cache = self._cache
3617 genlist = self._genlist.append
3625 genlist = self._genlist.append
3618 for item in self._gen:
3626 for item in self._gen:
3619 cache[item] = True
3627 cache[item] = True
3620 genlist(item)
3628 genlist(item)
3621 yield item
3629 yield item
3622 if not self._finished:
3630 if not self._finished:
3623 self._finished = True
3631 self._finished = True
3624 asc = self._genlist[:]
3632 asc = self._genlist[:]
3625 asc.sort()
3633 asc.sort()
3626 self._asclist = asc
3634 self._asclist = asc
3627 self.fastasc = asc.__iter__
3635 self.fastasc = asc.__iter__
3628 self.fastdesc = asc.__reversed__
3636 self.fastdesc = asc.__reversed__
3629
3637
3630 def __len__(self):
3638 def __len__(self):
3631 for x in self._consumegen():
3639 for x in self._consumegen():
3632 pass
3640 pass
3633 return len(self._genlist)
3641 return len(self._genlist)
3634
3642
3635 def sort(self, reverse=False):
3643 def sort(self, reverse=False):
3636 self._ascending = not reverse
3644 self._ascending = not reverse
3637
3645
3638 def reverse(self):
3646 def reverse(self):
3639 self._ascending = not self._ascending
3647 self._ascending = not self._ascending
3640
3648
3641 def isascending(self):
3649 def isascending(self):
3642 return self._ascending
3650 return self._ascending
3643
3651
3644 def isdescending(self):
3652 def isdescending(self):
3645 return not self._ascending
3653 return not self._ascending
3646
3654
3647 def first(self):
3655 def first(self):
3648 if self._ascending:
3656 if self._ascending:
3649 it = self.fastasc
3657 it = self.fastasc
3650 else:
3658 else:
3651 it = self.fastdesc
3659 it = self.fastdesc
3652 if it is None:
3660 if it is None:
3653 # we need to consume all and try again
3661 # we need to consume all and try again
3654 for x in self._consumegen():
3662 for x in self._consumegen():
3655 pass
3663 pass
3656 return self.first()
3664 return self.first()
3657 return next(it(), None)
3665 return next(it(), None)
3658
3666
3659 def last(self):
3667 def last(self):
3660 if self._ascending:
3668 if self._ascending:
3661 it = self.fastdesc
3669 it = self.fastdesc
3662 else:
3670 else:
3663 it = self.fastasc
3671 it = self.fastasc
3664 if it is None:
3672 if it is None:
3665 # we need to consume all and try again
3673 # we need to consume all and try again
3666 for x in self._consumegen():
3674 for x in self._consumegen():
3667 pass
3675 pass
3668 return self.first()
3676 return self.first()
3669 return next(it(), None)
3677 return next(it(), None)
3670
3678
3671 def __repr__(self):
3679 def __repr__(self):
3672 d = {False: '-', True: '+'}[self._ascending]
3680 d = {False: '-', True: '+'}[self._ascending]
3673 return '<%s%s>' % (type(self).__name__, d)
3681 return '<%s%s>' % (type(self).__name__, d)
3674
3682
3675 class spanset(abstractsmartset):
3683 class spanset(abstractsmartset):
3676 """Duck type for baseset class which represents a range of revisions and
3684 """Duck type for baseset class which represents a range of revisions and
3677 can work lazily and without having all the range in memory
3685 can work lazily and without having all the range in memory
3678
3686
3679 Note that spanset(x, y) behave almost like xrange(x, y) except for two
3687 Note that spanset(x, y) behave almost like xrange(x, y) except for two
3680 notable points:
3688 notable points:
3681 - when x < y it will be automatically descending,
3689 - when x < y it will be automatically descending,
3682 - revision filtered with this repoview will be skipped.
3690 - revision filtered with this repoview will be skipped.
3683
3691
3684 """
3692 """
3685 def __init__(self, repo, start=0, end=None):
3693 def __init__(self, repo, start=0, end=None):
3686 """
3694 """
3687 start: first revision included the set
3695 start: first revision included the set
3688 (default to 0)
3696 (default to 0)
3689 end: first revision excluded (last+1)
3697 end: first revision excluded (last+1)
3690 (default to len(repo)
3698 (default to len(repo)
3691
3699
3692 Spanset will be descending if `end` < `start`.
3700 Spanset will be descending if `end` < `start`.
3693 """
3701 """
3694 if end is None:
3702 if end is None:
3695 end = len(repo)
3703 end = len(repo)
3696 self._ascending = start <= end
3704 self._ascending = start <= end
3697 if not self._ascending:
3705 if not self._ascending:
3698 start, end = end + 1, start +1
3706 start, end = end + 1, start +1
3699 self._start = start
3707 self._start = start
3700 self._end = end
3708 self._end = end
3701 self._hiddenrevs = repo.changelog.filteredrevs
3709 self._hiddenrevs = repo.changelog.filteredrevs
3702
3710
3703 def sort(self, reverse=False):
3711 def sort(self, reverse=False):
3704 self._ascending = not reverse
3712 self._ascending = not reverse
3705
3713
3706 def reverse(self):
3714 def reverse(self):
3707 self._ascending = not self._ascending
3715 self._ascending = not self._ascending
3708
3716
3709 def _iterfilter(self, iterrange):
3717 def _iterfilter(self, iterrange):
3710 s = self._hiddenrevs
3718 s = self._hiddenrevs
3711 for r in iterrange:
3719 for r in iterrange:
3712 if r not in s:
3720 if r not in s:
3713 yield r
3721 yield r
3714
3722
3715 def __iter__(self):
3723 def __iter__(self):
3716 if self._ascending:
3724 if self._ascending:
3717 return self.fastasc()
3725 return self.fastasc()
3718 else:
3726 else:
3719 return self.fastdesc()
3727 return self.fastdesc()
3720
3728
3721 def fastasc(self):
3729 def fastasc(self):
3722 iterrange = xrange(self._start, self._end)
3730 iterrange = xrange(self._start, self._end)
3723 if self._hiddenrevs:
3731 if self._hiddenrevs:
3724 return self._iterfilter(iterrange)
3732 return self._iterfilter(iterrange)
3725 return iter(iterrange)
3733 return iter(iterrange)
3726
3734
3727 def fastdesc(self):
3735 def fastdesc(self):
3728 iterrange = xrange(self._end - 1, self._start - 1, -1)
3736 iterrange = xrange(self._end - 1, self._start - 1, -1)
3729 if self._hiddenrevs:
3737 if self._hiddenrevs:
3730 return self._iterfilter(iterrange)
3738 return self._iterfilter(iterrange)
3731 return iter(iterrange)
3739 return iter(iterrange)
3732
3740
3733 def __contains__(self, rev):
3741 def __contains__(self, rev):
3734 hidden = self._hiddenrevs
3742 hidden = self._hiddenrevs
3735 return ((self._start <= rev < self._end)
3743 return ((self._start <= rev < self._end)
3736 and not (hidden and rev in hidden))
3744 and not (hidden and rev in hidden))
3737
3745
3738 def __nonzero__(self):
3746 def __nonzero__(self):
3739 for r in self:
3747 for r in self:
3740 return True
3748 return True
3741 return False
3749 return False
3742
3750
3743 def __len__(self):
3751 def __len__(self):
3744 if not self._hiddenrevs:
3752 if not self._hiddenrevs:
3745 return abs(self._end - self._start)
3753 return abs(self._end - self._start)
3746 else:
3754 else:
3747 count = 0
3755 count = 0
3748 start = self._start
3756 start = self._start
3749 end = self._end
3757 end = self._end
3750 for rev in self._hiddenrevs:
3758 for rev in self._hiddenrevs:
3751 if (end < rev <= start) or (start <= rev < end):
3759 if (end < rev <= start) or (start <= rev < end):
3752 count += 1
3760 count += 1
3753 return abs(self._end - self._start) - count
3761 return abs(self._end - self._start) - count
3754
3762
3755 def isascending(self):
3763 def isascending(self):
3756 return self._ascending
3764 return self._ascending
3757
3765
3758 def isdescending(self):
3766 def isdescending(self):
3759 return not self._ascending
3767 return not self._ascending
3760
3768
3761 def first(self):
3769 def first(self):
3762 if self._ascending:
3770 if self._ascending:
3763 it = self.fastasc
3771 it = self.fastasc
3764 else:
3772 else:
3765 it = self.fastdesc
3773 it = self.fastdesc
3766 for x in it():
3774 for x in it():
3767 return x
3775 return x
3768 return None
3776 return None
3769
3777
3770 def last(self):
3778 def last(self):
3771 if self._ascending:
3779 if self._ascending:
3772 it = self.fastdesc
3780 it = self.fastdesc
3773 else:
3781 else:
3774 it = self.fastasc
3782 it = self.fastasc
3775 for x in it():
3783 for x in it():
3776 return x
3784 return x
3777 return None
3785 return None
3778
3786
3779 def __repr__(self):
3787 def __repr__(self):
3780 d = {False: '-', True: '+'}[self._ascending]
3788 d = {False: '-', True: '+'}[self._ascending]
3781 return '<%s%s %d:%d>' % (type(self).__name__, d,
3789 return '<%s%s %d:%d>' % (type(self).__name__, d,
3782 self._start, self._end - 1)
3790 self._start, self._end - 1)
3783
3791
3784 class fullreposet(spanset):
3792 class fullreposet(spanset):
3785 """a set containing all revisions in the repo
3793 """a set containing all revisions in the repo
3786
3794
3787 This class exists to host special optimization and magic to handle virtual
3795 This class exists to host special optimization and magic to handle virtual
3788 revisions such as "null".
3796 revisions such as "null".
3789 """
3797 """
3790
3798
3791 def __init__(self, repo):
3799 def __init__(self, repo):
3792 super(fullreposet, self).__init__(repo)
3800 super(fullreposet, self).__init__(repo)
3793
3801
3794 def __and__(self, other):
3802 def __and__(self, other):
3795 """As self contains the whole repo, all of the other set should also be
3803 """As self contains the whole repo, all of the other set should also be
3796 in self. Therefore `self & other = other`.
3804 in self. Therefore `self & other = other`.
3797
3805
3798 This boldly assumes the other contains valid revs only.
3806 This boldly assumes the other contains valid revs only.
3799 """
3807 """
3800 # other not a smartset, make is so
3808 # other not a smartset, make is so
3801 if not util.safehasattr(other, 'isascending'):
3809 if not util.safehasattr(other, 'isascending'):
3802 # filter out hidden revision
3810 # filter out hidden revision
3803 # (this boldly assumes all smartset are pure)
3811 # (this boldly assumes all smartset are pure)
3804 #
3812 #
3805 # `other` was used with "&", let's assume this is a set like
3813 # `other` was used with "&", let's assume this is a set like
3806 # object.
3814 # object.
3807 other = baseset(other - self._hiddenrevs)
3815 other = baseset(other - self._hiddenrevs)
3808
3816
3809 # XXX As fullreposet is also used as bootstrap, this is wrong.
3817 # XXX As fullreposet is also used as bootstrap, this is wrong.
3810 #
3818 #
3811 # With a giveme312() revset returning [3,1,2], this makes
3819 # With a giveme312() revset returning [3,1,2], this makes
3812 # 'hg log -r "giveme312()"' -> 1, 2, 3 (wrong)
3820 # 'hg log -r "giveme312()"' -> 1, 2, 3 (wrong)
3813 # We cannot just drop it because other usage still need to sort it:
3821 # We cannot just drop it because other usage still need to sort it:
3814 # 'hg log -r "all() and giveme312()"' -> 1, 2, 3 (right)
3822 # 'hg log -r "all() and giveme312()"' -> 1, 2, 3 (right)
3815 #
3823 #
3816 # There is also some faulty revset implementations that rely on it
3824 # There is also some faulty revset implementations that rely on it
3817 # (eg: children as of its state in e8075329c5fb)
3825 # (eg: children as of its state in e8075329c5fb)
3818 #
3826 #
3819 # When we fix the two points above we can move this into the if clause
3827 # When we fix the two points above we can move this into the if clause
3820 other.sort(reverse=self.isdescending())
3828 other.sort(reverse=self.isdescending())
3821 return other
3829 return other
3822
3830
3823 def prettyformatset(revs):
3831 def prettyformatset(revs):
3824 lines = []
3832 lines = []
3825 rs = repr(revs)
3833 rs = repr(revs)
3826 p = 0
3834 p = 0
3827 while p < len(rs):
3835 while p < len(rs):
3828 q = rs.find('<', p + 1)
3836 q = rs.find('<', p + 1)
3829 if q < 0:
3837 if q < 0:
3830 q = len(rs)
3838 q = len(rs)
3831 l = rs.count('<', 0, p) - rs.count('>', 0, p)
3839 l = rs.count('<', 0, p) - rs.count('>', 0, p)
3832 assert l >= 0
3840 assert l >= 0
3833 lines.append((l, rs[p:q].rstrip()))
3841 lines.append((l, rs[p:q].rstrip()))
3834 p = q
3842 p = q
3835 return '\n'.join(' ' * l + s for l, s in lines)
3843 return '\n'.join(' ' * l + s for l, s in lines)
3836
3844
3837 # tell hggettext to extract docstrings from these functions:
3845 # tell hggettext to extract docstrings from these functions:
3838 i18nfunctions = symbols.values()
3846 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now