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