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