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