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