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