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