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