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