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