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