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