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