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