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