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