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