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