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