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