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