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