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