##// END OF EJS Templates
filelog: fix backwards comment for 'backrevref'
Martin von Zweigbergk -
r23822:02f4560b default
parent child Browse files
Show More
@@ -1,3112 +1,3112 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: changerev -> filerev
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 node_(repo, subset, x):
1180 def node_(repo, subset, x):
1181 """``id(string)``
1181 """``id(string)``
1182 Revision non-ambiguously specified by the given hex string prefix.
1182 Revision non-ambiguously specified by the given hex string prefix.
1183 """
1183 """
1184 # i18n: "id" is a keyword
1184 # i18n: "id" is a keyword
1185 l = getargs(x, 1, 1, _("id requires one argument"))
1185 l = getargs(x, 1, 1, _("id requires one argument"))
1186 # i18n: "id" is a keyword
1186 # i18n: "id" is a keyword
1187 n = getstring(l[0], _("id requires a string"))
1187 n = getstring(l[0], _("id requires a string"))
1188 if len(n) == 40:
1188 if len(n) == 40:
1189 rn = repo[n].rev()
1189 rn = repo[n].rev()
1190 else:
1190 else:
1191 rn = None
1191 rn = None
1192 pm = repo.changelog._partialmatch(n)
1192 pm = repo.changelog._partialmatch(n)
1193 if pm is not None:
1193 if pm is not None:
1194 rn = repo.changelog.rev(pm)
1194 rn = repo.changelog.rev(pm)
1195
1195
1196 if rn is None:
1196 if rn is None:
1197 return baseset()
1197 return baseset()
1198 result = baseset([rn])
1198 result = baseset([rn])
1199 return result & subset
1199 return result & subset
1200
1200
1201 def obsolete(repo, subset, x):
1201 def obsolete(repo, subset, x):
1202 """``obsolete()``
1202 """``obsolete()``
1203 Mutable changeset with a newer version."""
1203 Mutable changeset with a newer version."""
1204 # i18n: "obsolete" is a keyword
1204 # i18n: "obsolete" is a keyword
1205 getargs(x, 0, 0, _("obsolete takes no arguments"))
1205 getargs(x, 0, 0, _("obsolete takes no arguments"))
1206 obsoletes = obsmod.getrevs(repo, 'obsolete')
1206 obsoletes = obsmod.getrevs(repo, 'obsolete')
1207 return subset & obsoletes
1207 return subset & obsoletes
1208
1208
1209 def only(repo, subset, x):
1209 def only(repo, subset, x):
1210 """``only(set, [set])``
1210 """``only(set, [set])``
1211 Changesets that are ancestors of the first set that are not ancestors
1211 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
1212 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
1213 is ancestors of the first set that are not ancestors of the second set
1214 (i.e. ::<set1> - ::<set2>).
1214 (i.e. ::<set1> - ::<set2>).
1215 """
1215 """
1216 cl = repo.changelog
1216 cl = repo.changelog
1217 # i18n: "only" is a keyword
1217 # i18n: "only" is a keyword
1218 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1218 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1219 include = getset(repo, spanset(repo), args[0])
1219 include = getset(repo, spanset(repo), args[0])
1220 if len(args) == 1:
1220 if len(args) == 1:
1221 if not include:
1221 if not include:
1222 return baseset()
1222 return baseset()
1223
1223
1224 descendants = set(_revdescendants(repo, include, False))
1224 descendants = set(_revdescendants(repo, include, False))
1225 exclude = [rev for rev in cl.headrevs()
1225 exclude = [rev for rev in cl.headrevs()
1226 if not rev in descendants and not rev in include]
1226 if not rev in descendants and not rev in include]
1227 else:
1227 else:
1228 exclude = getset(repo, spanset(repo), args[1])
1228 exclude = getset(repo, spanset(repo), args[1])
1229
1229
1230 results = set(cl.findmissingrevs(common=exclude, heads=include))
1230 results = set(cl.findmissingrevs(common=exclude, heads=include))
1231 return subset & results
1231 return subset & results
1232
1232
1233 def origin(repo, subset, x):
1233 def origin(repo, subset, x):
1234 """``origin([set])``
1234 """``origin([set])``
1235 Changesets that were specified as a source for the grafts, transplants or
1235 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
1236 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
1237 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
1238 specified as a source for one of these operations, only the source changeset
1239 for the first operation is selected.
1239 for the first operation is selected.
1240 """
1240 """
1241 if x is not None:
1241 if x is not None:
1242 dests = getset(repo, spanset(repo), x)
1242 dests = getset(repo, spanset(repo), x)
1243 else:
1243 else:
1244 dests = getall(repo, spanset(repo), x)
1244 dests = getall(repo, spanset(repo), x)
1245
1245
1246 def _firstsrc(rev):
1246 def _firstsrc(rev):
1247 src = _getrevsource(repo, rev)
1247 src = _getrevsource(repo, rev)
1248 if src is None:
1248 if src is None:
1249 return None
1249 return None
1250
1250
1251 while True:
1251 while True:
1252 prev = _getrevsource(repo, src)
1252 prev = _getrevsource(repo, src)
1253
1253
1254 if prev is None:
1254 if prev is None:
1255 return src
1255 return src
1256 src = prev
1256 src = prev
1257
1257
1258 o = set([_firstsrc(r) for r in dests])
1258 o = set([_firstsrc(r) for r in dests])
1259 o -= set([None])
1259 o -= set([None])
1260 return subset & o
1260 return subset & o
1261
1261
1262 def outgoing(repo, subset, x):
1262 def outgoing(repo, subset, x):
1263 """``outgoing([path])``
1263 """``outgoing([path])``
1264 Changesets not found in the specified destination repository, or the
1264 Changesets not found in the specified destination repository, or the
1265 default push location.
1265 default push location.
1266 """
1266 """
1267 import hg # avoid start-up nasties
1267 import hg # avoid start-up nasties
1268 # i18n: "outgoing" is a keyword
1268 # i18n: "outgoing" is a keyword
1269 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1269 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1270 # i18n: "outgoing" is a keyword
1270 # i18n: "outgoing" is a keyword
1271 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1271 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1272 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1272 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1273 dest, branches = hg.parseurl(dest)
1273 dest, branches = hg.parseurl(dest)
1274 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1274 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1275 if revs:
1275 if revs:
1276 revs = [repo.lookup(rev) for rev in revs]
1276 revs = [repo.lookup(rev) for rev in revs]
1277 other = hg.peer(repo, {}, dest)
1277 other = hg.peer(repo, {}, dest)
1278 repo.ui.pushbuffer()
1278 repo.ui.pushbuffer()
1279 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1279 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1280 repo.ui.popbuffer()
1280 repo.ui.popbuffer()
1281 cl = repo.changelog
1281 cl = repo.changelog
1282 o = set([cl.rev(r) for r in outgoing.missing])
1282 o = set([cl.rev(r) for r in outgoing.missing])
1283 return subset & o
1283 return subset & o
1284
1284
1285 def p1(repo, subset, x):
1285 def p1(repo, subset, x):
1286 """``p1([set])``
1286 """``p1([set])``
1287 First parent of changesets in set, or the working directory.
1287 First parent of changesets in set, or the working directory.
1288 """
1288 """
1289 if x is None:
1289 if x is None:
1290 p = repo[x].p1().rev()
1290 p = repo[x].p1().rev()
1291 if p >= 0:
1291 if p >= 0:
1292 return subset & baseset([p])
1292 return subset & baseset([p])
1293 return baseset()
1293 return baseset()
1294
1294
1295 ps = set()
1295 ps = set()
1296 cl = repo.changelog
1296 cl = repo.changelog
1297 for r in getset(repo, spanset(repo), x):
1297 for r in getset(repo, spanset(repo), x):
1298 ps.add(cl.parentrevs(r)[0])
1298 ps.add(cl.parentrevs(r)[0])
1299 ps -= set([node.nullrev])
1299 ps -= set([node.nullrev])
1300 return subset & ps
1300 return subset & ps
1301
1301
1302 def p2(repo, subset, x):
1302 def p2(repo, subset, x):
1303 """``p2([set])``
1303 """``p2([set])``
1304 Second parent of changesets in set, or the working directory.
1304 Second parent of changesets in set, or the working directory.
1305 """
1305 """
1306 if x is None:
1306 if x is None:
1307 ps = repo[x].parents()
1307 ps = repo[x].parents()
1308 try:
1308 try:
1309 p = ps[1].rev()
1309 p = ps[1].rev()
1310 if p >= 0:
1310 if p >= 0:
1311 return subset & baseset([p])
1311 return subset & baseset([p])
1312 return baseset()
1312 return baseset()
1313 except IndexError:
1313 except IndexError:
1314 return baseset()
1314 return baseset()
1315
1315
1316 ps = set()
1316 ps = set()
1317 cl = repo.changelog
1317 cl = repo.changelog
1318 for r in getset(repo, spanset(repo), x):
1318 for r in getset(repo, spanset(repo), x):
1319 ps.add(cl.parentrevs(r)[1])
1319 ps.add(cl.parentrevs(r)[1])
1320 ps -= set([node.nullrev])
1320 ps -= set([node.nullrev])
1321 return subset & ps
1321 return subset & ps
1322
1322
1323 def parents(repo, subset, x):
1323 def parents(repo, subset, x):
1324 """``parents([set])``
1324 """``parents([set])``
1325 The set of all parents for all changesets in set, or the working directory.
1325 The set of all parents for all changesets in set, or the working directory.
1326 """
1326 """
1327 if x is None:
1327 if x is None:
1328 ps = set(p.rev() for p in repo[x].parents())
1328 ps = set(p.rev() for p in repo[x].parents())
1329 else:
1329 else:
1330 ps = set()
1330 ps = set()
1331 cl = repo.changelog
1331 cl = repo.changelog
1332 for r in getset(repo, spanset(repo), x):
1332 for r in getset(repo, spanset(repo), x):
1333 ps.update(cl.parentrevs(r))
1333 ps.update(cl.parentrevs(r))
1334 ps -= set([node.nullrev])
1334 ps -= set([node.nullrev])
1335 return subset & ps
1335 return subset & ps
1336
1336
1337 def parentspec(repo, subset, x, n):
1337 def parentspec(repo, subset, x, n):
1338 """``set^0``
1338 """``set^0``
1339 The set.
1339 The set.
1340 ``set^1`` (or ``set^``), ``set^2``
1340 ``set^1`` (or ``set^``), ``set^2``
1341 First or second parent, respectively, of all changesets in set.
1341 First or second parent, respectively, of all changesets in set.
1342 """
1342 """
1343 try:
1343 try:
1344 n = int(n[1])
1344 n = int(n[1])
1345 if n not in (0, 1, 2):
1345 if n not in (0, 1, 2):
1346 raise ValueError
1346 raise ValueError
1347 except (TypeError, ValueError):
1347 except (TypeError, ValueError):
1348 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1348 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1349 ps = set()
1349 ps = set()
1350 cl = repo.changelog
1350 cl = repo.changelog
1351 for r in getset(repo, fullreposet(repo), x):
1351 for r in getset(repo, fullreposet(repo), x):
1352 if n == 0:
1352 if n == 0:
1353 ps.add(r)
1353 ps.add(r)
1354 elif n == 1:
1354 elif n == 1:
1355 ps.add(cl.parentrevs(r)[0])
1355 ps.add(cl.parentrevs(r)[0])
1356 elif n == 2:
1356 elif n == 2:
1357 parents = cl.parentrevs(r)
1357 parents = cl.parentrevs(r)
1358 if len(parents) > 1:
1358 if len(parents) > 1:
1359 ps.add(parents[1])
1359 ps.add(parents[1])
1360 return subset & ps
1360 return subset & ps
1361
1361
1362 def present(repo, subset, x):
1362 def present(repo, subset, x):
1363 """``present(set)``
1363 """``present(set)``
1364 An empty set, if any revision in set isn't found; otherwise,
1364 An empty set, if any revision in set isn't found; otherwise,
1365 all revisions in set.
1365 all revisions in set.
1366
1366
1367 If any of specified revisions is not present in the local repository,
1367 If any of specified revisions is not present in the local repository,
1368 the query is normally aborted. But this predicate allows the query
1368 the query is normally aborted. But this predicate allows the query
1369 to continue even in such cases.
1369 to continue even in such cases.
1370 """
1370 """
1371 try:
1371 try:
1372 return getset(repo, subset, x)
1372 return getset(repo, subset, x)
1373 except error.RepoLookupError:
1373 except error.RepoLookupError:
1374 return baseset()
1374 return baseset()
1375
1375
1376 def public(repo, subset, x):
1376 def public(repo, subset, x):
1377 """``public()``
1377 """``public()``
1378 Changeset in public phase."""
1378 Changeset in public phase."""
1379 # i18n: "public" is a keyword
1379 # i18n: "public" is a keyword
1380 getargs(x, 0, 0, _("public takes no arguments"))
1380 getargs(x, 0, 0, _("public takes no arguments"))
1381 phase = repo._phasecache.phase
1381 phase = repo._phasecache.phase
1382 target = phases.public
1382 target = phases.public
1383 condition = lambda r: phase(repo, r) == target
1383 condition = lambda r: phase(repo, r) == target
1384 return subset.filter(condition, cache=False)
1384 return subset.filter(condition, cache=False)
1385
1385
1386 def remote(repo, subset, x):
1386 def remote(repo, subset, x):
1387 """``remote([id [,path]])``
1387 """``remote([id [,path]])``
1388 Local revision that corresponds to the given identifier in a
1388 Local revision that corresponds to the given identifier in a
1389 remote repository, if present. Here, the '.' identifier is a
1389 remote repository, if present. Here, the '.' identifier is a
1390 synonym for the current local branch.
1390 synonym for the current local branch.
1391 """
1391 """
1392
1392
1393 import hg # avoid start-up nasties
1393 import hg # avoid start-up nasties
1394 # i18n: "remote" is a keyword
1394 # i18n: "remote" is a keyword
1395 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1395 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1396
1396
1397 q = '.'
1397 q = '.'
1398 if len(l) > 0:
1398 if len(l) > 0:
1399 # i18n: "remote" is a keyword
1399 # i18n: "remote" is a keyword
1400 q = getstring(l[0], _("remote requires a string id"))
1400 q = getstring(l[0], _("remote requires a string id"))
1401 if q == '.':
1401 if q == '.':
1402 q = repo['.'].branch()
1402 q = repo['.'].branch()
1403
1403
1404 dest = ''
1404 dest = ''
1405 if len(l) > 1:
1405 if len(l) > 1:
1406 # i18n: "remote" is a keyword
1406 # i18n: "remote" is a keyword
1407 dest = getstring(l[1], _("remote requires a repository path"))
1407 dest = getstring(l[1], _("remote requires a repository path"))
1408 dest = repo.ui.expandpath(dest or 'default')
1408 dest = repo.ui.expandpath(dest or 'default')
1409 dest, branches = hg.parseurl(dest)
1409 dest, branches = hg.parseurl(dest)
1410 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1410 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1411 if revs:
1411 if revs:
1412 revs = [repo.lookup(rev) for rev in revs]
1412 revs = [repo.lookup(rev) for rev in revs]
1413 other = hg.peer(repo, {}, dest)
1413 other = hg.peer(repo, {}, dest)
1414 n = other.lookup(q)
1414 n = other.lookup(q)
1415 if n in repo:
1415 if n in repo:
1416 r = repo[n].rev()
1416 r = repo[n].rev()
1417 if r in subset:
1417 if r in subset:
1418 return baseset([r])
1418 return baseset([r])
1419 return baseset()
1419 return baseset()
1420
1420
1421 def removes(repo, subset, x):
1421 def removes(repo, subset, x):
1422 """``removes(pattern)``
1422 """``removes(pattern)``
1423 Changesets which remove files matching pattern.
1423 Changesets which remove files matching pattern.
1424
1424
1425 The pattern without explicit kind like ``glob:`` is expected to be
1425 The pattern without explicit kind like ``glob:`` is expected to be
1426 relative to the current directory and match against a file or a
1426 relative to the current directory and match against a file or a
1427 directory.
1427 directory.
1428 """
1428 """
1429 # i18n: "removes" is a keyword
1429 # i18n: "removes" is a keyword
1430 pat = getstring(x, _("removes requires a pattern"))
1430 pat = getstring(x, _("removes requires a pattern"))
1431 return checkstatus(repo, subset, pat, 2)
1431 return checkstatus(repo, subset, pat, 2)
1432
1432
1433 def rev(repo, subset, x):
1433 def rev(repo, subset, x):
1434 """``rev(number)``
1434 """``rev(number)``
1435 Revision with the given numeric identifier.
1435 Revision with the given numeric identifier.
1436 """
1436 """
1437 # i18n: "rev" is a keyword
1437 # i18n: "rev" is a keyword
1438 l = getargs(x, 1, 1, _("rev requires one argument"))
1438 l = getargs(x, 1, 1, _("rev requires one argument"))
1439 try:
1439 try:
1440 # i18n: "rev" is a keyword
1440 # i18n: "rev" is a keyword
1441 l = int(getstring(l[0], _("rev requires a number")))
1441 l = int(getstring(l[0], _("rev requires a number")))
1442 except (TypeError, ValueError):
1442 except (TypeError, ValueError):
1443 # i18n: "rev" is a keyword
1443 # i18n: "rev" is a keyword
1444 raise error.ParseError(_("rev expects a number"))
1444 raise error.ParseError(_("rev expects a number"))
1445 if l not in fullreposet(repo):
1445 if l not in fullreposet(repo):
1446 return baseset()
1446 return baseset()
1447 return subset & baseset([l])
1447 return subset & baseset([l])
1448
1448
1449 def matching(repo, subset, x):
1449 def matching(repo, subset, x):
1450 """``matching(revision [, field])``
1450 """``matching(revision [, field])``
1451 Changesets in which a given set of fields match the set of fields in the
1451 Changesets in which a given set of fields match the set of fields in the
1452 selected revision or set.
1452 selected revision or set.
1453
1453
1454 To match more than one field pass the list of fields to match separated
1454 To match more than one field pass the list of fields to match separated
1455 by spaces (e.g. ``author description``).
1455 by spaces (e.g. ``author description``).
1456
1456
1457 Valid fields are most regular revision fields and some special fields.
1457 Valid fields are most regular revision fields and some special fields.
1458
1458
1459 Regular revision fields are ``description``, ``author``, ``branch``,
1459 Regular revision fields are ``description``, ``author``, ``branch``,
1460 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1460 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1461 and ``diff``.
1461 and ``diff``.
1462 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1462 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1463 contents of the revision. Two revisions matching their ``diff`` will
1463 contents of the revision. Two revisions matching their ``diff`` will
1464 also match their ``files``.
1464 also match their ``files``.
1465
1465
1466 Special fields are ``summary`` and ``metadata``:
1466 Special fields are ``summary`` and ``metadata``:
1467 ``summary`` matches the first line of the description.
1467 ``summary`` matches the first line of the description.
1468 ``metadata`` is equivalent to matching ``description user date``
1468 ``metadata`` is equivalent to matching ``description user date``
1469 (i.e. it matches the main metadata fields).
1469 (i.e. it matches the main metadata fields).
1470
1470
1471 ``metadata`` is the default field which is used when no fields are
1471 ``metadata`` is the default field which is used when no fields are
1472 specified. You can match more than one field at a time.
1472 specified. You can match more than one field at a time.
1473 """
1473 """
1474 # i18n: "matching" is a keyword
1474 # i18n: "matching" is a keyword
1475 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1475 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1476
1476
1477 revs = getset(repo, fullreposet(repo), l[0])
1477 revs = getset(repo, fullreposet(repo), l[0])
1478
1478
1479 fieldlist = ['metadata']
1479 fieldlist = ['metadata']
1480 if len(l) > 1:
1480 if len(l) > 1:
1481 fieldlist = getstring(l[1],
1481 fieldlist = getstring(l[1],
1482 # i18n: "matching" is a keyword
1482 # i18n: "matching" is a keyword
1483 _("matching requires a string "
1483 _("matching requires a string "
1484 "as its second argument")).split()
1484 "as its second argument")).split()
1485
1485
1486 # Make sure that there are no repeated fields,
1486 # Make sure that there are no repeated fields,
1487 # expand the 'special' 'metadata' field type
1487 # expand the 'special' 'metadata' field type
1488 # and check the 'files' whenever we check the 'diff'
1488 # and check the 'files' whenever we check the 'diff'
1489 fields = []
1489 fields = []
1490 for field in fieldlist:
1490 for field in fieldlist:
1491 if field == 'metadata':
1491 if field == 'metadata':
1492 fields += ['user', 'description', 'date']
1492 fields += ['user', 'description', 'date']
1493 elif field == 'diff':
1493 elif field == 'diff':
1494 # a revision matching the diff must also match the files
1494 # a revision matching the diff must also match the files
1495 # since matching the diff is very costly, make sure to
1495 # since matching the diff is very costly, make sure to
1496 # also match the files first
1496 # also match the files first
1497 fields += ['files', 'diff']
1497 fields += ['files', 'diff']
1498 else:
1498 else:
1499 if field == 'author':
1499 if field == 'author':
1500 field = 'user'
1500 field = 'user'
1501 fields.append(field)
1501 fields.append(field)
1502 fields = set(fields)
1502 fields = set(fields)
1503 if 'summary' in fields and 'description' in fields:
1503 if 'summary' in fields and 'description' in fields:
1504 # If a revision matches its description it also matches its summary
1504 # If a revision matches its description it also matches its summary
1505 fields.discard('summary')
1505 fields.discard('summary')
1506
1506
1507 # We may want to match more than one field
1507 # We may want to match more than one field
1508 # Not all fields take the same amount of time to be matched
1508 # Not all fields take the same amount of time to be matched
1509 # Sort the selected fields in order of increasing matching cost
1509 # Sort the selected fields in order of increasing matching cost
1510 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1510 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1511 'files', 'description', 'substate', 'diff']
1511 'files', 'description', 'substate', 'diff']
1512 def fieldkeyfunc(f):
1512 def fieldkeyfunc(f):
1513 try:
1513 try:
1514 return fieldorder.index(f)
1514 return fieldorder.index(f)
1515 except ValueError:
1515 except ValueError:
1516 # assume an unknown field is very costly
1516 # assume an unknown field is very costly
1517 return len(fieldorder)
1517 return len(fieldorder)
1518 fields = list(fields)
1518 fields = list(fields)
1519 fields.sort(key=fieldkeyfunc)
1519 fields.sort(key=fieldkeyfunc)
1520
1520
1521 # Each field will be matched with its own "getfield" function
1521 # Each field will be matched with its own "getfield" function
1522 # which will be added to the getfieldfuncs array of functions
1522 # which will be added to the getfieldfuncs array of functions
1523 getfieldfuncs = []
1523 getfieldfuncs = []
1524 _funcs = {
1524 _funcs = {
1525 'user': lambda r: repo[r].user(),
1525 'user': lambda r: repo[r].user(),
1526 'branch': lambda r: repo[r].branch(),
1526 'branch': lambda r: repo[r].branch(),
1527 'date': lambda r: repo[r].date(),
1527 'date': lambda r: repo[r].date(),
1528 'description': lambda r: repo[r].description(),
1528 'description': lambda r: repo[r].description(),
1529 'files': lambda r: repo[r].files(),
1529 'files': lambda r: repo[r].files(),
1530 'parents': lambda r: repo[r].parents(),
1530 'parents': lambda r: repo[r].parents(),
1531 'phase': lambda r: repo[r].phase(),
1531 'phase': lambda r: repo[r].phase(),
1532 'substate': lambda r: repo[r].substate,
1532 'substate': lambda r: repo[r].substate,
1533 'summary': lambda r: repo[r].description().splitlines()[0],
1533 'summary': lambda r: repo[r].description().splitlines()[0],
1534 'diff': lambda r: list(repo[r].diff(git=True),)
1534 'diff': lambda r: list(repo[r].diff(git=True),)
1535 }
1535 }
1536 for info in fields:
1536 for info in fields:
1537 getfield = _funcs.get(info, None)
1537 getfield = _funcs.get(info, None)
1538 if getfield is None:
1538 if getfield is None:
1539 raise error.ParseError(
1539 raise error.ParseError(
1540 # i18n: "matching" is a keyword
1540 # i18n: "matching" is a keyword
1541 _("unexpected field name passed to matching: %s") % info)
1541 _("unexpected field name passed to matching: %s") % info)
1542 getfieldfuncs.append(getfield)
1542 getfieldfuncs.append(getfield)
1543 # convert the getfield array of functions into a "getinfo" function
1543 # convert the getfield array of functions into a "getinfo" function
1544 # which returns an array of field values (or a single value if there
1544 # which returns an array of field values (or a single value if there
1545 # is only one field to match)
1545 # is only one field to match)
1546 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1546 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1547
1547
1548 def matches(x):
1548 def matches(x):
1549 for rev in revs:
1549 for rev in revs:
1550 target = getinfo(rev)
1550 target = getinfo(rev)
1551 match = True
1551 match = True
1552 for n, f in enumerate(getfieldfuncs):
1552 for n, f in enumerate(getfieldfuncs):
1553 if target[n] != f(x):
1553 if target[n] != f(x):
1554 match = False
1554 match = False
1555 if match:
1555 if match:
1556 return True
1556 return True
1557 return False
1557 return False
1558
1558
1559 return subset.filter(matches)
1559 return subset.filter(matches)
1560
1560
1561 def reverse(repo, subset, x):
1561 def reverse(repo, subset, x):
1562 """``reverse(set)``
1562 """``reverse(set)``
1563 Reverse order of set.
1563 Reverse order of set.
1564 """
1564 """
1565 l = getset(repo, subset, x)
1565 l = getset(repo, subset, x)
1566 l.reverse()
1566 l.reverse()
1567 return l
1567 return l
1568
1568
1569 def roots(repo, subset, x):
1569 def roots(repo, subset, x):
1570 """``roots(set)``
1570 """``roots(set)``
1571 Changesets in set with no parent changeset in set.
1571 Changesets in set with no parent changeset in set.
1572 """
1572 """
1573 s = getset(repo, spanset(repo), x)
1573 s = getset(repo, spanset(repo), x)
1574 subset = baseset([r for r in s if r in subset])
1574 subset = baseset([r for r in s if r in subset])
1575 cs = _children(repo, subset, s)
1575 cs = _children(repo, subset, s)
1576 return subset - cs
1576 return subset - cs
1577
1577
1578 def secret(repo, subset, x):
1578 def secret(repo, subset, x):
1579 """``secret()``
1579 """``secret()``
1580 Changeset in secret phase."""
1580 Changeset in secret phase."""
1581 # i18n: "secret" is a keyword
1581 # i18n: "secret" is a keyword
1582 getargs(x, 0, 0, _("secret takes no arguments"))
1582 getargs(x, 0, 0, _("secret takes no arguments"))
1583 phase = repo._phasecache.phase
1583 phase = repo._phasecache.phase
1584 target = phases.secret
1584 target = phases.secret
1585 condition = lambda r: phase(repo, r) == target
1585 condition = lambda r: phase(repo, r) == target
1586 return subset.filter(condition, cache=False)
1586 return subset.filter(condition, cache=False)
1587
1587
1588 def sort(repo, subset, x):
1588 def sort(repo, subset, x):
1589 """``sort(set[, [-]key...])``
1589 """``sort(set[, [-]key...])``
1590 Sort set by keys. The default sort order is ascending, specify a key
1590 Sort set by keys. The default sort order is ascending, specify a key
1591 as ``-key`` to sort in descending order.
1591 as ``-key`` to sort in descending order.
1592
1592
1593 The keys can be:
1593 The keys can be:
1594
1594
1595 - ``rev`` for the revision number,
1595 - ``rev`` for the revision number,
1596 - ``branch`` for the branch name,
1596 - ``branch`` for the branch name,
1597 - ``desc`` for the commit message (description),
1597 - ``desc`` for the commit message (description),
1598 - ``user`` for user name (``author`` can be used as an alias),
1598 - ``user`` for user name (``author`` can be used as an alias),
1599 - ``date`` for the commit date
1599 - ``date`` for the commit date
1600 """
1600 """
1601 # i18n: "sort" is a keyword
1601 # i18n: "sort" is a keyword
1602 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1602 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1603 keys = "rev"
1603 keys = "rev"
1604 if len(l) == 2:
1604 if len(l) == 2:
1605 # i18n: "sort" is a keyword
1605 # i18n: "sort" is a keyword
1606 keys = getstring(l[1], _("sort spec must be a string"))
1606 keys = getstring(l[1], _("sort spec must be a string"))
1607
1607
1608 s = l[0]
1608 s = l[0]
1609 keys = keys.split()
1609 keys = keys.split()
1610 l = []
1610 l = []
1611 def invert(s):
1611 def invert(s):
1612 return "".join(chr(255 - ord(c)) for c in s)
1612 return "".join(chr(255 - ord(c)) for c in s)
1613 revs = getset(repo, subset, s)
1613 revs = getset(repo, subset, s)
1614 if keys == ["rev"]:
1614 if keys == ["rev"]:
1615 revs.sort()
1615 revs.sort()
1616 return revs
1616 return revs
1617 elif keys == ["-rev"]:
1617 elif keys == ["-rev"]:
1618 revs.sort(reverse=True)
1618 revs.sort(reverse=True)
1619 return revs
1619 return revs
1620 for r in revs:
1620 for r in revs:
1621 c = repo[r]
1621 c = repo[r]
1622 e = []
1622 e = []
1623 for k in keys:
1623 for k in keys:
1624 if k == 'rev':
1624 if k == 'rev':
1625 e.append(r)
1625 e.append(r)
1626 elif k == '-rev':
1626 elif k == '-rev':
1627 e.append(-r)
1627 e.append(-r)
1628 elif k == 'branch':
1628 elif k == 'branch':
1629 e.append(c.branch())
1629 e.append(c.branch())
1630 elif k == '-branch':
1630 elif k == '-branch':
1631 e.append(invert(c.branch()))
1631 e.append(invert(c.branch()))
1632 elif k == 'desc':
1632 elif k == 'desc':
1633 e.append(c.description())
1633 e.append(c.description())
1634 elif k == '-desc':
1634 elif k == '-desc':
1635 e.append(invert(c.description()))
1635 e.append(invert(c.description()))
1636 elif k in 'user author':
1636 elif k in 'user author':
1637 e.append(c.user())
1637 e.append(c.user())
1638 elif k in '-user -author':
1638 elif k in '-user -author':
1639 e.append(invert(c.user()))
1639 e.append(invert(c.user()))
1640 elif k == 'date':
1640 elif k == 'date':
1641 e.append(c.date()[0])
1641 e.append(c.date()[0])
1642 elif k == '-date':
1642 elif k == '-date':
1643 e.append(-c.date()[0])
1643 e.append(-c.date()[0])
1644 else:
1644 else:
1645 raise error.ParseError(_("unknown sort key %r") % k)
1645 raise error.ParseError(_("unknown sort key %r") % k)
1646 e.append(r)
1646 e.append(r)
1647 l.append(e)
1647 l.append(e)
1648 l.sort()
1648 l.sort()
1649 return baseset([e[-1] for e in l])
1649 return baseset([e[-1] for e in l])
1650
1650
1651 def _stringmatcher(pattern):
1651 def _stringmatcher(pattern):
1652 """
1652 """
1653 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1653 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1654 returns the matcher name, pattern, and matcher function.
1654 returns the matcher name, pattern, and matcher function.
1655 missing or unknown prefixes are treated as literal matches.
1655 missing or unknown prefixes are treated as literal matches.
1656
1656
1657 helper for tests:
1657 helper for tests:
1658 >>> def test(pattern, *tests):
1658 >>> def test(pattern, *tests):
1659 ... kind, pattern, matcher = _stringmatcher(pattern)
1659 ... kind, pattern, matcher = _stringmatcher(pattern)
1660 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1660 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1661
1661
1662 exact matching (no prefix):
1662 exact matching (no prefix):
1663 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1663 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1664 ('literal', 'abcdefg', [False, False, True])
1664 ('literal', 'abcdefg', [False, False, True])
1665
1665
1666 regex matching ('re:' prefix)
1666 regex matching ('re:' prefix)
1667 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1667 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1668 ('re', 'a.+b', [False, False, True])
1668 ('re', 'a.+b', [False, False, True])
1669
1669
1670 force exact matches ('literal:' prefix)
1670 force exact matches ('literal:' prefix)
1671 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1671 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1672 ('literal', 're:foobar', [False, True])
1672 ('literal', 're:foobar', [False, True])
1673
1673
1674 unknown prefixes are ignored and treated as literals
1674 unknown prefixes are ignored and treated as literals
1675 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1675 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1676 ('literal', 'foo:bar', [False, False, True])
1676 ('literal', 'foo:bar', [False, False, True])
1677 """
1677 """
1678 if pattern.startswith('re:'):
1678 if pattern.startswith('re:'):
1679 pattern = pattern[3:]
1679 pattern = pattern[3:]
1680 try:
1680 try:
1681 regex = re.compile(pattern)
1681 regex = re.compile(pattern)
1682 except re.error, e:
1682 except re.error, e:
1683 raise error.ParseError(_('invalid regular expression: %s')
1683 raise error.ParseError(_('invalid regular expression: %s')
1684 % e)
1684 % e)
1685 return 're', pattern, regex.search
1685 return 're', pattern, regex.search
1686 elif pattern.startswith('literal:'):
1686 elif pattern.startswith('literal:'):
1687 pattern = pattern[8:]
1687 pattern = pattern[8:]
1688 return 'literal', pattern, pattern.__eq__
1688 return 'literal', pattern, pattern.__eq__
1689
1689
1690 def _substringmatcher(pattern):
1690 def _substringmatcher(pattern):
1691 kind, pattern, matcher = _stringmatcher(pattern)
1691 kind, pattern, matcher = _stringmatcher(pattern)
1692 if kind == 'literal':
1692 if kind == 'literal':
1693 matcher = lambda s: pattern in s
1693 matcher = lambda s: pattern in s
1694 return kind, pattern, matcher
1694 return kind, pattern, matcher
1695
1695
1696 def tag(repo, subset, x):
1696 def tag(repo, subset, x):
1697 """``tag([name])``
1697 """``tag([name])``
1698 The specified tag by name, or all tagged revisions if no name is given.
1698 The specified tag by name, or all tagged revisions if no name is given.
1699
1699
1700 If `name` starts with `re:`, the remainder of the name is treated as
1700 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:`,
1701 a regular expression. To match a tag that actually starts with `re:`,
1702 use the prefix `literal:`.
1702 use the prefix `literal:`.
1703 """
1703 """
1704 # i18n: "tag" is a keyword
1704 # i18n: "tag" is a keyword
1705 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1705 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1706 cl = repo.changelog
1706 cl = repo.changelog
1707 if args:
1707 if args:
1708 pattern = getstring(args[0],
1708 pattern = getstring(args[0],
1709 # i18n: "tag" is a keyword
1709 # i18n: "tag" is a keyword
1710 _('the argument to tag must be a string'))
1710 _('the argument to tag must be a string'))
1711 kind, pattern, matcher = _stringmatcher(pattern)
1711 kind, pattern, matcher = _stringmatcher(pattern)
1712 if kind == 'literal':
1712 if kind == 'literal':
1713 # avoid resolving all tags
1713 # avoid resolving all tags
1714 tn = repo._tagscache.tags.get(pattern, None)
1714 tn = repo._tagscache.tags.get(pattern, None)
1715 if tn is None:
1715 if tn is None:
1716 raise util.Abort(_("tag '%s' does not exist") % pattern)
1716 raise util.Abort(_("tag '%s' does not exist") % pattern)
1717 s = set([repo[tn].rev()])
1717 s = set([repo[tn].rev()])
1718 else:
1718 else:
1719 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1719 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1720 else:
1720 else:
1721 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1721 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1722 return subset & s
1722 return subset & s
1723
1723
1724 def tagged(repo, subset, x):
1724 def tagged(repo, subset, x):
1725 return tag(repo, subset, x)
1725 return tag(repo, subset, x)
1726
1726
1727 def unstable(repo, subset, x):
1727 def unstable(repo, subset, x):
1728 """``unstable()``
1728 """``unstable()``
1729 Non-obsolete changesets with obsolete ancestors.
1729 Non-obsolete changesets with obsolete ancestors.
1730 """
1730 """
1731 # i18n: "unstable" is a keyword
1731 # i18n: "unstable" is a keyword
1732 getargs(x, 0, 0, _("unstable takes no arguments"))
1732 getargs(x, 0, 0, _("unstable takes no arguments"))
1733 unstables = obsmod.getrevs(repo, 'unstable')
1733 unstables = obsmod.getrevs(repo, 'unstable')
1734 return subset & unstables
1734 return subset & unstables
1735
1735
1736
1736
1737 def user(repo, subset, x):
1737 def user(repo, subset, x):
1738 """``user(string)``
1738 """``user(string)``
1739 User name contains string. The match is case-insensitive.
1739 User name contains string. The match is case-insensitive.
1740
1740
1741 If `string` starts with `re:`, the remainder of the string is treated as
1741 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
1742 a regular expression. To match a user that actually contains `re:`, use
1743 the prefix `literal:`.
1743 the prefix `literal:`.
1744 """
1744 """
1745 return author(repo, subset, x)
1745 return author(repo, subset, x)
1746
1746
1747 # for internal use
1747 # for internal use
1748 def _list(repo, subset, x):
1748 def _list(repo, subset, x):
1749 s = getstring(x, "internal error")
1749 s = getstring(x, "internal error")
1750 if not s:
1750 if not s:
1751 return baseset()
1751 return baseset()
1752 ls = [repo[r].rev() for r in s.split('\0')]
1752 ls = [repo[r].rev() for r in s.split('\0')]
1753 s = subset
1753 s = subset
1754 return baseset([r for r in ls if r in s])
1754 return baseset([r for r in ls if r in s])
1755
1755
1756 # for internal use
1756 # for internal use
1757 def _intlist(repo, subset, x):
1757 def _intlist(repo, subset, x):
1758 s = getstring(x, "internal error")
1758 s = getstring(x, "internal error")
1759 if not s:
1759 if not s:
1760 return baseset()
1760 return baseset()
1761 ls = [int(r) for r in s.split('\0')]
1761 ls = [int(r) for r in s.split('\0')]
1762 s = subset
1762 s = subset
1763 return baseset([r for r in ls if r in s])
1763 return baseset([r for r in ls if r in s])
1764
1764
1765 # for internal use
1765 # for internal use
1766 def _hexlist(repo, subset, x):
1766 def _hexlist(repo, subset, x):
1767 s = getstring(x, "internal error")
1767 s = getstring(x, "internal error")
1768 if not s:
1768 if not s:
1769 return baseset()
1769 return baseset()
1770 cl = repo.changelog
1770 cl = repo.changelog
1771 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
1771 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
1772 s = subset
1772 s = subset
1773 return baseset([r for r in ls if r in s])
1773 return baseset([r for r in ls if r in s])
1774
1774
1775 symbols = {
1775 symbols = {
1776 "adds": adds,
1776 "adds": adds,
1777 "all": getall,
1777 "all": getall,
1778 "ancestor": ancestor,
1778 "ancestor": ancestor,
1779 "ancestors": ancestors,
1779 "ancestors": ancestors,
1780 "_firstancestors": _firstancestors,
1780 "_firstancestors": _firstancestors,
1781 "author": author,
1781 "author": author,
1782 "bisect": bisect,
1782 "bisect": bisect,
1783 "bisected": bisected,
1783 "bisected": bisected,
1784 "bookmark": bookmark,
1784 "bookmark": bookmark,
1785 "branch": branch,
1785 "branch": branch,
1786 "branchpoint": branchpoint,
1786 "branchpoint": branchpoint,
1787 "bumped": bumped,
1787 "bumped": bumped,
1788 "bundle": bundle,
1788 "bundle": bundle,
1789 "children": children,
1789 "children": children,
1790 "closed": closed,
1790 "closed": closed,
1791 "contains": contains,
1791 "contains": contains,
1792 "converted": converted,
1792 "converted": converted,
1793 "date": date,
1793 "date": date,
1794 "desc": desc,
1794 "desc": desc,
1795 "descendants": descendants,
1795 "descendants": descendants,
1796 "_firstdescendants": _firstdescendants,
1796 "_firstdescendants": _firstdescendants,
1797 "destination": destination,
1797 "destination": destination,
1798 "divergent": divergent,
1798 "divergent": divergent,
1799 "draft": draft,
1799 "draft": draft,
1800 "extinct": extinct,
1800 "extinct": extinct,
1801 "extra": extra,
1801 "extra": extra,
1802 "file": hasfile,
1802 "file": hasfile,
1803 "filelog": filelog,
1803 "filelog": filelog,
1804 "first": first,
1804 "first": first,
1805 "follow": follow,
1805 "follow": follow,
1806 "_followfirst": _followfirst,
1806 "_followfirst": _followfirst,
1807 "grep": grep,
1807 "grep": grep,
1808 "head": head,
1808 "head": head,
1809 "heads": heads,
1809 "heads": heads,
1810 "hidden": hidden,
1810 "hidden": hidden,
1811 "id": node_,
1811 "id": node_,
1812 "keyword": keyword,
1812 "keyword": keyword,
1813 "last": last,
1813 "last": last,
1814 "limit": limit,
1814 "limit": limit,
1815 "_matchfiles": _matchfiles,
1815 "_matchfiles": _matchfiles,
1816 "max": maxrev,
1816 "max": maxrev,
1817 "merge": merge,
1817 "merge": merge,
1818 "min": minrev,
1818 "min": minrev,
1819 "modifies": modifies,
1819 "modifies": modifies,
1820 "obsolete": obsolete,
1820 "obsolete": obsolete,
1821 "only": only,
1821 "only": only,
1822 "origin": origin,
1822 "origin": origin,
1823 "outgoing": outgoing,
1823 "outgoing": outgoing,
1824 "p1": p1,
1824 "p1": p1,
1825 "p2": p2,
1825 "p2": p2,
1826 "parents": parents,
1826 "parents": parents,
1827 "present": present,
1827 "present": present,
1828 "public": public,
1828 "public": public,
1829 "remote": remote,
1829 "remote": remote,
1830 "removes": removes,
1830 "removes": removes,
1831 "rev": rev,
1831 "rev": rev,
1832 "reverse": reverse,
1832 "reverse": reverse,
1833 "roots": roots,
1833 "roots": roots,
1834 "sort": sort,
1834 "sort": sort,
1835 "secret": secret,
1835 "secret": secret,
1836 "matching": matching,
1836 "matching": matching,
1837 "tag": tag,
1837 "tag": tag,
1838 "tagged": tagged,
1838 "tagged": tagged,
1839 "user": user,
1839 "user": user,
1840 "unstable": unstable,
1840 "unstable": unstable,
1841 "_list": _list,
1841 "_list": _list,
1842 "_intlist": _intlist,
1842 "_intlist": _intlist,
1843 "_hexlist": _hexlist,
1843 "_hexlist": _hexlist,
1844 }
1844 }
1845
1845
1846 # symbols which can't be used for a DoS attack for any given input
1846 # 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)
1847 # (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
1848 # functions that just return a lot of changesets (like all) don't count here
1849 safesymbols = set([
1849 safesymbols = set([
1850 "adds",
1850 "adds",
1851 "all",
1851 "all",
1852 "ancestor",
1852 "ancestor",
1853 "ancestors",
1853 "ancestors",
1854 "_firstancestors",
1854 "_firstancestors",
1855 "author",
1855 "author",
1856 "bisect",
1856 "bisect",
1857 "bisected",
1857 "bisected",
1858 "bookmark",
1858 "bookmark",
1859 "branch",
1859 "branch",
1860 "branchpoint",
1860 "branchpoint",
1861 "bumped",
1861 "bumped",
1862 "bundle",
1862 "bundle",
1863 "children",
1863 "children",
1864 "closed",
1864 "closed",
1865 "converted",
1865 "converted",
1866 "date",
1866 "date",
1867 "desc",
1867 "desc",
1868 "descendants",
1868 "descendants",
1869 "_firstdescendants",
1869 "_firstdescendants",
1870 "destination",
1870 "destination",
1871 "divergent",
1871 "divergent",
1872 "draft",
1872 "draft",
1873 "extinct",
1873 "extinct",
1874 "extra",
1874 "extra",
1875 "file",
1875 "file",
1876 "filelog",
1876 "filelog",
1877 "first",
1877 "first",
1878 "follow",
1878 "follow",
1879 "_followfirst",
1879 "_followfirst",
1880 "head",
1880 "head",
1881 "heads",
1881 "heads",
1882 "hidden",
1882 "hidden",
1883 "id",
1883 "id",
1884 "keyword",
1884 "keyword",
1885 "last",
1885 "last",
1886 "limit",
1886 "limit",
1887 "_matchfiles",
1887 "_matchfiles",
1888 "max",
1888 "max",
1889 "merge",
1889 "merge",
1890 "min",
1890 "min",
1891 "modifies",
1891 "modifies",
1892 "obsolete",
1892 "obsolete",
1893 "only",
1893 "only",
1894 "origin",
1894 "origin",
1895 "outgoing",
1895 "outgoing",
1896 "p1",
1896 "p1",
1897 "p2",
1897 "p2",
1898 "parents",
1898 "parents",
1899 "present",
1899 "present",
1900 "public",
1900 "public",
1901 "remote",
1901 "remote",
1902 "removes",
1902 "removes",
1903 "rev",
1903 "rev",
1904 "reverse",
1904 "reverse",
1905 "roots",
1905 "roots",
1906 "sort",
1906 "sort",
1907 "secret",
1907 "secret",
1908 "matching",
1908 "matching",
1909 "tag",
1909 "tag",
1910 "tagged",
1910 "tagged",
1911 "user",
1911 "user",
1912 "unstable",
1912 "unstable",
1913 "_list",
1913 "_list",
1914 "_intlist",
1914 "_intlist",
1915 "_hexlist",
1915 "_hexlist",
1916 ])
1916 ])
1917
1917
1918 methods = {
1918 methods = {
1919 "range": rangeset,
1919 "range": rangeset,
1920 "dagrange": dagrange,
1920 "dagrange": dagrange,
1921 "string": stringset,
1921 "string": stringset,
1922 "symbol": symbolset,
1922 "symbol": symbolset,
1923 "and": andset,
1923 "and": andset,
1924 "or": orset,
1924 "or": orset,
1925 "not": notset,
1925 "not": notset,
1926 "list": listset,
1926 "list": listset,
1927 "func": func,
1927 "func": func,
1928 "ancestor": ancestorspec,
1928 "ancestor": ancestorspec,
1929 "parent": parentspec,
1929 "parent": parentspec,
1930 "parentpost": p1,
1930 "parentpost": p1,
1931 "only": only,
1931 "only": only,
1932 "onlypost": only,
1932 "onlypost": only,
1933 }
1933 }
1934
1934
1935 def optimize(x, small):
1935 def optimize(x, small):
1936 if x is None:
1936 if x is None:
1937 return 0, x
1937 return 0, x
1938
1938
1939 smallbonus = 1
1939 smallbonus = 1
1940 if small:
1940 if small:
1941 smallbonus = .5
1941 smallbonus = .5
1942
1942
1943 op = x[0]
1943 op = x[0]
1944 if op == 'minus':
1944 if op == 'minus':
1945 return optimize(('and', x[1], ('not', x[2])), small)
1945 return optimize(('and', x[1], ('not', x[2])), small)
1946 elif op == 'only':
1946 elif op == 'only':
1947 return optimize(('func', ('symbol', 'only'),
1947 return optimize(('func', ('symbol', 'only'),
1948 ('list', x[1], x[2])), small)
1948 ('list', x[1], x[2])), small)
1949 elif op == 'dagrangepre':
1949 elif op == 'dagrangepre':
1950 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1950 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1951 elif op == 'dagrangepost':
1951 elif op == 'dagrangepost':
1952 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1952 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1953 elif op == 'rangepre':
1953 elif op == 'rangepre':
1954 return optimize(('range', ('string', '0'), x[1]), small)
1954 return optimize(('range', ('string', '0'), x[1]), small)
1955 elif op == 'rangepost':
1955 elif op == 'rangepost':
1956 return optimize(('range', x[1], ('string', 'tip')), small)
1956 return optimize(('range', x[1], ('string', 'tip')), small)
1957 elif op == 'negate':
1957 elif op == 'negate':
1958 return optimize(('string',
1958 return optimize(('string',
1959 '-' + getstring(x[1], _("can't negate that"))), small)
1959 '-' + getstring(x[1], _("can't negate that"))), small)
1960 elif op in 'string symbol negate':
1960 elif op in 'string symbol negate':
1961 return smallbonus, x # single revisions are small
1961 return smallbonus, x # single revisions are small
1962 elif op == 'and':
1962 elif op == 'and':
1963 wa, ta = optimize(x[1], True)
1963 wa, ta = optimize(x[1], True)
1964 wb, tb = optimize(x[2], True)
1964 wb, tb = optimize(x[2], True)
1965
1965
1966 # (::x and not ::y)/(not ::y and ::x) have a fast path
1966 # (::x and not ::y)/(not ::y and ::x) have a fast path
1967 def isonly(revs, bases):
1967 def isonly(revs, bases):
1968 return (
1968 return (
1969 revs[0] == 'func'
1969 revs[0] == 'func'
1970 and getstring(revs[1], _('not a symbol')) == 'ancestors'
1970 and getstring(revs[1], _('not a symbol')) == 'ancestors'
1971 and bases[0] == 'not'
1971 and bases[0] == 'not'
1972 and bases[1][0] == 'func'
1972 and bases[1][0] == 'func'
1973 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
1973 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
1974
1974
1975 w = min(wa, wb)
1975 w = min(wa, wb)
1976 if isonly(ta, tb):
1976 if isonly(ta, tb):
1977 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
1977 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
1978 if isonly(tb, ta):
1978 if isonly(tb, ta):
1979 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
1979 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
1980
1980
1981 if wa > wb:
1981 if wa > wb:
1982 return w, (op, tb, ta)
1982 return w, (op, tb, ta)
1983 return w, (op, ta, tb)
1983 return w, (op, ta, tb)
1984 elif op == 'or':
1984 elif op == 'or':
1985 wa, ta = optimize(x[1], False)
1985 wa, ta = optimize(x[1], False)
1986 wb, tb = optimize(x[2], False)
1986 wb, tb = optimize(x[2], False)
1987 if wb < wa:
1987 if wb < wa:
1988 wb, wa = wa, wb
1988 wb, wa = wa, wb
1989 return max(wa, wb), (op, ta, tb)
1989 return max(wa, wb), (op, ta, tb)
1990 elif op == 'not':
1990 elif op == 'not':
1991 o = optimize(x[1], not small)
1991 o = optimize(x[1], not small)
1992 return o[0], (op, o[1])
1992 return o[0], (op, o[1])
1993 elif op == 'parentpost':
1993 elif op == 'parentpost':
1994 o = optimize(x[1], small)
1994 o = optimize(x[1], small)
1995 return o[0], (op, o[1])
1995 return o[0], (op, o[1])
1996 elif op == 'group':
1996 elif op == 'group':
1997 return optimize(x[1], small)
1997 return optimize(x[1], small)
1998 elif op in 'dagrange range list parent ancestorspec':
1998 elif op in 'dagrange range list parent ancestorspec':
1999 if op == 'parent':
1999 if op == 'parent':
2000 # x^:y means (x^) : y, not x ^ (:y)
2000 # x^:y means (x^) : y, not x ^ (:y)
2001 post = ('parentpost', x[1])
2001 post = ('parentpost', x[1])
2002 if x[2][0] == 'dagrangepre':
2002 if x[2][0] == 'dagrangepre':
2003 return optimize(('dagrange', post, x[2][1]), small)
2003 return optimize(('dagrange', post, x[2][1]), small)
2004 elif x[2][0] == 'rangepre':
2004 elif x[2][0] == 'rangepre':
2005 return optimize(('range', post, x[2][1]), small)
2005 return optimize(('range', post, x[2][1]), small)
2006
2006
2007 wa, ta = optimize(x[1], small)
2007 wa, ta = optimize(x[1], small)
2008 wb, tb = optimize(x[2], small)
2008 wb, tb = optimize(x[2], small)
2009 return wa + wb, (op, ta, tb)
2009 return wa + wb, (op, ta, tb)
2010 elif op == 'func':
2010 elif op == 'func':
2011 f = getstring(x[1], _("not a symbol"))
2011 f = getstring(x[1], _("not a symbol"))
2012 wa, ta = optimize(x[2], small)
2012 wa, ta = optimize(x[2], small)
2013 if f in ("author branch closed date desc file grep keyword "
2013 if f in ("author branch closed date desc file grep keyword "
2014 "outgoing user"):
2014 "outgoing user"):
2015 w = 10 # slow
2015 w = 10 # slow
2016 elif f in "modifies adds removes":
2016 elif f in "modifies adds removes":
2017 w = 30 # slower
2017 w = 30 # slower
2018 elif f == "contains":
2018 elif f == "contains":
2019 w = 100 # very slow
2019 w = 100 # very slow
2020 elif f == "ancestor":
2020 elif f == "ancestor":
2021 w = 1 * smallbonus
2021 w = 1 * smallbonus
2022 elif f in "reverse limit first _intlist":
2022 elif f in "reverse limit first _intlist":
2023 w = 0
2023 w = 0
2024 elif f in "sort":
2024 elif f in "sort":
2025 w = 10 # assume most sorts look at changelog
2025 w = 10 # assume most sorts look at changelog
2026 else:
2026 else:
2027 w = 1
2027 w = 1
2028 return w + wa, (op, x[1], ta)
2028 return w + wa, (op, x[1], ta)
2029 return 1, x
2029 return 1, x
2030
2030
2031 _aliasarg = ('func', ('symbol', '_aliasarg'))
2031 _aliasarg = ('func', ('symbol', '_aliasarg'))
2032 def _getaliasarg(tree):
2032 def _getaliasarg(tree):
2033 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
2033 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
2034 return X, None otherwise.
2034 return X, None otherwise.
2035 """
2035 """
2036 if (len(tree) == 3 and tree[:2] == _aliasarg
2036 if (len(tree) == 3 and tree[:2] == _aliasarg
2037 and tree[2][0] == 'string'):
2037 and tree[2][0] == 'string'):
2038 return tree[2][1]
2038 return tree[2][1]
2039 return None
2039 return None
2040
2040
2041 def _checkaliasarg(tree, known=None):
2041 def _checkaliasarg(tree, known=None):
2042 """Check tree contains no _aliasarg construct or only ones which
2042 """Check tree contains no _aliasarg construct or only ones which
2043 value is in known. Used to avoid alias placeholders injection.
2043 value is in known. Used to avoid alias placeholders injection.
2044 """
2044 """
2045 if isinstance(tree, tuple):
2045 if isinstance(tree, tuple):
2046 arg = _getaliasarg(tree)
2046 arg = _getaliasarg(tree)
2047 if arg is not None and (not known or arg not in known):
2047 if arg is not None and (not known or arg not in known):
2048 raise error.ParseError(_("not a function: %s") % '_aliasarg')
2048 raise error.ParseError(_("not a function: %s") % '_aliasarg')
2049 for t in tree:
2049 for t in tree:
2050 _checkaliasarg(t, known)
2050 _checkaliasarg(t, known)
2051
2051
2052 class revsetalias(object):
2052 class revsetalias(object):
2053 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
2053 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
2054 args = None
2054 args = None
2055
2055
2056 # error message at parsing, or None
2056 # error message at parsing, or None
2057 error = None
2057 error = None
2058 # whether own `error` information is already shown or not.
2058 # whether own `error` information is already shown or not.
2059 # this avoids showing same warning multiple times at each `findaliases`.
2059 # this avoids showing same warning multiple times at each `findaliases`.
2060 warned = False
2060 warned = False
2061
2061
2062 def __init__(self, name, value):
2062 def __init__(self, name, value):
2063 '''Aliases like:
2063 '''Aliases like:
2064
2064
2065 h = heads(default)
2065 h = heads(default)
2066 b($1) = ancestors($1) - ancestors(default)
2066 b($1) = ancestors($1) - ancestors(default)
2067 '''
2067 '''
2068 m = self.funcre.search(name)
2068 m = self.funcre.search(name)
2069 if m:
2069 if m:
2070 self.name = m.group(1)
2070 self.name = m.group(1)
2071 self.tree = ('func', ('symbol', m.group(1)))
2071 self.tree = ('func', ('symbol', m.group(1)))
2072 self.args = [x.strip() for x in m.group(2).split(',')]
2072 self.args = [x.strip() for x in m.group(2).split(',')]
2073 for arg in self.args:
2073 for arg in self.args:
2074 # _aliasarg() is an unknown symbol only used separate
2074 # _aliasarg() is an unknown symbol only used separate
2075 # alias argument placeholders from regular strings.
2075 # alias argument placeholders from regular strings.
2076 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
2076 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
2077 else:
2077 else:
2078 self.name = name
2078 self.name = name
2079 self.tree = ('symbol', name)
2079 self.tree = ('symbol', name)
2080
2080
2081 try:
2081 try:
2082 self.replacement, pos = parse(value)
2082 self.replacement, pos = parse(value)
2083 if pos != len(value):
2083 if pos != len(value):
2084 raise error.ParseError(_('invalid token'), pos)
2084 raise error.ParseError(_('invalid token'), pos)
2085 # Check for placeholder injection
2085 # Check for placeholder injection
2086 _checkaliasarg(self.replacement, self.args)
2086 _checkaliasarg(self.replacement, self.args)
2087 except error.ParseError, inst:
2087 except error.ParseError, inst:
2088 if len(inst.args) > 1:
2088 if len(inst.args) > 1:
2089 self.error = _('at %s: %s') % (inst.args[1], inst.args[0])
2089 self.error = _('at %s: %s') % (inst.args[1], inst.args[0])
2090 else:
2090 else:
2091 self.error = inst.args[0]
2091 self.error = inst.args[0]
2092
2092
2093 def _getalias(aliases, tree):
2093 def _getalias(aliases, tree):
2094 """If tree looks like an unexpanded alias, return it. Return None
2094 """If tree looks like an unexpanded alias, return it. Return None
2095 otherwise.
2095 otherwise.
2096 """
2096 """
2097 if isinstance(tree, tuple) and tree:
2097 if isinstance(tree, tuple) and tree:
2098 if tree[0] == 'symbol' and len(tree) == 2:
2098 if tree[0] == 'symbol' and len(tree) == 2:
2099 name = tree[1]
2099 name = tree[1]
2100 alias = aliases.get(name)
2100 alias = aliases.get(name)
2101 if alias and alias.args is None and alias.tree == tree:
2101 if alias and alias.args is None and alias.tree == tree:
2102 return alias
2102 return alias
2103 if tree[0] == 'func' and len(tree) > 1:
2103 if tree[0] == 'func' and len(tree) > 1:
2104 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
2104 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
2105 name = tree[1][1]
2105 name = tree[1][1]
2106 alias = aliases.get(name)
2106 alias = aliases.get(name)
2107 if alias and alias.args is not None and alias.tree == tree[:2]:
2107 if alias and alias.args is not None and alias.tree == tree[:2]:
2108 return alias
2108 return alias
2109 return None
2109 return None
2110
2110
2111 def _expandargs(tree, args):
2111 def _expandargs(tree, args):
2112 """Replace _aliasarg instances with the substitution value of the
2112 """Replace _aliasarg instances with the substitution value of the
2113 same name in args, recursively.
2113 same name in args, recursively.
2114 """
2114 """
2115 if not tree or not isinstance(tree, tuple):
2115 if not tree or not isinstance(tree, tuple):
2116 return tree
2116 return tree
2117 arg = _getaliasarg(tree)
2117 arg = _getaliasarg(tree)
2118 if arg is not None:
2118 if arg is not None:
2119 return args[arg]
2119 return args[arg]
2120 return tuple(_expandargs(t, args) for t in tree)
2120 return tuple(_expandargs(t, args) for t in tree)
2121
2121
2122 def _expandaliases(aliases, tree, expanding, cache):
2122 def _expandaliases(aliases, tree, expanding, cache):
2123 """Expand aliases in tree, recursively.
2123 """Expand aliases in tree, recursively.
2124
2124
2125 'aliases' is a dictionary mapping user defined aliases to
2125 'aliases' is a dictionary mapping user defined aliases to
2126 revsetalias objects.
2126 revsetalias objects.
2127 """
2127 """
2128 if not isinstance(tree, tuple):
2128 if not isinstance(tree, tuple):
2129 # Do not expand raw strings
2129 # Do not expand raw strings
2130 return tree
2130 return tree
2131 alias = _getalias(aliases, tree)
2131 alias = _getalias(aliases, tree)
2132 if alias is not None:
2132 if alias is not None:
2133 if alias.error:
2133 if alias.error:
2134 raise util.Abort(_('failed to parse revset alias "%s": %s') %
2134 raise util.Abort(_('failed to parse revset alias "%s": %s') %
2135 (alias.name, alias.error))
2135 (alias.name, alias.error))
2136 if alias in expanding:
2136 if alias in expanding:
2137 raise error.ParseError(_('infinite expansion of revset alias "%s" '
2137 raise error.ParseError(_('infinite expansion of revset alias "%s" '
2138 'detected') % alias.name)
2138 'detected') % alias.name)
2139 expanding.append(alias)
2139 expanding.append(alias)
2140 if alias.name not in cache:
2140 if alias.name not in cache:
2141 cache[alias.name] = _expandaliases(aliases, alias.replacement,
2141 cache[alias.name] = _expandaliases(aliases, alias.replacement,
2142 expanding, cache)
2142 expanding, cache)
2143 result = cache[alias.name]
2143 result = cache[alias.name]
2144 expanding.pop()
2144 expanding.pop()
2145 if alias.args is not None:
2145 if alias.args is not None:
2146 l = getlist(tree[2])
2146 l = getlist(tree[2])
2147 if len(l) != len(alias.args):
2147 if len(l) != len(alias.args):
2148 raise error.ParseError(
2148 raise error.ParseError(
2149 _('invalid number of arguments: %s') % len(l))
2149 _('invalid number of arguments: %s') % len(l))
2150 l = [_expandaliases(aliases, a, [], cache) for a in l]
2150 l = [_expandaliases(aliases, a, [], cache) for a in l]
2151 result = _expandargs(result, dict(zip(alias.args, l)))
2151 result = _expandargs(result, dict(zip(alias.args, l)))
2152 else:
2152 else:
2153 result = tuple(_expandaliases(aliases, t, expanding, cache)
2153 result = tuple(_expandaliases(aliases, t, expanding, cache)
2154 for t in tree)
2154 for t in tree)
2155 return result
2155 return result
2156
2156
2157 def findaliases(ui, tree, showwarning=None):
2157 def findaliases(ui, tree, showwarning=None):
2158 _checkaliasarg(tree)
2158 _checkaliasarg(tree)
2159 aliases = {}
2159 aliases = {}
2160 for k, v in ui.configitems('revsetalias'):
2160 for k, v in ui.configitems('revsetalias'):
2161 alias = revsetalias(k, v)
2161 alias = revsetalias(k, v)
2162 aliases[alias.name] = alias
2162 aliases[alias.name] = alias
2163 tree = _expandaliases(aliases, tree, [], {})
2163 tree = _expandaliases(aliases, tree, [], {})
2164 if showwarning:
2164 if showwarning:
2165 # warn about problematic (but not referred) aliases
2165 # warn about problematic (but not referred) aliases
2166 for name, alias in sorted(aliases.iteritems()):
2166 for name, alias in sorted(aliases.iteritems()):
2167 if alias.error and not alias.warned:
2167 if alias.error and not alias.warned:
2168 msg = _('failed to parse revset alias "%s": %s'
2168 msg = _('failed to parse revset alias "%s": %s'
2169 ) % (name, alias.error)
2169 ) % (name, alias.error)
2170 showwarning(_('warning: %s\n') % (msg))
2170 showwarning(_('warning: %s\n') % (msg))
2171 alias.warned = True
2171 alias.warned = True
2172 return tree
2172 return tree
2173
2173
2174 def foldconcat(tree):
2174 def foldconcat(tree):
2175 """Fold elements to be concatenated by `##`
2175 """Fold elements to be concatenated by `##`
2176 """
2176 """
2177 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2177 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2178 return tree
2178 return tree
2179 if tree[0] == '_concat':
2179 if tree[0] == '_concat':
2180 pending = [tree]
2180 pending = [tree]
2181 l = []
2181 l = []
2182 while pending:
2182 while pending:
2183 e = pending.pop()
2183 e = pending.pop()
2184 if e[0] == '_concat':
2184 if e[0] == '_concat':
2185 pending.extend(reversed(e[1:]))
2185 pending.extend(reversed(e[1:]))
2186 elif e[0] in ('string', 'symbol'):
2186 elif e[0] in ('string', 'symbol'):
2187 l.append(e[1])
2187 l.append(e[1])
2188 else:
2188 else:
2189 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2189 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2190 raise error.ParseError(msg)
2190 raise error.ParseError(msg)
2191 return ('string', ''.join(l))
2191 return ('string', ''.join(l))
2192 else:
2192 else:
2193 return tuple(foldconcat(t) for t in tree)
2193 return tuple(foldconcat(t) for t in tree)
2194
2194
2195 def parse(spec, lookup=None):
2195 def parse(spec, lookup=None):
2196 p = parser.parser(tokenize, elements)
2196 p = parser.parser(tokenize, elements)
2197 return p.parse(spec, lookup=lookup)
2197 return p.parse(spec, lookup=lookup)
2198
2198
2199 def match(ui, spec, repo=None):
2199 def match(ui, spec, repo=None):
2200 if not spec:
2200 if not spec:
2201 raise error.ParseError(_("empty query"))
2201 raise error.ParseError(_("empty query"))
2202 lookup = None
2202 lookup = None
2203 if repo:
2203 if repo:
2204 lookup = repo.__contains__
2204 lookup = repo.__contains__
2205 tree, pos = parse(spec, lookup)
2205 tree, pos = parse(spec, lookup)
2206 if (pos != len(spec)):
2206 if (pos != len(spec)):
2207 raise error.ParseError(_("invalid token"), pos)
2207 raise error.ParseError(_("invalid token"), pos)
2208 if ui:
2208 if ui:
2209 tree = findaliases(ui, tree, showwarning=ui.warn)
2209 tree = findaliases(ui, tree, showwarning=ui.warn)
2210 tree = foldconcat(tree)
2210 tree = foldconcat(tree)
2211 weight, tree = optimize(tree, True)
2211 weight, tree = optimize(tree, True)
2212 def mfunc(repo, subset):
2212 def mfunc(repo, subset):
2213 if util.safehasattr(subset, 'isascending'):
2213 if util.safehasattr(subset, 'isascending'):
2214 result = getset(repo, subset, tree)
2214 result = getset(repo, subset, tree)
2215 else:
2215 else:
2216 result = getset(repo, baseset(subset), tree)
2216 result = getset(repo, baseset(subset), tree)
2217 return result
2217 return result
2218 return mfunc
2218 return mfunc
2219
2219
2220 def formatspec(expr, *args):
2220 def formatspec(expr, *args):
2221 '''
2221 '''
2222 This is a convenience function for using revsets internally, and
2222 This is a convenience function for using revsets internally, and
2223 escapes arguments appropriately. Aliases are intentionally ignored
2223 escapes arguments appropriately. Aliases are intentionally ignored
2224 so that intended expression behavior isn't accidentally subverted.
2224 so that intended expression behavior isn't accidentally subverted.
2225
2225
2226 Supported arguments:
2226 Supported arguments:
2227
2227
2228 %r = revset expression, parenthesized
2228 %r = revset expression, parenthesized
2229 %d = int(arg), no quoting
2229 %d = int(arg), no quoting
2230 %s = string(arg), escaped and single-quoted
2230 %s = string(arg), escaped and single-quoted
2231 %b = arg.branch(), escaped and single-quoted
2231 %b = arg.branch(), escaped and single-quoted
2232 %n = hex(arg), single-quoted
2232 %n = hex(arg), single-quoted
2233 %% = a literal '%'
2233 %% = a literal '%'
2234
2234
2235 Prefixing the type with 'l' specifies a parenthesized list of that type.
2235 Prefixing the type with 'l' specifies a parenthesized list of that type.
2236
2236
2237 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2237 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2238 '(10 or 11):: and ((this()) or (that()))'
2238 '(10 or 11):: and ((this()) or (that()))'
2239 >>> formatspec('%d:: and not %d::', 10, 20)
2239 >>> formatspec('%d:: and not %d::', 10, 20)
2240 '10:: and not 20::'
2240 '10:: and not 20::'
2241 >>> formatspec('%ld or %ld', [], [1])
2241 >>> formatspec('%ld or %ld', [], [1])
2242 "_list('') or 1"
2242 "_list('') or 1"
2243 >>> formatspec('keyword(%s)', 'foo\\xe9')
2243 >>> formatspec('keyword(%s)', 'foo\\xe9')
2244 "keyword('foo\\\\xe9')"
2244 "keyword('foo\\\\xe9')"
2245 >>> b = lambda: 'default'
2245 >>> b = lambda: 'default'
2246 >>> b.branch = b
2246 >>> b.branch = b
2247 >>> formatspec('branch(%b)', b)
2247 >>> formatspec('branch(%b)', b)
2248 "branch('default')"
2248 "branch('default')"
2249 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2249 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2250 "root(_list('a\\x00b\\x00c\\x00d'))"
2250 "root(_list('a\\x00b\\x00c\\x00d'))"
2251 '''
2251 '''
2252
2252
2253 def quote(s):
2253 def quote(s):
2254 return repr(str(s))
2254 return repr(str(s))
2255
2255
2256 def argtype(c, arg):
2256 def argtype(c, arg):
2257 if c == 'd':
2257 if c == 'd':
2258 return str(int(arg))
2258 return str(int(arg))
2259 elif c == 's':
2259 elif c == 's':
2260 return quote(arg)
2260 return quote(arg)
2261 elif c == 'r':
2261 elif c == 'r':
2262 parse(arg) # make sure syntax errors are confined
2262 parse(arg) # make sure syntax errors are confined
2263 return '(%s)' % arg
2263 return '(%s)' % arg
2264 elif c == 'n':
2264 elif c == 'n':
2265 return quote(node.hex(arg))
2265 return quote(node.hex(arg))
2266 elif c == 'b':
2266 elif c == 'b':
2267 return quote(arg.branch())
2267 return quote(arg.branch())
2268
2268
2269 def listexp(s, t):
2269 def listexp(s, t):
2270 l = len(s)
2270 l = len(s)
2271 if l == 0:
2271 if l == 0:
2272 return "_list('')"
2272 return "_list('')"
2273 elif l == 1:
2273 elif l == 1:
2274 return argtype(t, s[0])
2274 return argtype(t, s[0])
2275 elif t == 'd':
2275 elif t == 'd':
2276 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2276 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2277 elif t == 's':
2277 elif t == 's':
2278 return "_list('%s')" % "\0".join(s)
2278 return "_list('%s')" % "\0".join(s)
2279 elif t == 'n':
2279 elif t == 'n':
2280 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2280 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2281 elif t == 'b':
2281 elif t == 'b':
2282 return "_list('%s')" % "\0".join(a.branch() for a in s)
2282 return "_list('%s')" % "\0".join(a.branch() for a in s)
2283
2283
2284 m = l // 2
2284 m = l // 2
2285 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2285 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2286
2286
2287 ret = ''
2287 ret = ''
2288 pos = 0
2288 pos = 0
2289 arg = 0
2289 arg = 0
2290 while pos < len(expr):
2290 while pos < len(expr):
2291 c = expr[pos]
2291 c = expr[pos]
2292 if c == '%':
2292 if c == '%':
2293 pos += 1
2293 pos += 1
2294 d = expr[pos]
2294 d = expr[pos]
2295 if d == '%':
2295 if d == '%':
2296 ret += d
2296 ret += d
2297 elif d in 'dsnbr':
2297 elif d in 'dsnbr':
2298 ret += argtype(d, args[arg])
2298 ret += argtype(d, args[arg])
2299 arg += 1
2299 arg += 1
2300 elif d == 'l':
2300 elif d == 'l':
2301 # a list of some type
2301 # a list of some type
2302 pos += 1
2302 pos += 1
2303 d = expr[pos]
2303 d = expr[pos]
2304 ret += listexp(list(args[arg]), d)
2304 ret += listexp(list(args[arg]), d)
2305 arg += 1
2305 arg += 1
2306 else:
2306 else:
2307 raise util.Abort('unexpected revspec format character %s' % d)
2307 raise util.Abort('unexpected revspec format character %s' % d)
2308 else:
2308 else:
2309 ret += c
2309 ret += c
2310 pos += 1
2310 pos += 1
2311
2311
2312 return ret
2312 return ret
2313
2313
2314 def prettyformat(tree):
2314 def prettyformat(tree):
2315 def _prettyformat(tree, level, lines):
2315 def _prettyformat(tree, level, lines):
2316 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2316 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2317 lines.append((level, str(tree)))
2317 lines.append((level, str(tree)))
2318 else:
2318 else:
2319 lines.append((level, '(%s' % tree[0]))
2319 lines.append((level, '(%s' % tree[0]))
2320 for s in tree[1:]:
2320 for s in tree[1:]:
2321 _prettyformat(s, level + 1, lines)
2321 _prettyformat(s, level + 1, lines)
2322 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
2322 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
2323
2323
2324 lines = []
2324 lines = []
2325 _prettyformat(tree, 0, lines)
2325 _prettyformat(tree, 0, lines)
2326 output = '\n'.join((' '*l + s) for l, s in lines)
2326 output = '\n'.join((' '*l + s) for l, s in lines)
2327 return output
2327 return output
2328
2328
2329 def depth(tree):
2329 def depth(tree):
2330 if isinstance(tree, tuple):
2330 if isinstance(tree, tuple):
2331 return max(map(depth, tree)) + 1
2331 return max(map(depth, tree)) + 1
2332 else:
2332 else:
2333 return 0
2333 return 0
2334
2334
2335 def funcsused(tree):
2335 def funcsused(tree):
2336 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2336 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2337 return set()
2337 return set()
2338 else:
2338 else:
2339 funcs = set()
2339 funcs = set()
2340 for s in tree[1:]:
2340 for s in tree[1:]:
2341 funcs |= funcsused(s)
2341 funcs |= funcsused(s)
2342 if tree[0] == 'func':
2342 if tree[0] == 'func':
2343 funcs.add(tree[1][1])
2343 funcs.add(tree[1][1])
2344 return funcs
2344 return funcs
2345
2345
2346 class abstractsmartset(object):
2346 class abstractsmartset(object):
2347
2347
2348 def __nonzero__(self):
2348 def __nonzero__(self):
2349 """True if the smartset is not empty"""
2349 """True if the smartset is not empty"""
2350 raise NotImplementedError()
2350 raise NotImplementedError()
2351
2351
2352 def __contains__(self, rev):
2352 def __contains__(self, rev):
2353 """provide fast membership testing"""
2353 """provide fast membership testing"""
2354 raise NotImplementedError()
2354 raise NotImplementedError()
2355
2355
2356 def __iter__(self):
2356 def __iter__(self):
2357 """iterate the set in the order it is supposed to be iterated"""
2357 """iterate the set in the order it is supposed to be iterated"""
2358 raise NotImplementedError()
2358 raise NotImplementedError()
2359
2359
2360 # Attributes containing a function to perform a fast iteration in a given
2360 # Attributes containing a function to perform a fast iteration in a given
2361 # direction. A smartset can have none, one, or both defined.
2361 # direction. A smartset can have none, one, or both defined.
2362 #
2362 #
2363 # Default value is None instead of a function returning None to avoid
2363 # Default value is None instead of a function returning None to avoid
2364 # initializing an iterator just for testing if a fast method exists.
2364 # initializing an iterator just for testing if a fast method exists.
2365 fastasc = None
2365 fastasc = None
2366 fastdesc = None
2366 fastdesc = None
2367
2367
2368 def isascending(self):
2368 def isascending(self):
2369 """True if the set will iterate in ascending order"""
2369 """True if the set will iterate in ascending order"""
2370 raise NotImplementedError()
2370 raise NotImplementedError()
2371
2371
2372 def isdescending(self):
2372 def isdescending(self):
2373 """True if the set will iterate in descending order"""
2373 """True if the set will iterate in descending order"""
2374 raise NotImplementedError()
2374 raise NotImplementedError()
2375
2375
2376 def min(self):
2376 def min(self):
2377 """return the minimum element in the set"""
2377 """return the minimum element in the set"""
2378 if self.fastasc is not None:
2378 if self.fastasc is not None:
2379 for r in self.fastasc():
2379 for r in self.fastasc():
2380 return r
2380 return r
2381 raise ValueError('arg is an empty sequence')
2381 raise ValueError('arg is an empty sequence')
2382 return min(self)
2382 return min(self)
2383
2383
2384 def max(self):
2384 def max(self):
2385 """return the maximum element in the set"""
2385 """return the maximum element in the set"""
2386 if self.fastdesc is not None:
2386 if self.fastdesc is not None:
2387 for r in self.fastdesc():
2387 for r in self.fastdesc():
2388 return r
2388 return r
2389 raise ValueError('arg is an empty sequence')
2389 raise ValueError('arg is an empty sequence')
2390 return max(self)
2390 return max(self)
2391
2391
2392 def first(self):
2392 def first(self):
2393 """return the first element in the set (user iteration perspective)
2393 """return the first element in the set (user iteration perspective)
2394
2394
2395 Return None if the set is empty"""
2395 Return None if the set is empty"""
2396 raise NotImplementedError()
2396 raise NotImplementedError()
2397
2397
2398 def last(self):
2398 def last(self):
2399 """return the last element in the set (user iteration perspective)
2399 """return the last element in the set (user iteration perspective)
2400
2400
2401 Return None if the set is empty"""
2401 Return None if the set is empty"""
2402 raise NotImplementedError()
2402 raise NotImplementedError()
2403
2403
2404 def __len__(self):
2404 def __len__(self):
2405 """return the length of the smartsets
2405 """return the length of the smartsets
2406
2406
2407 This can be expensive on smartset that could be lazy otherwise."""
2407 This can be expensive on smartset that could be lazy otherwise."""
2408 raise NotImplementedError()
2408 raise NotImplementedError()
2409
2409
2410 def reverse(self):
2410 def reverse(self):
2411 """reverse the expected iteration order"""
2411 """reverse the expected iteration order"""
2412 raise NotImplementedError()
2412 raise NotImplementedError()
2413
2413
2414 def sort(self, reverse=True):
2414 def sort(self, reverse=True):
2415 """get the set to iterate in an ascending or descending order"""
2415 """get the set to iterate in an ascending or descending order"""
2416 raise NotImplementedError()
2416 raise NotImplementedError()
2417
2417
2418 def __and__(self, other):
2418 def __and__(self, other):
2419 """Returns a new object with the intersection of the two collections.
2419 """Returns a new object with the intersection of the two collections.
2420
2420
2421 This is part of the mandatory API for smartset."""
2421 This is part of the mandatory API for smartset."""
2422 return self.filter(other.__contains__, cache=False)
2422 return self.filter(other.__contains__, cache=False)
2423
2423
2424 def __add__(self, other):
2424 def __add__(self, other):
2425 """Returns a new object with the union of the two collections.
2425 """Returns a new object with the union of the two collections.
2426
2426
2427 This is part of the mandatory API for smartset."""
2427 This is part of the mandatory API for smartset."""
2428 return addset(self, other)
2428 return addset(self, other)
2429
2429
2430 def __sub__(self, other):
2430 def __sub__(self, other):
2431 """Returns a new object with the substraction of the two collections.
2431 """Returns a new object with the substraction of the two collections.
2432
2432
2433 This is part of the mandatory API for smartset."""
2433 This is part of the mandatory API for smartset."""
2434 c = other.__contains__
2434 c = other.__contains__
2435 return self.filter(lambda r: not c(r), cache=False)
2435 return self.filter(lambda r: not c(r), cache=False)
2436
2436
2437 def filter(self, condition, cache=True):
2437 def filter(self, condition, cache=True):
2438 """Returns this smartset filtered by condition as a new smartset.
2438 """Returns this smartset filtered by condition as a new smartset.
2439
2439
2440 `condition` is a callable which takes a revision number and returns a
2440 `condition` is a callable which takes a revision number and returns a
2441 boolean.
2441 boolean.
2442
2442
2443 This is part of the mandatory API for smartset."""
2443 This is part of the mandatory API for smartset."""
2444 # builtin cannot be cached. but do not needs to
2444 # builtin cannot be cached. but do not needs to
2445 if cache and util.safehasattr(condition, 'func_code'):
2445 if cache and util.safehasattr(condition, 'func_code'):
2446 condition = util.cachefunc(condition)
2446 condition = util.cachefunc(condition)
2447 return filteredset(self, condition)
2447 return filteredset(self, condition)
2448
2448
2449 class baseset(abstractsmartset):
2449 class baseset(abstractsmartset):
2450 """Basic data structure that represents a revset and contains the basic
2450 """Basic data structure that represents a revset and contains the basic
2451 operation that it should be able to perform.
2451 operation that it should be able to perform.
2452
2452
2453 Every method in this class should be implemented by any smartset class.
2453 Every method in this class should be implemented by any smartset class.
2454 """
2454 """
2455 def __init__(self, data=()):
2455 def __init__(self, data=()):
2456 if not isinstance(data, list):
2456 if not isinstance(data, list):
2457 data = list(data)
2457 data = list(data)
2458 self._list = data
2458 self._list = data
2459 self._ascending = None
2459 self._ascending = None
2460
2460
2461 @util.propertycache
2461 @util.propertycache
2462 def _set(self):
2462 def _set(self):
2463 return set(self._list)
2463 return set(self._list)
2464
2464
2465 @util.propertycache
2465 @util.propertycache
2466 def _asclist(self):
2466 def _asclist(self):
2467 asclist = self._list[:]
2467 asclist = self._list[:]
2468 asclist.sort()
2468 asclist.sort()
2469 return asclist
2469 return asclist
2470
2470
2471 def __iter__(self):
2471 def __iter__(self):
2472 if self._ascending is None:
2472 if self._ascending is None:
2473 return iter(self._list)
2473 return iter(self._list)
2474 elif self._ascending:
2474 elif self._ascending:
2475 return iter(self._asclist)
2475 return iter(self._asclist)
2476 else:
2476 else:
2477 return reversed(self._asclist)
2477 return reversed(self._asclist)
2478
2478
2479 def fastasc(self):
2479 def fastasc(self):
2480 return iter(self._asclist)
2480 return iter(self._asclist)
2481
2481
2482 def fastdesc(self):
2482 def fastdesc(self):
2483 return reversed(self._asclist)
2483 return reversed(self._asclist)
2484
2484
2485 @util.propertycache
2485 @util.propertycache
2486 def __contains__(self):
2486 def __contains__(self):
2487 return self._set.__contains__
2487 return self._set.__contains__
2488
2488
2489 def __nonzero__(self):
2489 def __nonzero__(self):
2490 return bool(self._list)
2490 return bool(self._list)
2491
2491
2492 def sort(self, reverse=False):
2492 def sort(self, reverse=False):
2493 self._ascending = not bool(reverse)
2493 self._ascending = not bool(reverse)
2494
2494
2495 def reverse(self):
2495 def reverse(self):
2496 if self._ascending is None:
2496 if self._ascending is None:
2497 self._list.reverse()
2497 self._list.reverse()
2498 else:
2498 else:
2499 self._ascending = not self._ascending
2499 self._ascending = not self._ascending
2500
2500
2501 def __len__(self):
2501 def __len__(self):
2502 return len(self._list)
2502 return len(self._list)
2503
2503
2504 def isascending(self):
2504 def isascending(self):
2505 """Returns True if the collection is ascending order, False if not.
2505 """Returns True if the collection is ascending order, False if not.
2506
2506
2507 This is part of the mandatory API for smartset."""
2507 This is part of the mandatory API for smartset."""
2508 if len(self) <= 1:
2508 if len(self) <= 1:
2509 return True
2509 return True
2510 return self._ascending is not None and self._ascending
2510 return self._ascending is not None and self._ascending
2511
2511
2512 def isdescending(self):
2512 def isdescending(self):
2513 """Returns True if the collection is descending order, False if not.
2513 """Returns True if the collection is descending order, False if not.
2514
2514
2515 This is part of the mandatory API for smartset."""
2515 This is part of the mandatory API for smartset."""
2516 if len(self) <= 1:
2516 if len(self) <= 1:
2517 return True
2517 return True
2518 return self._ascending is not None and not self._ascending
2518 return self._ascending is not None and not self._ascending
2519
2519
2520 def first(self):
2520 def first(self):
2521 if self:
2521 if self:
2522 if self._ascending is None:
2522 if self._ascending is None:
2523 return self._list[0]
2523 return self._list[0]
2524 elif self._ascending:
2524 elif self._ascending:
2525 return self._asclist[0]
2525 return self._asclist[0]
2526 else:
2526 else:
2527 return self._asclist[-1]
2527 return self._asclist[-1]
2528 return None
2528 return None
2529
2529
2530 def last(self):
2530 def last(self):
2531 if self:
2531 if self:
2532 if self._ascending is None:
2532 if self._ascending is None:
2533 return self._list[-1]
2533 return self._list[-1]
2534 elif self._ascending:
2534 elif self._ascending:
2535 return self._asclist[-1]
2535 return self._asclist[-1]
2536 else:
2536 else:
2537 return self._asclist[0]
2537 return self._asclist[0]
2538 return None
2538 return None
2539
2539
2540 class filteredset(abstractsmartset):
2540 class filteredset(abstractsmartset):
2541 """Duck type for baseset class which iterates lazily over the revisions in
2541 """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
2542 the subset and contains a function which tests for membership in the
2543 revset
2543 revset
2544 """
2544 """
2545 def __init__(self, subset, condition=lambda x: True):
2545 def __init__(self, subset, condition=lambda x: True):
2546 """
2546 """
2547 condition: a function that decide whether a revision in the subset
2547 condition: a function that decide whether a revision in the subset
2548 belongs to the revset or not.
2548 belongs to the revset or not.
2549 """
2549 """
2550 self._subset = subset
2550 self._subset = subset
2551 self._condition = condition
2551 self._condition = condition
2552 self._cache = {}
2552 self._cache = {}
2553
2553
2554 def __contains__(self, x):
2554 def __contains__(self, x):
2555 c = self._cache
2555 c = self._cache
2556 if x not in c:
2556 if x not in c:
2557 v = c[x] = x in self._subset and self._condition(x)
2557 v = c[x] = x in self._subset and self._condition(x)
2558 return v
2558 return v
2559 return c[x]
2559 return c[x]
2560
2560
2561 def __iter__(self):
2561 def __iter__(self):
2562 return self._iterfilter(self._subset)
2562 return self._iterfilter(self._subset)
2563
2563
2564 def _iterfilter(self, it):
2564 def _iterfilter(self, it):
2565 cond = self._condition
2565 cond = self._condition
2566 for x in it:
2566 for x in it:
2567 if cond(x):
2567 if cond(x):
2568 yield x
2568 yield x
2569
2569
2570 @property
2570 @property
2571 def fastasc(self):
2571 def fastasc(self):
2572 it = self._subset.fastasc
2572 it = self._subset.fastasc
2573 if it is None:
2573 if it is None:
2574 return None
2574 return None
2575 return lambda: self._iterfilter(it())
2575 return lambda: self._iterfilter(it())
2576
2576
2577 @property
2577 @property
2578 def fastdesc(self):
2578 def fastdesc(self):
2579 it = self._subset.fastdesc
2579 it = self._subset.fastdesc
2580 if it is None:
2580 if it is None:
2581 return None
2581 return None
2582 return lambda: self._iterfilter(it())
2582 return lambda: self._iterfilter(it())
2583
2583
2584 def __nonzero__(self):
2584 def __nonzero__(self):
2585 for r in self:
2585 for r in self:
2586 return True
2586 return True
2587 return False
2587 return False
2588
2588
2589 def __len__(self):
2589 def __len__(self):
2590 # Basic implementation to be changed in future patches.
2590 # Basic implementation to be changed in future patches.
2591 l = baseset([r for r in self])
2591 l = baseset([r for r in self])
2592 return len(l)
2592 return len(l)
2593
2593
2594 def sort(self, reverse=False):
2594 def sort(self, reverse=False):
2595 self._subset.sort(reverse=reverse)
2595 self._subset.sort(reverse=reverse)
2596
2596
2597 def reverse(self):
2597 def reverse(self):
2598 self._subset.reverse()
2598 self._subset.reverse()
2599
2599
2600 def isascending(self):
2600 def isascending(self):
2601 return self._subset.isascending()
2601 return self._subset.isascending()
2602
2602
2603 def isdescending(self):
2603 def isdescending(self):
2604 return self._subset.isdescending()
2604 return self._subset.isdescending()
2605
2605
2606 def first(self):
2606 def first(self):
2607 for x in self:
2607 for x in self:
2608 return x
2608 return x
2609 return None
2609 return None
2610
2610
2611 def last(self):
2611 def last(self):
2612 it = None
2612 it = None
2613 if self._subset.isascending:
2613 if self._subset.isascending:
2614 it = self.fastdesc
2614 it = self.fastdesc
2615 elif self._subset.isdescending:
2615 elif self._subset.isdescending:
2616 it = self.fastdesc
2616 it = self.fastdesc
2617 if it is None:
2617 if it is None:
2618 # slowly consume everything. This needs improvement
2618 # slowly consume everything. This needs improvement
2619 it = lambda: reversed(list(self))
2619 it = lambda: reversed(list(self))
2620 for x in it():
2620 for x in it():
2621 return x
2621 return x
2622 return None
2622 return None
2623
2623
2624 class addset(abstractsmartset):
2624 class addset(abstractsmartset):
2625 """Represent the addition of two sets
2625 """Represent the addition of two sets
2626
2626
2627 Wrapper structure for lazily adding two structures without losing much
2627 Wrapper structure for lazily adding two structures without losing much
2628 performance on the __contains__ method
2628 performance on the __contains__ method
2629
2629
2630 If the ascending attribute is set, that means the two structures are
2630 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
2631 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
2632 them maintaining the order by iterating over both at the same time
2633 """
2633 """
2634 def __init__(self, revs1, revs2, ascending=None):
2634 def __init__(self, revs1, revs2, ascending=None):
2635 self._r1 = revs1
2635 self._r1 = revs1
2636 self._r2 = revs2
2636 self._r2 = revs2
2637 self._iter = None
2637 self._iter = None
2638 self._ascending = ascending
2638 self._ascending = ascending
2639 self._genlist = None
2639 self._genlist = None
2640 self._asclist = None
2640 self._asclist = None
2641
2641
2642 def __len__(self):
2642 def __len__(self):
2643 return len(self._list)
2643 return len(self._list)
2644
2644
2645 def __nonzero__(self):
2645 def __nonzero__(self):
2646 return bool(self._r1) or bool(self._r2)
2646 return bool(self._r1) or bool(self._r2)
2647
2647
2648 @util.propertycache
2648 @util.propertycache
2649 def _list(self):
2649 def _list(self):
2650 if not self._genlist:
2650 if not self._genlist:
2651 self._genlist = baseset(self._iterator())
2651 self._genlist = baseset(self._iterator())
2652 return self._genlist
2652 return self._genlist
2653
2653
2654 def _iterator(self):
2654 def _iterator(self):
2655 """Iterate over both collections without repeating elements
2655 """Iterate over both collections without repeating elements
2656
2656
2657 If the ascending attribute is not set, iterate over the first one and
2657 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
2658 then over the second one checking for membership on the first one so we
2659 dont yield any duplicates.
2659 dont yield any duplicates.
2660
2660
2661 If the ascending attribute is set, iterate over both collections at the
2661 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.
2662 same time, yielding only one value at a time in the given order.
2663 """
2663 """
2664 if self._ascending is None:
2664 if self._ascending is None:
2665 def gen():
2665 def gen():
2666 for r in self._r1:
2666 for r in self._r1:
2667 yield r
2667 yield r
2668 inr1 = self._r1.__contains__
2668 inr1 = self._r1.__contains__
2669 for r in self._r2:
2669 for r in self._r2:
2670 if not inr1(r):
2670 if not inr1(r):
2671 yield r
2671 yield r
2672 gen = gen()
2672 gen = gen()
2673 else:
2673 else:
2674 iter1 = iter(self._r1)
2674 iter1 = iter(self._r1)
2675 iter2 = iter(self._r2)
2675 iter2 = iter(self._r2)
2676 gen = self._iterordered(self._ascending, iter1, iter2)
2676 gen = self._iterordered(self._ascending, iter1, iter2)
2677 return gen
2677 return gen
2678
2678
2679 def __iter__(self):
2679 def __iter__(self):
2680 if self._ascending is None:
2680 if self._ascending is None:
2681 if self._genlist:
2681 if self._genlist:
2682 return iter(self._genlist)
2682 return iter(self._genlist)
2683 return iter(self._iterator())
2683 return iter(self._iterator())
2684 self._trysetasclist()
2684 self._trysetasclist()
2685 if self._ascending:
2685 if self._ascending:
2686 it = self.fastasc
2686 it = self.fastasc
2687 else:
2687 else:
2688 it = self.fastdesc
2688 it = self.fastdesc
2689 if it is None:
2689 if it is None:
2690 # consume the gen and try again
2690 # consume the gen and try again
2691 self._list
2691 self._list
2692 return iter(self)
2692 return iter(self)
2693 return it()
2693 return it()
2694
2694
2695 def _trysetasclist(self):
2695 def _trysetasclist(self):
2696 """populate the _asclist attribute if possible and necessary"""
2696 """populate the _asclist attribute if possible and necessary"""
2697 if self._genlist is not None and self._asclist is None:
2697 if self._genlist is not None and self._asclist is None:
2698 self._asclist = sorted(self._genlist)
2698 self._asclist = sorted(self._genlist)
2699
2699
2700 @property
2700 @property
2701 def fastasc(self):
2701 def fastasc(self):
2702 self._trysetasclist()
2702 self._trysetasclist()
2703 if self._asclist is not None:
2703 if self._asclist is not None:
2704 return self._asclist.__iter__
2704 return self._asclist.__iter__
2705 iter1 = self._r1.fastasc
2705 iter1 = self._r1.fastasc
2706 iter2 = self._r2.fastasc
2706 iter2 = self._r2.fastasc
2707 if None in (iter1, iter2):
2707 if None in (iter1, iter2):
2708 return None
2708 return None
2709 return lambda: self._iterordered(True, iter1(), iter2())
2709 return lambda: self._iterordered(True, iter1(), iter2())
2710
2710
2711 @property
2711 @property
2712 def fastdesc(self):
2712 def fastdesc(self):
2713 self._trysetasclist()
2713 self._trysetasclist()
2714 if self._asclist is not None:
2714 if self._asclist is not None:
2715 return self._asclist.__reversed__
2715 return self._asclist.__reversed__
2716 iter1 = self._r1.fastdesc
2716 iter1 = self._r1.fastdesc
2717 iter2 = self._r2.fastdesc
2717 iter2 = self._r2.fastdesc
2718 if None in (iter1, iter2):
2718 if None in (iter1, iter2):
2719 return None
2719 return None
2720 return lambda: self._iterordered(False, iter1(), iter2())
2720 return lambda: self._iterordered(False, iter1(), iter2())
2721
2721
2722 def _iterordered(self, ascending, iter1, iter2):
2722 def _iterordered(self, ascending, iter1, iter2):
2723 """produce an ordered iteration from two iterators with the same order
2723 """produce an ordered iteration from two iterators with the same order
2724
2724
2725 The ascending is used to indicated the iteration direction.
2725 The ascending is used to indicated the iteration direction.
2726 """
2726 """
2727 choice = max
2727 choice = max
2728 if ascending:
2728 if ascending:
2729 choice = min
2729 choice = min
2730
2730
2731 val1 = None
2731 val1 = None
2732 val2 = None
2732 val2 = None
2733
2733
2734 choice = max
2734 choice = max
2735 if ascending:
2735 if ascending:
2736 choice = min
2736 choice = min
2737 try:
2737 try:
2738 # Consume both iterators in an ordered way until one is
2738 # Consume both iterators in an ordered way until one is
2739 # empty
2739 # empty
2740 while True:
2740 while True:
2741 if val1 is None:
2741 if val1 is None:
2742 val1 = iter1.next()
2742 val1 = iter1.next()
2743 if val2 is None:
2743 if val2 is None:
2744 val2 = iter2.next()
2744 val2 = iter2.next()
2745 next = choice(val1, val2)
2745 next = choice(val1, val2)
2746 yield next
2746 yield next
2747 if val1 == next:
2747 if val1 == next:
2748 val1 = None
2748 val1 = None
2749 if val2 == next:
2749 if val2 == next:
2750 val2 = None
2750 val2 = None
2751 except StopIteration:
2751 except StopIteration:
2752 # Flush any remaining values and consume the other one
2752 # Flush any remaining values and consume the other one
2753 it = iter2
2753 it = iter2
2754 if val1 is not None:
2754 if val1 is not None:
2755 yield val1
2755 yield val1
2756 it = iter1
2756 it = iter1
2757 elif val2 is not None:
2757 elif val2 is not None:
2758 # might have been equality and both are empty
2758 # might have been equality and both are empty
2759 yield val2
2759 yield val2
2760 for val in it:
2760 for val in it:
2761 yield val
2761 yield val
2762
2762
2763 def __contains__(self, x):
2763 def __contains__(self, x):
2764 return x in self._r1 or x in self._r2
2764 return x in self._r1 or x in self._r2
2765
2765
2766 def sort(self, reverse=False):
2766 def sort(self, reverse=False):
2767 """Sort the added set
2767 """Sort the added set
2768
2768
2769 For this we use the cached list with all the generated values and if we
2769 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.
2770 know they are ascending or descending we can sort them in a smart way.
2771 """
2771 """
2772 self._ascending = not reverse
2772 self._ascending = not reverse
2773
2773
2774 def isascending(self):
2774 def isascending(self):
2775 return self._ascending is not None and self._ascending
2775 return self._ascending is not None and self._ascending
2776
2776
2777 def isdescending(self):
2777 def isdescending(self):
2778 return self._ascending is not None and not self._ascending
2778 return self._ascending is not None and not self._ascending
2779
2779
2780 def reverse(self):
2780 def reverse(self):
2781 if self._ascending is None:
2781 if self._ascending is None:
2782 self._list.reverse()
2782 self._list.reverse()
2783 else:
2783 else:
2784 self._ascending = not self._ascending
2784 self._ascending = not self._ascending
2785
2785
2786 def first(self):
2786 def first(self):
2787 for x in self:
2787 for x in self:
2788 return x
2788 return x
2789 return None
2789 return None
2790
2790
2791 def last(self):
2791 def last(self):
2792 self.reverse()
2792 self.reverse()
2793 val = self.first()
2793 val = self.first()
2794 self.reverse()
2794 self.reverse()
2795 return val
2795 return val
2796
2796
2797 class generatorset(abstractsmartset):
2797 class generatorset(abstractsmartset):
2798 """Wrap a generator for lazy iteration
2798 """Wrap a generator for lazy iteration
2799
2799
2800 Wrapper structure for generators that provides lazy membership and can
2800 Wrapper structure for generators that provides lazy membership and can
2801 be iterated more than once.
2801 be iterated more than once.
2802 When asked for membership it generates values until either it finds the
2802 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
2803 requested one or has gone through all the elements in the generator
2804 """
2804 """
2805 def __init__(self, gen, iterasc=None):
2805 def __init__(self, gen, iterasc=None):
2806 """
2806 """
2807 gen: a generator producing the values for the generatorset.
2807 gen: a generator producing the values for the generatorset.
2808 """
2808 """
2809 self._gen = gen
2809 self._gen = gen
2810 self._asclist = None
2810 self._asclist = None
2811 self._cache = {}
2811 self._cache = {}
2812 self._genlist = []
2812 self._genlist = []
2813 self._finished = False
2813 self._finished = False
2814 self._ascending = True
2814 self._ascending = True
2815 if iterasc is not None:
2815 if iterasc is not None:
2816 if iterasc:
2816 if iterasc:
2817 self.fastasc = self._iterator
2817 self.fastasc = self._iterator
2818 self.__contains__ = self._asccontains
2818 self.__contains__ = self._asccontains
2819 else:
2819 else:
2820 self.fastdesc = self._iterator
2820 self.fastdesc = self._iterator
2821 self.__contains__ = self._desccontains
2821 self.__contains__ = self._desccontains
2822
2822
2823 def __nonzero__(self):
2823 def __nonzero__(self):
2824 for r in self:
2824 for r in self:
2825 return True
2825 return True
2826 return False
2826 return False
2827
2827
2828 def __contains__(self, x):
2828 def __contains__(self, x):
2829 if x in self._cache:
2829 if x in self._cache:
2830 return self._cache[x]
2830 return self._cache[x]
2831
2831
2832 # Use new values only, as existing values would be cached.
2832 # Use new values only, as existing values would be cached.
2833 for l in self._consumegen():
2833 for l in self._consumegen():
2834 if l == x:
2834 if l == x:
2835 return True
2835 return True
2836
2836
2837 self._cache[x] = False
2837 self._cache[x] = False
2838 return False
2838 return False
2839
2839
2840 def _asccontains(self, x):
2840 def _asccontains(self, x):
2841 """version of contains optimised for ascending generator"""
2841 """version of contains optimised for ascending generator"""
2842 if x in self._cache:
2842 if x in self._cache:
2843 return self._cache[x]
2843 return self._cache[x]
2844
2844
2845 # Use new values only, as existing values would be cached.
2845 # Use new values only, as existing values would be cached.
2846 for l in self._consumegen():
2846 for l in self._consumegen():
2847 if l == x:
2847 if l == x:
2848 return True
2848 return True
2849 if l > x:
2849 if l > x:
2850 break
2850 break
2851
2851
2852 self._cache[x] = False
2852 self._cache[x] = False
2853 return False
2853 return False
2854
2854
2855 def _desccontains(self, x):
2855 def _desccontains(self, x):
2856 """version of contains optimised for descending generator"""
2856 """version of contains optimised for descending generator"""
2857 if x in self._cache:
2857 if x in self._cache:
2858 return self._cache[x]
2858 return self._cache[x]
2859
2859
2860 # Use new values only, as existing values would be cached.
2860 # Use new values only, as existing values would be cached.
2861 for l in self._consumegen():
2861 for l in self._consumegen():
2862 if l == x:
2862 if l == x:
2863 return True
2863 return True
2864 if l < x:
2864 if l < x:
2865 break
2865 break
2866
2866
2867 self._cache[x] = False
2867 self._cache[x] = False
2868 return False
2868 return False
2869
2869
2870 def __iter__(self):
2870 def __iter__(self):
2871 if self._ascending:
2871 if self._ascending:
2872 it = self.fastasc
2872 it = self.fastasc
2873 else:
2873 else:
2874 it = self.fastdesc
2874 it = self.fastdesc
2875 if it is not None:
2875 if it is not None:
2876 return it()
2876 return it()
2877 # we need to consume the iterator
2877 # we need to consume the iterator
2878 for x in self._consumegen():
2878 for x in self._consumegen():
2879 pass
2879 pass
2880 # recall the same code
2880 # recall the same code
2881 return iter(self)
2881 return iter(self)
2882
2882
2883 def _iterator(self):
2883 def _iterator(self):
2884 if self._finished:
2884 if self._finished:
2885 return iter(self._genlist)
2885 return iter(self._genlist)
2886
2886
2887 # We have to use this complex iteration strategy to allow multiple
2887 # 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
2888 # iterations at the same time. We need to be able to catch revision
2889 # removed from _consumegen and added to genlist in another instance.
2889 # removed from _consumegen and added to genlist in another instance.
2890 #
2890 #
2891 # Getting rid of it would provide an about 15% speed up on this
2891 # Getting rid of it would provide an about 15% speed up on this
2892 # iteration.
2892 # iteration.
2893 genlist = self._genlist
2893 genlist = self._genlist
2894 nextrev = self._consumegen().next
2894 nextrev = self._consumegen().next
2895 _len = len # cache global lookup
2895 _len = len # cache global lookup
2896 def gen():
2896 def gen():
2897 i = 0
2897 i = 0
2898 while True:
2898 while True:
2899 if i < _len(genlist):
2899 if i < _len(genlist):
2900 yield genlist[i]
2900 yield genlist[i]
2901 else:
2901 else:
2902 yield nextrev()
2902 yield nextrev()
2903 i += 1
2903 i += 1
2904 return gen()
2904 return gen()
2905
2905
2906 def _consumegen(self):
2906 def _consumegen(self):
2907 cache = self._cache
2907 cache = self._cache
2908 genlist = self._genlist.append
2908 genlist = self._genlist.append
2909 for item in self._gen:
2909 for item in self._gen:
2910 cache[item] = True
2910 cache[item] = True
2911 genlist(item)
2911 genlist(item)
2912 yield item
2912 yield item
2913 if not self._finished:
2913 if not self._finished:
2914 self._finished = True
2914 self._finished = True
2915 asc = self._genlist[:]
2915 asc = self._genlist[:]
2916 asc.sort()
2916 asc.sort()
2917 self._asclist = asc
2917 self._asclist = asc
2918 self.fastasc = asc.__iter__
2918 self.fastasc = asc.__iter__
2919 self.fastdesc = asc.__reversed__
2919 self.fastdesc = asc.__reversed__
2920
2920
2921 def __len__(self):
2921 def __len__(self):
2922 for x in self._consumegen():
2922 for x in self._consumegen():
2923 pass
2923 pass
2924 return len(self._genlist)
2924 return len(self._genlist)
2925
2925
2926 def sort(self, reverse=False):
2926 def sort(self, reverse=False):
2927 self._ascending = not reverse
2927 self._ascending = not reverse
2928
2928
2929 def reverse(self):
2929 def reverse(self):
2930 self._ascending = not self._ascending
2930 self._ascending = not self._ascending
2931
2931
2932 def isascending(self):
2932 def isascending(self):
2933 return self._ascending
2933 return self._ascending
2934
2934
2935 def isdescending(self):
2935 def isdescending(self):
2936 return not self._ascending
2936 return not self._ascending
2937
2937
2938 def first(self):
2938 def first(self):
2939 if self._ascending:
2939 if self._ascending:
2940 it = self.fastasc
2940 it = self.fastasc
2941 else:
2941 else:
2942 it = self.fastdesc
2942 it = self.fastdesc
2943 if it is None:
2943 if it is None:
2944 # we need to consume all and try again
2944 # we need to consume all and try again
2945 for x in self._consumegen():
2945 for x in self._consumegen():
2946 pass
2946 pass
2947 return self.first()
2947 return self.first()
2948 if self:
2948 if self:
2949 return it().next()
2949 return it().next()
2950 return None
2950 return None
2951
2951
2952 def last(self):
2952 def last(self):
2953 if self._ascending:
2953 if self._ascending:
2954 it = self.fastdesc
2954 it = self.fastdesc
2955 else:
2955 else:
2956 it = self.fastasc
2956 it = self.fastasc
2957 if it is None:
2957 if it is None:
2958 # we need to consume all and try again
2958 # we need to consume all and try again
2959 for x in self._consumegen():
2959 for x in self._consumegen():
2960 pass
2960 pass
2961 return self.first()
2961 return self.first()
2962 if self:
2962 if self:
2963 return it().next()
2963 return it().next()
2964 return None
2964 return None
2965
2965
2966 def spanset(repo, start=None, end=None):
2966 def spanset(repo, start=None, end=None):
2967 """factory function to dispatch between fullreposet and actual spanset
2967 """factory function to dispatch between fullreposet and actual spanset
2968
2968
2969 Feel free to update all spanset call sites and kill this function at some
2969 Feel free to update all spanset call sites and kill this function at some
2970 point.
2970 point.
2971 """
2971 """
2972 if start is None and end is None:
2972 if start is None and end is None:
2973 return fullreposet(repo)
2973 return fullreposet(repo)
2974 return _spanset(repo, start, end)
2974 return _spanset(repo, start, end)
2975
2975
2976
2976
2977 class _spanset(abstractsmartset):
2977 class _spanset(abstractsmartset):
2978 """Duck type for baseset class which represents a range of revisions and
2978 """Duck type for baseset class which represents a range of revisions and
2979 can work lazily and without having all the range in memory
2979 can work lazily and without having all the range in memory
2980
2980
2981 Note that spanset(x, y) behave almost like xrange(x, y) except for two
2981 Note that spanset(x, y) behave almost like xrange(x, y) except for two
2982 notable points:
2982 notable points:
2983 - when x < y it will be automatically descending,
2983 - when x < y it will be automatically descending,
2984 - revision filtered with this repoview will be skipped.
2984 - revision filtered with this repoview will be skipped.
2985
2985
2986 """
2986 """
2987 def __init__(self, repo, start=0, end=None):
2987 def __init__(self, repo, start=0, end=None):
2988 """
2988 """
2989 start: first revision included the set
2989 start: first revision included the set
2990 (default to 0)
2990 (default to 0)
2991 end: first revision excluded (last+1)
2991 end: first revision excluded (last+1)
2992 (default to len(repo)
2992 (default to len(repo)
2993
2993
2994 Spanset will be descending if `end` < `start`.
2994 Spanset will be descending if `end` < `start`.
2995 """
2995 """
2996 if end is None:
2996 if end is None:
2997 end = len(repo)
2997 end = len(repo)
2998 self._ascending = start <= end
2998 self._ascending = start <= end
2999 if not self._ascending:
2999 if not self._ascending:
3000 start, end = end + 1, start +1
3000 start, end = end + 1, start +1
3001 self._start = start
3001 self._start = start
3002 self._end = end
3002 self._end = end
3003 self._hiddenrevs = repo.changelog.filteredrevs
3003 self._hiddenrevs = repo.changelog.filteredrevs
3004
3004
3005 def sort(self, reverse=False):
3005 def sort(self, reverse=False):
3006 self._ascending = not reverse
3006 self._ascending = not reverse
3007
3007
3008 def reverse(self):
3008 def reverse(self):
3009 self._ascending = not self._ascending
3009 self._ascending = not self._ascending
3010
3010
3011 def _iterfilter(self, iterrange):
3011 def _iterfilter(self, iterrange):
3012 s = self._hiddenrevs
3012 s = self._hiddenrevs
3013 for r in iterrange:
3013 for r in iterrange:
3014 if r not in s:
3014 if r not in s:
3015 yield r
3015 yield r
3016
3016
3017 def __iter__(self):
3017 def __iter__(self):
3018 if self._ascending:
3018 if self._ascending:
3019 return self.fastasc()
3019 return self.fastasc()
3020 else:
3020 else:
3021 return self.fastdesc()
3021 return self.fastdesc()
3022
3022
3023 def fastasc(self):
3023 def fastasc(self):
3024 iterrange = xrange(self._start, self._end)
3024 iterrange = xrange(self._start, self._end)
3025 if self._hiddenrevs:
3025 if self._hiddenrevs:
3026 return self._iterfilter(iterrange)
3026 return self._iterfilter(iterrange)
3027 return iter(iterrange)
3027 return iter(iterrange)
3028
3028
3029 def fastdesc(self):
3029 def fastdesc(self):
3030 iterrange = xrange(self._end - 1, self._start - 1, -1)
3030 iterrange = xrange(self._end - 1, self._start - 1, -1)
3031 if self._hiddenrevs:
3031 if self._hiddenrevs:
3032 return self._iterfilter(iterrange)
3032 return self._iterfilter(iterrange)
3033 return iter(iterrange)
3033 return iter(iterrange)
3034
3034
3035 def __contains__(self, rev):
3035 def __contains__(self, rev):
3036 hidden = self._hiddenrevs
3036 hidden = self._hiddenrevs
3037 return ((self._start <= rev < self._end)
3037 return ((self._start <= rev < self._end)
3038 and not (hidden and rev in hidden))
3038 and not (hidden and rev in hidden))
3039
3039
3040 def __nonzero__(self):
3040 def __nonzero__(self):
3041 for r in self:
3041 for r in self:
3042 return True
3042 return True
3043 return False
3043 return False
3044
3044
3045 def __len__(self):
3045 def __len__(self):
3046 if not self._hiddenrevs:
3046 if not self._hiddenrevs:
3047 return abs(self._end - self._start)
3047 return abs(self._end - self._start)
3048 else:
3048 else:
3049 count = 0
3049 count = 0
3050 start = self._start
3050 start = self._start
3051 end = self._end
3051 end = self._end
3052 for rev in self._hiddenrevs:
3052 for rev in self._hiddenrevs:
3053 if (end < rev <= start) or (start <= rev < end):
3053 if (end < rev <= start) or (start <= rev < end):
3054 count += 1
3054 count += 1
3055 return abs(self._end - self._start) - count
3055 return abs(self._end - self._start) - count
3056
3056
3057 def isascending(self):
3057 def isascending(self):
3058 return self._start <= self._end
3058 return self._start <= self._end
3059
3059
3060 def isdescending(self):
3060 def isdescending(self):
3061 return self._start >= self._end
3061 return self._start >= self._end
3062
3062
3063 def first(self):
3063 def first(self):
3064 if self._ascending:
3064 if self._ascending:
3065 it = self.fastasc
3065 it = self.fastasc
3066 else:
3066 else:
3067 it = self.fastdesc
3067 it = self.fastdesc
3068 for x in it():
3068 for x in it():
3069 return x
3069 return x
3070 return None
3070 return None
3071
3071
3072 def last(self):
3072 def last(self):
3073 if self._ascending:
3073 if self._ascending:
3074 it = self.fastdesc
3074 it = self.fastdesc
3075 else:
3075 else:
3076 it = self.fastasc
3076 it = self.fastasc
3077 for x in it():
3077 for x in it():
3078 return x
3078 return x
3079 return None
3079 return None
3080
3080
3081 class fullreposet(_spanset):
3081 class fullreposet(_spanset):
3082 """a set containing all revisions in the repo
3082 """a set containing all revisions in the repo
3083
3083
3084 This class exists to host special optimization.
3084 This class exists to host special optimization.
3085 """
3085 """
3086
3086
3087 def __init__(self, repo):
3087 def __init__(self, repo):
3088 super(fullreposet, self).__init__(repo)
3088 super(fullreposet, self).__init__(repo)
3089
3089
3090 def __and__(self, other):
3090 def __and__(self, other):
3091 """As self contains the whole repo, all of the other set should also be
3091 """As self contains the whole repo, all of the other set should also be
3092 in self. Therefore `self & other = other`.
3092 in self. Therefore `self & other = other`.
3093
3093
3094 This boldly assumes the other contains valid revs only.
3094 This boldly assumes the other contains valid revs only.
3095 """
3095 """
3096 # other not a smartset, make is so
3096 # other not a smartset, make is so
3097 if not util.safehasattr(other, 'isascending'):
3097 if not util.safehasattr(other, 'isascending'):
3098 # filter out hidden revision
3098 # filter out hidden revision
3099 # (this boldly assumes all smartset are pure)
3099 # (this boldly assumes all smartset are pure)
3100 #
3100 #
3101 # `other` was used with "&", let's assume this is a set like
3101 # `other` was used with "&", let's assume this is a set like
3102 # object.
3102 # object.
3103 other = baseset(other - self._hiddenrevs)
3103 other = baseset(other - self._hiddenrevs)
3104
3104
3105 if self.isascending():
3105 if self.isascending():
3106 other.sort()
3106 other.sort()
3107 else:
3107 else:
3108 other.sort(reverse)
3108 other.sort(reverse)
3109 return other
3109 return other
3110
3110
3111 # tell hggettext to extract docstrings from these functions:
3111 # tell hggettext to extract docstrings from these functions:
3112 i18nfunctions = symbols.values()
3112 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now