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