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