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