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