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