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