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