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