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