##// END OF EJS Templates
util: extract stringmatcher() from revset...
Matt Harbison -
r26481:7d132557 default
parent child Browse files
Show More
@@ -1,3854 +1,3815
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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import heapq
10 import heapq
11 import re
11 import re
12
12
13 from .i18n import _
13 from .i18n import _
14 from . import (
14 from . import (
15 encoding,
15 encoding,
16 error,
16 error,
17 hbisect,
17 hbisect,
18 match as matchmod,
18 match as matchmod,
19 node,
19 node,
20 obsolete as obsmod,
20 obsolete as obsmod,
21 parser,
21 parser,
22 pathutil,
22 pathutil,
23 phases,
23 phases,
24 repoview,
24 repoview,
25 util,
25 util,
26 )
26 )
27
27
28 def _revancestors(repo, revs, followfirst):
28 def _revancestors(repo, revs, followfirst):
29 """Like revlog.ancestors(), but supports followfirst."""
29 """Like revlog.ancestors(), but supports followfirst."""
30 if followfirst:
30 if followfirst:
31 cut = 1
31 cut = 1
32 else:
32 else:
33 cut = None
33 cut = None
34 cl = repo.changelog
34 cl = repo.changelog
35
35
36 def iterate():
36 def iterate():
37 revs.sort(reverse=True)
37 revs.sort(reverse=True)
38 irevs = iter(revs)
38 irevs = iter(revs)
39 h = []
39 h = []
40
40
41 inputrev = next(irevs, None)
41 inputrev = next(irevs, None)
42 if inputrev is not None:
42 if inputrev is not None:
43 heapq.heappush(h, -inputrev)
43 heapq.heappush(h, -inputrev)
44
44
45 seen = set()
45 seen = set()
46 while h:
46 while h:
47 current = -heapq.heappop(h)
47 current = -heapq.heappop(h)
48 if current == inputrev:
48 if current == inputrev:
49 inputrev = next(irevs, None)
49 inputrev = next(irevs, None)
50 if inputrev is not None:
50 if inputrev is not None:
51 heapq.heappush(h, -inputrev)
51 heapq.heappush(h, -inputrev)
52 if current not in seen:
52 if current not in seen:
53 seen.add(current)
53 seen.add(current)
54 yield current
54 yield current
55 for parent in cl.parentrevs(current)[:cut]:
55 for parent in cl.parentrevs(current)[:cut]:
56 if parent != node.nullrev:
56 if parent != node.nullrev:
57 heapq.heappush(h, -parent)
57 heapq.heappush(h, -parent)
58
58
59 return generatorset(iterate(), iterasc=False)
59 return generatorset(iterate(), iterasc=False)
60
60
61 def _revdescendants(repo, revs, followfirst):
61 def _revdescendants(repo, revs, followfirst):
62 """Like revlog.descendants() but supports followfirst."""
62 """Like revlog.descendants() but supports followfirst."""
63 if followfirst:
63 if followfirst:
64 cut = 1
64 cut = 1
65 else:
65 else:
66 cut = None
66 cut = None
67
67
68 def iterate():
68 def iterate():
69 cl = repo.changelog
69 cl = repo.changelog
70 # XXX this should be 'parentset.min()' assuming 'parentset' is a
70 # XXX this should be 'parentset.min()' assuming 'parentset' is a
71 # smartset (and if it is not, it should.)
71 # smartset (and if it is not, it should.)
72 first = min(revs)
72 first = min(revs)
73 nullrev = node.nullrev
73 nullrev = node.nullrev
74 if first == nullrev:
74 if first == nullrev:
75 # Are there nodes with a null first parent and a non-null
75 # Are there nodes with a null first parent and a non-null
76 # second one? Maybe. Do we care? Probably not.
76 # second one? Maybe. Do we care? Probably not.
77 for i in cl:
77 for i in cl:
78 yield i
78 yield i
79 else:
79 else:
80 seen = set(revs)
80 seen = set(revs)
81 for i in cl.revs(first + 1):
81 for i in cl.revs(first + 1):
82 for x in cl.parentrevs(i)[:cut]:
82 for x in cl.parentrevs(i)[:cut]:
83 if x != nullrev and x in seen:
83 if x != nullrev and x in seen:
84 seen.add(i)
84 seen.add(i)
85 yield i
85 yield i
86 break
86 break
87
87
88 return generatorset(iterate(), iterasc=True)
88 return generatorset(iterate(), iterasc=True)
89
89
90 def _reachablerootspure(repo, minroot, roots, heads, includepath):
90 def _reachablerootspure(repo, minroot, roots, heads, includepath):
91 """return (heads(::<roots> and ::<heads>))
91 """return (heads(::<roots> and ::<heads>))
92
92
93 If includepath is True, return (<roots>::<heads>)."""
93 If includepath is True, return (<roots>::<heads>)."""
94 if not roots:
94 if not roots:
95 return []
95 return []
96 parentrevs = repo.changelog.parentrevs
96 parentrevs = repo.changelog.parentrevs
97 roots = set(roots)
97 roots = set(roots)
98 visit = list(heads)
98 visit = list(heads)
99 reachable = set()
99 reachable = set()
100 seen = {}
100 seen = {}
101 # prefetch all the things! (because python is slow)
101 # prefetch all the things! (because python is slow)
102 reached = reachable.add
102 reached = reachable.add
103 dovisit = visit.append
103 dovisit = visit.append
104 nextvisit = visit.pop
104 nextvisit = visit.pop
105 # open-code the post-order traversal due to the tiny size of
105 # open-code the post-order traversal due to the tiny size of
106 # sys.getrecursionlimit()
106 # sys.getrecursionlimit()
107 while visit:
107 while visit:
108 rev = nextvisit()
108 rev = nextvisit()
109 if rev in roots:
109 if rev in roots:
110 reached(rev)
110 reached(rev)
111 if not includepath:
111 if not includepath:
112 continue
112 continue
113 parents = parentrevs(rev)
113 parents = parentrevs(rev)
114 seen[rev] = parents
114 seen[rev] = parents
115 for parent in parents:
115 for parent in parents:
116 if parent >= minroot and parent not in seen:
116 if parent >= minroot and parent not in seen:
117 dovisit(parent)
117 dovisit(parent)
118 if not reachable:
118 if not reachable:
119 return baseset()
119 return baseset()
120 if not includepath:
120 if not includepath:
121 return reachable
121 return reachable
122 for rev in sorted(seen):
122 for rev in sorted(seen):
123 for parent in seen[rev]:
123 for parent in seen[rev]:
124 if parent in reachable:
124 if parent in reachable:
125 reached(rev)
125 reached(rev)
126 return reachable
126 return reachable
127
127
128 def reachableroots(repo, roots, heads, includepath=False):
128 def reachableroots(repo, roots, heads, includepath=False):
129 """return (heads(::<roots> and ::<heads>))
129 """return (heads(::<roots> and ::<heads>))
130
130
131 If includepath is True, return (<roots>::<heads>)."""
131 If includepath is True, return (<roots>::<heads>)."""
132 if not roots:
132 if not roots:
133 return baseset()
133 return baseset()
134 minroot = roots.min()
134 minroot = roots.min()
135 roots = list(roots)
135 roots = list(roots)
136 heads = list(heads)
136 heads = list(heads)
137 try:
137 try:
138 revs = repo.changelog.reachableroots(minroot, heads, roots, includepath)
138 revs = repo.changelog.reachableroots(minroot, heads, roots, includepath)
139 except AttributeError:
139 except AttributeError:
140 revs = _reachablerootspure(repo, minroot, roots, heads, includepath)
140 revs = _reachablerootspure(repo, minroot, roots, heads, includepath)
141 revs = baseset(revs)
141 revs = baseset(revs)
142 revs.sort()
142 revs.sort()
143 return revs
143 return revs
144
144
145 elements = {
145 elements = {
146 # token-type: binding-strength, primary, prefix, infix, suffix
146 # token-type: binding-strength, primary, prefix, infix, suffix
147 "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None),
147 "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None),
148 "##": (20, None, None, ("_concat", 20), None),
148 "##": (20, None, None, ("_concat", 20), None),
149 "~": (18, None, None, ("ancestor", 18), None),
149 "~": (18, None, None, ("ancestor", 18), None),
150 "^": (18, None, None, ("parent", 18), ("parentpost", 18)),
150 "^": (18, None, None, ("parent", 18), ("parentpost", 18)),
151 "-": (5, None, ("negate", 19), ("minus", 5), None),
151 "-": (5, None, ("negate", 19), ("minus", 5), None),
152 "::": (17, None, ("dagrangepre", 17), ("dagrange", 17),
152 "::": (17, None, ("dagrangepre", 17), ("dagrange", 17),
153 ("dagrangepost", 17)),
153 ("dagrangepost", 17)),
154 "..": (17, None, ("dagrangepre", 17), ("dagrange", 17),
154 "..": (17, None, ("dagrangepre", 17), ("dagrange", 17),
155 ("dagrangepost", 17)),
155 ("dagrangepost", 17)),
156 ":": (15, "rangeall", ("rangepre", 15), ("range", 15), ("rangepost", 15)),
156 ":": (15, "rangeall", ("rangepre", 15), ("range", 15), ("rangepost", 15)),
157 "not": (10, None, ("not", 10), None, None),
157 "not": (10, None, ("not", 10), None, None),
158 "!": (10, None, ("not", 10), None, None),
158 "!": (10, None, ("not", 10), None, None),
159 "and": (5, None, None, ("and", 5), None),
159 "and": (5, None, None, ("and", 5), None),
160 "&": (5, None, None, ("and", 5), None),
160 "&": (5, None, None, ("and", 5), None),
161 "%": (5, None, None, ("only", 5), ("onlypost", 5)),
161 "%": (5, None, None, ("only", 5), ("onlypost", 5)),
162 "or": (4, None, None, ("or", 4), None),
162 "or": (4, None, None, ("or", 4), None),
163 "|": (4, None, None, ("or", 4), None),
163 "|": (4, None, None, ("or", 4), None),
164 "+": (4, None, None, ("or", 4), None),
164 "+": (4, None, None, ("or", 4), None),
165 "=": (3, None, None, ("keyvalue", 3), None),
165 "=": (3, None, None, ("keyvalue", 3), None),
166 ",": (2, None, None, ("list", 2), None),
166 ",": (2, None, None, ("list", 2), None),
167 ")": (0, None, None, None, None),
167 ")": (0, None, None, None, None),
168 "symbol": (0, "symbol", None, None, None),
168 "symbol": (0, "symbol", None, None, None),
169 "string": (0, "string", None, None, None),
169 "string": (0, "string", None, None, None),
170 "end": (0, None, None, None, None),
170 "end": (0, None, None, None, None),
171 }
171 }
172
172
173 keywords = set(['and', 'or', 'not'])
173 keywords = set(['and', 'or', 'not'])
174
174
175 # default set of valid characters for the initial letter of symbols
175 # default set of valid characters for the initial letter of symbols
176 _syminitletters = set(c for c in [chr(i) for i in xrange(256)]
176 _syminitletters = set(c for c in [chr(i) for i in xrange(256)]
177 if c.isalnum() or c in '._@' or ord(c) > 127)
177 if c.isalnum() or c in '._@' or ord(c) > 127)
178
178
179 # default set of valid characters for non-initial letters of symbols
179 # default set of valid characters for non-initial letters of symbols
180 _symletters = set(c for c in [chr(i) for i in xrange(256)]
180 _symletters = set(c for c in [chr(i) for i in xrange(256)]
181 if c.isalnum() or c in '-._/@' or ord(c) > 127)
181 if c.isalnum() or c in '-._/@' or ord(c) > 127)
182
182
183 def tokenize(program, lookup=None, syminitletters=None, symletters=None):
183 def tokenize(program, lookup=None, syminitletters=None, symletters=None):
184 '''
184 '''
185 Parse a revset statement into a stream of tokens
185 Parse a revset statement into a stream of tokens
186
186
187 ``syminitletters`` is the set of valid characters for the initial
187 ``syminitletters`` is the set of valid characters for the initial
188 letter of symbols.
188 letter of symbols.
189
189
190 By default, character ``c`` is recognized as valid for initial
190 By default, character ``c`` is recognized as valid for initial
191 letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.
191 letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.
192
192
193 ``symletters`` is the set of valid characters for non-initial
193 ``symletters`` is the set of valid characters for non-initial
194 letters of symbols.
194 letters of symbols.
195
195
196 By default, character ``c`` is recognized as valid for non-initial
196 By default, character ``c`` is recognized as valid for non-initial
197 letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
197 letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
198
198
199 Check that @ is a valid unquoted token character (issue3686):
199 Check that @ is a valid unquoted token character (issue3686):
200 >>> list(tokenize("@::"))
200 >>> list(tokenize("@::"))
201 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
201 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
202
202
203 '''
203 '''
204 if syminitletters is None:
204 if syminitletters is None:
205 syminitletters = _syminitletters
205 syminitletters = _syminitletters
206 if symletters is None:
206 if symletters is None:
207 symletters = _symletters
207 symletters = _symletters
208
208
209 if program and lookup:
209 if program and lookup:
210 # attempt to parse old-style ranges first to deal with
210 # attempt to parse old-style ranges first to deal with
211 # things like old-tag which contain query metacharacters
211 # things like old-tag which contain query metacharacters
212 parts = program.split(':', 1)
212 parts = program.split(':', 1)
213 if all(lookup(sym) for sym in parts if sym):
213 if all(lookup(sym) for sym in parts if sym):
214 if parts[0]:
214 if parts[0]:
215 yield ('symbol', parts[0], 0)
215 yield ('symbol', parts[0], 0)
216 if len(parts) > 1:
216 if len(parts) > 1:
217 s = len(parts[0])
217 s = len(parts[0])
218 yield (':', None, s)
218 yield (':', None, s)
219 if parts[1]:
219 if parts[1]:
220 yield ('symbol', parts[1], s + 1)
220 yield ('symbol', parts[1], s + 1)
221 yield ('end', None, len(program))
221 yield ('end', None, len(program))
222 return
222 return
223
223
224 pos, l = 0, len(program)
224 pos, l = 0, len(program)
225 while pos < l:
225 while pos < l:
226 c = program[pos]
226 c = program[pos]
227 if c.isspace(): # skip inter-token whitespace
227 if c.isspace(): # skip inter-token whitespace
228 pass
228 pass
229 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
229 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
230 yield ('::', None, pos)
230 yield ('::', None, pos)
231 pos += 1 # skip ahead
231 pos += 1 # skip ahead
232 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
232 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
233 yield ('..', None, pos)
233 yield ('..', None, pos)
234 pos += 1 # skip ahead
234 pos += 1 # skip ahead
235 elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
235 elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
236 yield ('##', None, pos)
236 yield ('##', None, pos)
237 pos += 1 # skip ahead
237 pos += 1 # skip ahead
238 elif c in "():=,-|&+!~^%": # handle simple operators
238 elif c in "():=,-|&+!~^%": # handle simple operators
239 yield (c, None, pos)
239 yield (c, None, pos)
240 elif (c in '"\'' or c == 'r' and
240 elif (c in '"\'' or c == 'r' and
241 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
241 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
242 if c == 'r':
242 if c == 'r':
243 pos += 1
243 pos += 1
244 c = program[pos]
244 c = program[pos]
245 decode = lambda x: x
245 decode = lambda x: x
246 else:
246 else:
247 decode = parser.unescapestr
247 decode = parser.unescapestr
248 pos += 1
248 pos += 1
249 s = pos
249 s = pos
250 while pos < l: # find closing quote
250 while pos < l: # find closing quote
251 d = program[pos]
251 d = program[pos]
252 if d == '\\': # skip over escaped characters
252 if d == '\\': # skip over escaped characters
253 pos += 2
253 pos += 2
254 continue
254 continue
255 if d == c:
255 if d == c:
256 yield ('string', decode(program[s:pos]), s)
256 yield ('string', decode(program[s:pos]), s)
257 break
257 break
258 pos += 1
258 pos += 1
259 else:
259 else:
260 raise error.ParseError(_("unterminated string"), s)
260 raise error.ParseError(_("unterminated string"), s)
261 # gather up a symbol/keyword
261 # gather up a symbol/keyword
262 elif c in syminitletters:
262 elif c in syminitletters:
263 s = pos
263 s = pos
264 pos += 1
264 pos += 1
265 while pos < l: # find end of symbol
265 while pos < l: # find end of symbol
266 d = program[pos]
266 d = program[pos]
267 if d not in symletters:
267 if d not in symletters:
268 break
268 break
269 if d == '.' and program[pos - 1] == '.': # special case for ..
269 if d == '.' and program[pos - 1] == '.': # special case for ..
270 pos -= 1
270 pos -= 1
271 break
271 break
272 pos += 1
272 pos += 1
273 sym = program[s:pos]
273 sym = program[s:pos]
274 if sym in keywords: # operator keywords
274 if sym in keywords: # operator keywords
275 yield (sym, None, s)
275 yield (sym, None, s)
276 elif '-' in sym:
276 elif '-' in sym:
277 # some jerk gave us foo-bar-baz, try to check if it's a symbol
277 # some jerk gave us foo-bar-baz, try to check if it's a symbol
278 if lookup and lookup(sym):
278 if lookup and lookup(sym):
279 # looks like a real symbol
279 # looks like a real symbol
280 yield ('symbol', sym, s)
280 yield ('symbol', sym, s)
281 else:
281 else:
282 # looks like an expression
282 # looks like an expression
283 parts = sym.split('-')
283 parts = sym.split('-')
284 for p in parts[:-1]:
284 for p in parts[:-1]:
285 if p: # possible consecutive -
285 if p: # possible consecutive -
286 yield ('symbol', p, s)
286 yield ('symbol', p, s)
287 s += len(p)
287 s += len(p)
288 yield ('-', None, pos)
288 yield ('-', None, pos)
289 s += 1
289 s += 1
290 if parts[-1]: # possible trailing -
290 if parts[-1]: # possible trailing -
291 yield ('symbol', parts[-1], s)
291 yield ('symbol', parts[-1], s)
292 else:
292 else:
293 yield ('symbol', sym, s)
293 yield ('symbol', sym, s)
294 pos -= 1
294 pos -= 1
295 else:
295 else:
296 raise error.ParseError(_("syntax error in revset '%s'") %
296 raise error.ParseError(_("syntax error in revset '%s'") %
297 program, pos)
297 program, pos)
298 pos += 1
298 pos += 1
299 yield ('end', None, pos)
299 yield ('end', None, pos)
300
300
301 def parseerrordetail(inst):
301 def parseerrordetail(inst):
302 """Compose error message from specified ParseError object
302 """Compose error message from specified ParseError object
303 """
303 """
304 if len(inst.args) > 1:
304 if len(inst.args) > 1:
305 return _('at %s: %s') % (inst.args[1], inst.args[0])
305 return _('at %s: %s') % (inst.args[1], inst.args[0])
306 else:
306 else:
307 return inst.args[0]
307 return inst.args[0]
308
308
309 # helpers
309 # helpers
310
310
311 def getstring(x, err):
311 def getstring(x, err):
312 if x and (x[0] == 'string' or x[0] == 'symbol'):
312 if x and (x[0] == 'string' or x[0] == 'symbol'):
313 return x[1]
313 return x[1]
314 raise error.ParseError(err)
314 raise error.ParseError(err)
315
315
316 def getlist(x):
316 def getlist(x):
317 if not x:
317 if not x:
318 return []
318 return []
319 if x[0] == 'list':
319 if x[0] == 'list':
320 return getlist(x[1]) + [x[2]]
320 return getlist(x[1]) + [x[2]]
321 return [x]
321 return [x]
322
322
323 def getargs(x, min, max, err):
323 def getargs(x, min, max, err):
324 l = getlist(x)
324 l = getlist(x)
325 if len(l) < min or (max >= 0 and len(l) > max):
325 if len(l) < min or (max >= 0 and len(l) > max):
326 raise error.ParseError(err)
326 raise error.ParseError(err)
327 return l
327 return l
328
328
329 def getargsdict(x, funcname, keys):
329 def getargsdict(x, funcname, keys):
330 return parser.buildargsdict(getlist(x), funcname, keys.split(),
330 return parser.buildargsdict(getlist(x), funcname, keys.split(),
331 keyvaluenode='keyvalue', keynode='symbol')
331 keyvaluenode='keyvalue', keynode='symbol')
332
332
333 def isvalidsymbol(tree):
333 def isvalidsymbol(tree):
334 """Examine whether specified ``tree`` is valid ``symbol`` or not
334 """Examine whether specified ``tree`` is valid ``symbol`` or not
335 """
335 """
336 return tree[0] == 'symbol' and len(tree) > 1
336 return tree[0] == 'symbol' and len(tree) > 1
337
337
338 def getsymbol(tree):
338 def getsymbol(tree):
339 """Get symbol name from valid ``symbol`` in ``tree``
339 """Get symbol name from valid ``symbol`` in ``tree``
340
340
341 This assumes that ``tree`` is already examined by ``isvalidsymbol``.
341 This assumes that ``tree`` is already examined by ``isvalidsymbol``.
342 """
342 """
343 return tree[1]
343 return tree[1]
344
344
345 def isvalidfunc(tree):
345 def isvalidfunc(tree):
346 """Examine whether specified ``tree`` is valid ``func`` or not
346 """Examine whether specified ``tree`` is valid ``func`` or not
347 """
347 """
348 return tree[0] == 'func' and len(tree) > 1 and isvalidsymbol(tree[1])
348 return tree[0] == 'func' and len(tree) > 1 and isvalidsymbol(tree[1])
349
349
350 def getfuncname(tree):
350 def getfuncname(tree):
351 """Get function name from valid ``func`` in ``tree``
351 """Get function name from valid ``func`` in ``tree``
352
352
353 This assumes that ``tree`` is already examined by ``isvalidfunc``.
353 This assumes that ``tree`` is already examined by ``isvalidfunc``.
354 """
354 """
355 return getsymbol(tree[1])
355 return getsymbol(tree[1])
356
356
357 def getfuncargs(tree):
357 def getfuncargs(tree):
358 """Get list of function arguments from valid ``func`` in ``tree``
358 """Get list of function arguments from valid ``func`` in ``tree``
359
359
360 This assumes that ``tree`` is already examined by ``isvalidfunc``.
360 This assumes that ``tree`` is already examined by ``isvalidfunc``.
361 """
361 """
362 if len(tree) > 2:
362 if len(tree) > 2:
363 return getlist(tree[2])
363 return getlist(tree[2])
364 else:
364 else:
365 return []
365 return []
366
366
367 def getset(repo, subset, x):
367 def getset(repo, subset, x):
368 if not x:
368 if not x:
369 raise error.ParseError(_("missing argument"))
369 raise error.ParseError(_("missing argument"))
370 s = methods[x[0]](repo, subset, *x[1:])
370 s = methods[x[0]](repo, subset, *x[1:])
371 if util.safehasattr(s, 'isascending'):
371 if util.safehasattr(s, 'isascending'):
372 return s
372 return s
373 if (repo.ui.configbool('devel', 'all-warnings')
373 if (repo.ui.configbool('devel', 'all-warnings')
374 or repo.ui.configbool('devel', 'old-revset')):
374 or repo.ui.configbool('devel', 'old-revset')):
375 # else case should not happen, because all non-func are internal,
375 # else case should not happen, because all non-func are internal,
376 # ignoring for now.
376 # ignoring for now.
377 if x[0] == 'func' and x[1][0] == 'symbol' and x[1][1] in symbols:
377 if x[0] == 'func' and x[1][0] == 'symbol' and x[1][1] in symbols:
378 repo.ui.develwarn('revset "%s" use list instead of smartset, '
378 repo.ui.develwarn('revset "%s" use list instead of smartset, '
379 '(upgrade your code)' % x[1][1])
379 '(upgrade your code)' % x[1][1])
380 return baseset(s)
380 return baseset(s)
381
381
382 def _getrevsource(repo, r):
382 def _getrevsource(repo, r):
383 extra = repo[r].extra()
383 extra = repo[r].extra()
384 for label in ('source', 'transplant_source', 'rebase_source'):
384 for label in ('source', 'transplant_source', 'rebase_source'):
385 if label in extra:
385 if label in extra:
386 try:
386 try:
387 return repo[extra[label]].rev()
387 return repo[extra[label]].rev()
388 except error.RepoLookupError:
388 except error.RepoLookupError:
389 pass
389 pass
390 return None
390 return None
391
391
392 # operator methods
392 # operator methods
393
393
394 def stringset(repo, subset, x):
394 def stringset(repo, subset, x):
395 x = repo[x].rev()
395 x = repo[x].rev()
396 if (x in subset
396 if (x in subset
397 or x == node.nullrev and isinstance(subset, fullreposet)):
397 or x == node.nullrev and isinstance(subset, fullreposet)):
398 return baseset([x])
398 return baseset([x])
399 return baseset()
399 return baseset()
400
400
401 def rangeset(repo, subset, x, y):
401 def rangeset(repo, subset, x, y):
402 m = getset(repo, fullreposet(repo), x)
402 m = getset(repo, fullreposet(repo), x)
403 n = getset(repo, fullreposet(repo), y)
403 n = getset(repo, fullreposet(repo), y)
404
404
405 if not m or not n:
405 if not m or not n:
406 return baseset()
406 return baseset()
407 m, n = m.first(), n.last()
407 m, n = m.first(), n.last()
408
408
409 if m == n:
409 if m == n:
410 r = baseset([m])
410 r = baseset([m])
411 elif n == node.wdirrev:
411 elif n == node.wdirrev:
412 r = spanset(repo, m, len(repo)) + baseset([n])
412 r = spanset(repo, m, len(repo)) + baseset([n])
413 elif m == node.wdirrev:
413 elif m == node.wdirrev:
414 r = baseset([m]) + spanset(repo, len(repo) - 1, n - 1)
414 r = baseset([m]) + spanset(repo, len(repo) - 1, n - 1)
415 elif m < n:
415 elif m < n:
416 r = spanset(repo, m, n + 1)
416 r = spanset(repo, m, n + 1)
417 else:
417 else:
418 r = spanset(repo, m, n - 1)
418 r = spanset(repo, m, n - 1)
419 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
419 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
420 # necessary to ensure we preserve the order in subset.
420 # necessary to ensure we preserve the order in subset.
421 #
421 #
422 # This has performance implication, carrying the sorting over when possible
422 # This has performance implication, carrying the sorting over when possible
423 # would be more efficient.
423 # would be more efficient.
424 return r & subset
424 return r & subset
425
425
426 def dagrange(repo, subset, x, y):
426 def dagrange(repo, subset, x, y):
427 r = fullreposet(repo)
427 r = fullreposet(repo)
428 xs = reachableroots(repo, getset(repo, r, x), getset(repo, r, y),
428 xs = reachableroots(repo, getset(repo, r, x), getset(repo, r, y),
429 includepath=True)
429 includepath=True)
430 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
430 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
431 # necessary to ensure we preserve the order in subset.
431 # necessary to ensure we preserve the order in subset.
432 return xs & subset
432 return xs & subset
433
433
434 def andset(repo, subset, x, y):
434 def andset(repo, subset, x, y):
435 return getset(repo, getset(repo, subset, x), y)
435 return getset(repo, getset(repo, subset, x), y)
436
436
437 def orset(repo, subset, *xs):
437 def orset(repo, subset, *xs):
438 assert xs
438 assert xs
439 if len(xs) == 1:
439 if len(xs) == 1:
440 return getset(repo, subset, xs[0])
440 return getset(repo, subset, xs[0])
441 p = len(xs) // 2
441 p = len(xs) // 2
442 a = orset(repo, subset, *xs[:p])
442 a = orset(repo, subset, *xs[:p])
443 b = orset(repo, subset, *xs[p:])
443 b = orset(repo, subset, *xs[p:])
444 return a + b
444 return a + b
445
445
446 def notset(repo, subset, x):
446 def notset(repo, subset, x):
447 return subset - getset(repo, subset, x)
447 return subset - getset(repo, subset, x)
448
448
449 def listset(repo, subset, a, b):
449 def listset(repo, subset, a, b):
450 raise error.ParseError(_("can't use a list in this context"))
450 raise error.ParseError(_("can't use a list in this context"))
451
451
452 def keyvaluepair(repo, subset, k, v):
452 def keyvaluepair(repo, subset, k, v):
453 raise error.ParseError(_("can't use a key-value pair in this context"))
453 raise error.ParseError(_("can't use a key-value pair in this context"))
454
454
455 def func(repo, subset, a, b):
455 def func(repo, subset, a, b):
456 if a[0] == 'symbol' and a[1] in symbols:
456 if a[0] == 'symbol' and a[1] in symbols:
457 return symbols[a[1]](repo, subset, b)
457 return symbols[a[1]](repo, subset, b)
458
458
459 keep = lambda fn: getattr(fn, '__doc__', None) is not None
459 keep = lambda fn: getattr(fn, '__doc__', None) is not None
460
460
461 syms = [s for (s, fn) in symbols.items() if keep(fn)]
461 syms = [s for (s, fn) in symbols.items() if keep(fn)]
462 raise error.UnknownIdentifier(a[1], syms)
462 raise error.UnknownIdentifier(a[1], syms)
463
463
464 # functions
464 # functions
465
465
466 def _mergedefaultdest(repo, subset, x):
466 def _mergedefaultdest(repo, subset, x):
467 # ``_mergedefaultdest()``
467 # ``_mergedefaultdest()``
468
468
469 # default destination for merge.
469 # default destination for merge.
470 # # XXX: Currently private because I expect the signature to change.
470 # # XXX: Currently private because I expect the signature to change.
471 # # XXX: - taking rev as arguments,
471 # # XXX: - taking rev as arguments,
472 # # XXX: - bailing out in case of ambiguity vs returning all data.
472 # # XXX: - bailing out in case of ambiguity vs returning all data.
473 getargs(x, 0, 0, _("_mergedefaultdest takes no arguments"))
473 getargs(x, 0, 0, _("_mergedefaultdest takes no arguments"))
474 if repo._activebookmark:
474 if repo._activebookmark:
475 bmheads = repo.bookmarkheads(repo._activebookmark)
475 bmheads = repo.bookmarkheads(repo._activebookmark)
476 curhead = repo[repo._activebookmark].node()
476 curhead = repo[repo._activebookmark].node()
477 if len(bmheads) == 2:
477 if len(bmheads) == 2:
478 if curhead == bmheads[0]:
478 if curhead == bmheads[0]:
479 node = bmheads[1]
479 node = bmheads[1]
480 else:
480 else:
481 node = bmheads[0]
481 node = bmheads[0]
482 elif len(bmheads) > 2:
482 elif len(bmheads) > 2:
483 raise util.Abort(_("multiple matching bookmarks to merge - "
483 raise util.Abort(_("multiple matching bookmarks to merge - "
484 "please merge with an explicit rev or bookmark"),
484 "please merge with an explicit rev or bookmark"),
485 hint=_("run 'hg heads' to see all heads"))
485 hint=_("run 'hg heads' to see all heads"))
486 elif len(bmheads) <= 1:
486 elif len(bmheads) <= 1:
487 raise util.Abort(_("no matching bookmark to merge - "
487 raise util.Abort(_("no matching bookmark to merge - "
488 "please merge with an explicit rev or bookmark"),
488 "please merge with an explicit rev or bookmark"),
489 hint=_("run 'hg heads' to see all heads"))
489 hint=_("run 'hg heads' to see all heads"))
490 else:
490 else:
491 branch = repo[None].branch()
491 branch = repo[None].branch()
492 bheads = repo.branchheads(branch)
492 bheads = repo.branchheads(branch)
493 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
493 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
494
494
495 if len(nbhs) > 2:
495 if len(nbhs) > 2:
496 raise util.Abort(_("branch '%s' has %d heads - "
496 raise util.Abort(_("branch '%s' has %d heads - "
497 "please merge with an explicit rev")
497 "please merge with an explicit rev")
498 % (branch, len(bheads)),
498 % (branch, len(bheads)),
499 hint=_("run 'hg heads .' to see heads"))
499 hint=_("run 'hg heads .' to see heads"))
500
500
501 parent = repo.dirstate.p1()
501 parent = repo.dirstate.p1()
502 if len(nbhs) <= 1:
502 if len(nbhs) <= 1:
503 if len(bheads) > 1:
503 if len(bheads) > 1:
504 raise util.Abort(_("heads are bookmarked - "
504 raise util.Abort(_("heads are bookmarked - "
505 "please merge with an explicit rev"),
505 "please merge with an explicit rev"),
506 hint=_("run 'hg heads' to see all heads"))
506 hint=_("run 'hg heads' to see all heads"))
507 if len(repo.heads()) > 1:
507 if len(repo.heads()) > 1:
508 raise util.Abort(_("branch '%s' has one head - "
508 raise util.Abort(_("branch '%s' has one head - "
509 "please merge with an explicit rev")
509 "please merge with an explicit rev")
510 % branch,
510 % branch,
511 hint=_("run 'hg heads' to see all heads"))
511 hint=_("run 'hg heads' to see all heads"))
512 msg, hint = _('nothing to merge'), None
512 msg, hint = _('nothing to merge'), None
513 if parent != repo.lookup(branch):
513 if parent != repo.lookup(branch):
514 hint = _("use 'hg update' instead")
514 hint = _("use 'hg update' instead")
515 raise util.Abort(msg, hint=hint)
515 raise util.Abort(msg, hint=hint)
516
516
517 if parent not in bheads:
517 if parent not in bheads:
518 raise util.Abort(_('working directory not at a head revision'),
518 raise util.Abort(_('working directory not at a head revision'),
519 hint=_("use 'hg update' or merge with an "
519 hint=_("use 'hg update' or merge with an "
520 "explicit revision"))
520 "explicit revision"))
521 if parent == nbhs[0]:
521 if parent == nbhs[0]:
522 node = nbhs[-1]
522 node = nbhs[-1]
523 else:
523 else:
524 node = nbhs[0]
524 node = nbhs[0]
525 return subset & baseset([repo[node].rev()])
525 return subset & baseset([repo[node].rev()])
526
526
527 def _updatedefaultdest(repo, subset, x):
527 def _updatedefaultdest(repo, subset, x):
528 # ``_updatedefaultdest()``
528 # ``_updatedefaultdest()``
529
529
530 # default destination for update.
530 # default destination for update.
531 # # XXX: Currently private because I expect the signature to change.
531 # # XXX: Currently private because I expect the signature to change.
532 # # XXX: - taking rev as arguments,
532 # # XXX: - taking rev as arguments,
533 # # XXX: - bailing out in case of ambiguity vs returning all data.
533 # # XXX: - bailing out in case of ambiguity vs returning all data.
534 getargs(x, 0, 0, _("_updatedefaultdest takes no arguments"))
534 getargs(x, 0, 0, _("_updatedefaultdest takes no arguments"))
535 # Here is where we should consider bookmarks, divergent bookmarks,
535 # Here is where we should consider bookmarks, divergent bookmarks,
536 # foreground changesets (successors), and tip of current branch;
536 # foreground changesets (successors), and tip of current branch;
537 # but currently we are only checking the branch tips.
537 # but currently we are only checking the branch tips.
538 node = None
538 node = None
539 wc = repo[None]
539 wc = repo[None]
540 p1 = wc.p1()
540 p1 = wc.p1()
541 try:
541 try:
542 node = repo.branchtip(wc.branch())
542 node = repo.branchtip(wc.branch())
543 except error.RepoLookupError:
543 except error.RepoLookupError:
544 if wc.branch() == 'default': # no default branch!
544 if wc.branch() == 'default': # no default branch!
545 node = repo.lookup('tip') # update to tip
545 node = repo.lookup('tip') # update to tip
546 else:
546 else:
547 raise util.Abort(_("branch %s not found") % wc.branch())
547 raise util.Abort(_("branch %s not found") % wc.branch())
548
548
549 if p1.obsolete() and not p1.children():
549 if p1.obsolete() and not p1.children():
550 # allow updating to successors
550 # allow updating to successors
551 successors = obsmod.successorssets(repo, p1.node())
551 successors = obsmod.successorssets(repo, p1.node())
552
552
553 # behavior of certain cases is as follows,
553 # behavior of certain cases is as follows,
554 #
554 #
555 # divergent changesets: update to highest rev, similar to what
555 # divergent changesets: update to highest rev, similar to what
556 # is currently done when there are more than one head
556 # is currently done when there are more than one head
557 # (i.e. 'tip')
557 # (i.e. 'tip')
558 #
558 #
559 # replaced changesets: same as divergent except we know there
559 # replaced changesets: same as divergent except we know there
560 # is no conflict
560 # is no conflict
561 #
561 #
562 # pruned changeset: no update is done; though, we could
562 # pruned changeset: no update is done; though, we could
563 # consider updating to the first non-obsolete parent,
563 # consider updating to the first non-obsolete parent,
564 # similar to what is current done for 'hg prune'
564 # similar to what is current done for 'hg prune'
565
565
566 if successors:
566 if successors:
567 # flatten the list here handles both divergent (len > 1)
567 # flatten the list here handles both divergent (len > 1)
568 # and the usual case (len = 1)
568 # and the usual case (len = 1)
569 successors = [n for sub in successors for n in sub]
569 successors = [n for sub in successors for n in sub]
570
570
571 # get the max revision for the given successors set,
571 # get the max revision for the given successors set,
572 # i.e. the 'tip' of a set
572 # i.e. the 'tip' of a set
573 node = repo.revs('max(%ln)', successors).first()
573 node = repo.revs('max(%ln)', successors).first()
574 return subset & baseset([repo[node].rev()])
574 return subset & baseset([repo[node].rev()])
575
575
576 def adds(repo, subset, x):
576 def adds(repo, subset, x):
577 """``adds(pattern)``
577 """``adds(pattern)``
578 Changesets that add a file matching pattern.
578 Changesets that add a file matching pattern.
579
579
580 The pattern without explicit kind like ``glob:`` is expected to be
580 The pattern without explicit kind like ``glob:`` is expected to be
581 relative to the current directory and match against a file or a
581 relative to the current directory and match against a file or a
582 directory.
582 directory.
583 """
583 """
584 # i18n: "adds" is a keyword
584 # i18n: "adds" is a keyword
585 pat = getstring(x, _("adds requires a pattern"))
585 pat = getstring(x, _("adds requires a pattern"))
586 return checkstatus(repo, subset, pat, 1)
586 return checkstatus(repo, subset, pat, 1)
587
587
588 def ancestor(repo, subset, x):
588 def ancestor(repo, subset, x):
589 """``ancestor(*changeset)``
589 """``ancestor(*changeset)``
590 A greatest common ancestor of the changesets.
590 A greatest common ancestor of the changesets.
591
591
592 Accepts 0 or more changesets.
592 Accepts 0 or more changesets.
593 Will return empty list when passed no args.
593 Will return empty list when passed no args.
594 Greatest common ancestor of a single changeset is that changeset.
594 Greatest common ancestor of a single changeset is that changeset.
595 """
595 """
596 # i18n: "ancestor" is a keyword
596 # i18n: "ancestor" is a keyword
597 l = getlist(x)
597 l = getlist(x)
598 rl = fullreposet(repo)
598 rl = fullreposet(repo)
599 anc = None
599 anc = None
600
600
601 # (getset(repo, rl, i) for i in l) generates a list of lists
601 # (getset(repo, rl, i) for i in l) generates a list of lists
602 for revs in (getset(repo, rl, i) for i in l):
602 for revs in (getset(repo, rl, i) for i in l):
603 for r in revs:
603 for r in revs:
604 if anc is None:
604 if anc is None:
605 anc = repo[r]
605 anc = repo[r]
606 else:
606 else:
607 anc = anc.ancestor(repo[r])
607 anc = anc.ancestor(repo[r])
608
608
609 if anc is not None and anc.rev() in subset:
609 if anc is not None and anc.rev() in subset:
610 return baseset([anc.rev()])
610 return baseset([anc.rev()])
611 return baseset()
611 return baseset()
612
612
613 def _ancestors(repo, subset, x, followfirst=False):
613 def _ancestors(repo, subset, x, followfirst=False):
614 heads = getset(repo, fullreposet(repo), x)
614 heads = getset(repo, fullreposet(repo), x)
615 if not heads:
615 if not heads:
616 return baseset()
616 return baseset()
617 s = _revancestors(repo, heads, followfirst)
617 s = _revancestors(repo, heads, followfirst)
618 return subset & s
618 return subset & s
619
619
620 def ancestors(repo, subset, x):
620 def ancestors(repo, subset, x):
621 """``ancestors(set)``
621 """``ancestors(set)``
622 Changesets that are ancestors of a changeset in set.
622 Changesets that are ancestors of a changeset in set.
623 """
623 """
624 return _ancestors(repo, subset, x)
624 return _ancestors(repo, subset, x)
625
625
626 def _firstancestors(repo, subset, x):
626 def _firstancestors(repo, subset, x):
627 # ``_firstancestors(set)``
627 # ``_firstancestors(set)``
628 # Like ``ancestors(set)`` but follows only the first parents.
628 # Like ``ancestors(set)`` but follows only the first parents.
629 return _ancestors(repo, subset, x, followfirst=True)
629 return _ancestors(repo, subset, x, followfirst=True)
630
630
631 def ancestorspec(repo, subset, x, n):
631 def ancestorspec(repo, subset, x, n):
632 """``set~n``
632 """``set~n``
633 Changesets that are the Nth ancestor (first parents only) of a changeset
633 Changesets that are the Nth ancestor (first parents only) of a changeset
634 in set.
634 in set.
635 """
635 """
636 try:
636 try:
637 n = int(n[1])
637 n = int(n[1])
638 except (TypeError, ValueError):
638 except (TypeError, ValueError):
639 raise error.ParseError(_("~ expects a number"))
639 raise error.ParseError(_("~ expects a number"))
640 ps = set()
640 ps = set()
641 cl = repo.changelog
641 cl = repo.changelog
642 for r in getset(repo, fullreposet(repo), x):
642 for r in getset(repo, fullreposet(repo), x):
643 for i in range(n):
643 for i in range(n):
644 r = cl.parentrevs(r)[0]
644 r = cl.parentrevs(r)[0]
645 ps.add(r)
645 ps.add(r)
646 return subset & ps
646 return subset & ps
647
647
648 def author(repo, subset, x):
648 def author(repo, subset, x):
649 """``author(string)``
649 """``author(string)``
650 Alias for ``user(string)``.
650 Alias for ``user(string)``.
651 """
651 """
652 # i18n: "author" is a keyword
652 # i18n: "author" is a keyword
653 n = encoding.lower(getstring(x, _("author requires a string")))
653 n = encoding.lower(getstring(x, _("author requires a string")))
654 kind, pattern, matcher = _substringmatcher(n)
654 kind, pattern, matcher = _substringmatcher(n)
655 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())))
655 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())))
656
656
657 def bisect(repo, subset, x):
657 def bisect(repo, subset, x):
658 """``bisect(string)``
658 """``bisect(string)``
659 Changesets marked in the specified bisect status:
659 Changesets marked in the specified bisect status:
660
660
661 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
661 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
662 - ``goods``, ``bads`` : csets topologically good/bad
662 - ``goods``, ``bads`` : csets topologically good/bad
663 - ``range`` : csets taking part in the bisection
663 - ``range`` : csets taking part in the bisection
664 - ``pruned`` : csets that are goods, bads or skipped
664 - ``pruned`` : csets that are goods, bads or skipped
665 - ``untested`` : csets whose fate is yet unknown
665 - ``untested`` : csets whose fate is yet unknown
666 - ``ignored`` : csets ignored due to DAG topology
666 - ``ignored`` : csets ignored due to DAG topology
667 - ``current`` : the cset currently being bisected
667 - ``current`` : the cset currently being bisected
668 """
668 """
669 # i18n: "bisect" is a keyword
669 # i18n: "bisect" is a keyword
670 status = getstring(x, _("bisect requires a string")).lower()
670 status = getstring(x, _("bisect requires a string")).lower()
671 state = set(hbisect.get(repo, status))
671 state = set(hbisect.get(repo, status))
672 return subset & state
672 return subset & state
673
673
674 # Backward-compatibility
674 # Backward-compatibility
675 # - no help entry so that we do not advertise it any more
675 # - no help entry so that we do not advertise it any more
676 def bisected(repo, subset, x):
676 def bisected(repo, subset, x):
677 return bisect(repo, subset, x)
677 return bisect(repo, subset, x)
678
678
679 def bookmark(repo, subset, x):
679 def bookmark(repo, subset, x):
680 """``bookmark([name])``
680 """``bookmark([name])``
681 The named bookmark or all bookmarks.
681 The named bookmark or all bookmarks.
682
682
683 If `name` starts with `re:`, the remainder of the name is treated as
683 If `name` starts with `re:`, the remainder of the name is treated as
684 a regular expression. To match a bookmark that actually starts with `re:`,
684 a regular expression. To match a bookmark that actually starts with `re:`,
685 use the prefix `literal:`.
685 use the prefix `literal:`.
686 """
686 """
687 # i18n: "bookmark" is a keyword
687 # i18n: "bookmark" is a keyword
688 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
688 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
689 if args:
689 if args:
690 bm = getstring(args[0],
690 bm = getstring(args[0],
691 # i18n: "bookmark" is a keyword
691 # i18n: "bookmark" is a keyword
692 _('the argument to bookmark must be a string'))
692 _('the argument to bookmark must be a string'))
693 kind, pattern, matcher = _stringmatcher(bm)
693 kind, pattern, matcher = util.stringmatcher(bm)
694 bms = set()
694 bms = set()
695 if kind == 'literal':
695 if kind == 'literal':
696 bmrev = repo._bookmarks.get(pattern, None)
696 bmrev = repo._bookmarks.get(pattern, None)
697 if not bmrev:
697 if not bmrev:
698 raise error.RepoLookupError(_("bookmark '%s' does not exist")
698 raise error.RepoLookupError(_("bookmark '%s' does not exist")
699 % bm)
699 % bm)
700 bms.add(repo[bmrev].rev())
700 bms.add(repo[bmrev].rev())
701 else:
701 else:
702 matchrevs = set()
702 matchrevs = set()
703 for name, bmrev in repo._bookmarks.iteritems():
703 for name, bmrev in repo._bookmarks.iteritems():
704 if matcher(name):
704 if matcher(name):
705 matchrevs.add(bmrev)
705 matchrevs.add(bmrev)
706 if not matchrevs:
706 if not matchrevs:
707 raise error.RepoLookupError(_("no bookmarks exist"
707 raise error.RepoLookupError(_("no bookmarks exist"
708 " that match '%s'") % pattern)
708 " that match '%s'") % pattern)
709 for bmrev in matchrevs:
709 for bmrev in matchrevs:
710 bms.add(repo[bmrev].rev())
710 bms.add(repo[bmrev].rev())
711 else:
711 else:
712 bms = set([repo[r].rev()
712 bms = set([repo[r].rev()
713 for r in repo._bookmarks.values()])
713 for r in repo._bookmarks.values()])
714 bms -= set([node.nullrev])
714 bms -= set([node.nullrev])
715 return subset & bms
715 return subset & bms
716
716
717 def branch(repo, subset, x):
717 def branch(repo, subset, x):
718 """``branch(string or set)``
718 """``branch(string or set)``
719 All changesets belonging to the given branch or the branches of the given
719 All changesets belonging to the given branch or the branches of the given
720 changesets.
720 changesets.
721
721
722 If `string` starts with `re:`, the remainder of the name is treated as
722 If `string` starts with `re:`, the remainder of the name is treated as
723 a regular expression. To match a branch that actually starts with `re:`,
723 a regular expression. To match a branch that actually starts with `re:`,
724 use the prefix `literal:`.
724 use the prefix `literal:`.
725 """
725 """
726 getbi = repo.revbranchcache().branchinfo
726 getbi = repo.revbranchcache().branchinfo
727
727
728 try:
728 try:
729 b = getstring(x, '')
729 b = getstring(x, '')
730 except error.ParseError:
730 except error.ParseError:
731 # not a string, but another revspec, e.g. tip()
731 # not a string, but another revspec, e.g. tip()
732 pass
732 pass
733 else:
733 else:
734 kind, pattern, matcher = _stringmatcher(b)
734 kind, pattern, matcher = util.stringmatcher(b)
735 if kind == 'literal':
735 if kind == 'literal':
736 # note: falls through to the revspec case if no branch with
736 # note: falls through to the revspec case if no branch with
737 # this name exists
737 # this name exists
738 if pattern in repo.branchmap():
738 if pattern in repo.branchmap():
739 return subset.filter(lambda r: matcher(getbi(r)[0]))
739 return subset.filter(lambda r: matcher(getbi(r)[0]))
740 else:
740 else:
741 return subset.filter(lambda r: matcher(getbi(r)[0]))
741 return subset.filter(lambda r: matcher(getbi(r)[0]))
742
742
743 s = getset(repo, fullreposet(repo), x)
743 s = getset(repo, fullreposet(repo), x)
744 b = set()
744 b = set()
745 for r in s:
745 for r in s:
746 b.add(getbi(r)[0])
746 b.add(getbi(r)[0])
747 c = s.__contains__
747 c = s.__contains__
748 return subset.filter(lambda r: c(r) or getbi(r)[0] in b)
748 return subset.filter(lambda r: c(r) or getbi(r)[0] in b)
749
749
750 def bumped(repo, subset, x):
750 def bumped(repo, subset, x):
751 """``bumped()``
751 """``bumped()``
752 Mutable changesets marked as successors of public changesets.
752 Mutable changesets marked as successors of public changesets.
753
753
754 Only non-public and non-obsolete changesets can be `bumped`.
754 Only non-public and non-obsolete changesets can be `bumped`.
755 """
755 """
756 # i18n: "bumped" is a keyword
756 # i18n: "bumped" is a keyword
757 getargs(x, 0, 0, _("bumped takes no arguments"))
757 getargs(x, 0, 0, _("bumped takes no arguments"))
758 bumped = obsmod.getrevs(repo, 'bumped')
758 bumped = obsmod.getrevs(repo, 'bumped')
759 return subset & bumped
759 return subset & bumped
760
760
761 def bundle(repo, subset, x):
761 def bundle(repo, subset, x):
762 """``bundle()``
762 """``bundle()``
763 Changesets in the bundle.
763 Changesets in the bundle.
764
764
765 Bundle must be specified by the -R option."""
765 Bundle must be specified by the -R option."""
766
766
767 try:
767 try:
768 bundlerevs = repo.changelog.bundlerevs
768 bundlerevs = repo.changelog.bundlerevs
769 except AttributeError:
769 except AttributeError:
770 raise util.Abort(_("no bundle provided - specify with -R"))
770 raise util.Abort(_("no bundle provided - specify with -R"))
771 return subset & bundlerevs
771 return subset & bundlerevs
772
772
773 def checkstatus(repo, subset, pat, field):
773 def checkstatus(repo, subset, pat, field):
774 hasset = matchmod.patkind(pat) == 'set'
774 hasset = matchmod.patkind(pat) == 'set'
775
775
776 mcache = [None]
776 mcache = [None]
777 def matches(x):
777 def matches(x):
778 c = repo[x]
778 c = repo[x]
779 if not mcache[0] or hasset:
779 if not mcache[0] or hasset:
780 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
780 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
781 m = mcache[0]
781 m = mcache[0]
782 fname = None
782 fname = None
783 if not m.anypats() and len(m.files()) == 1:
783 if not m.anypats() and len(m.files()) == 1:
784 fname = m.files()[0]
784 fname = m.files()[0]
785 if fname is not None:
785 if fname is not None:
786 if fname not in c.files():
786 if fname not in c.files():
787 return False
787 return False
788 else:
788 else:
789 for f in c.files():
789 for f in c.files():
790 if m(f):
790 if m(f):
791 break
791 break
792 else:
792 else:
793 return False
793 return False
794 files = repo.status(c.p1().node(), c.node())[field]
794 files = repo.status(c.p1().node(), c.node())[field]
795 if fname is not None:
795 if fname is not None:
796 if fname in files:
796 if fname in files:
797 return True
797 return True
798 else:
798 else:
799 for f in files:
799 for f in files:
800 if m(f):
800 if m(f):
801 return True
801 return True
802
802
803 return subset.filter(matches)
803 return subset.filter(matches)
804
804
805 def _children(repo, narrow, parentset):
805 def _children(repo, narrow, parentset):
806 if not parentset:
806 if not parentset:
807 return baseset()
807 return baseset()
808 cs = set()
808 cs = set()
809 pr = repo.changelog.parentrevs
809 pr = repo.changelog.parentrevs
810 minrev = parentset.min()
810 minrev = parentset.min()
811 for r in narrow:
811 for r in narrow:
812 if r <= minrev:
812 if r <= minrev:
813 continue
813 continue
814 for p in pr(r):
814 for p in pr(r):
815 if p in parentset:
815 if p in parentset:
816 cs.add(r)
816 cs.add(r)
817 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
817 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
818 # This does not break because of other fullreposet misbehavior.
818 # This does not break because of other fullreposet misbehavior.
819 return baseset(cs)
819 return baseset(cs)
820
820
821 def children(repo, subset, x):
821 def children(repo, subset, x):
822 """``children(set)``
822 """``children(set)``
823 Child changesets of changesets in set.
823 Child changesets of changesets in set.
824 """
824 """
825 s = getset(repo, fullreposet(repo), x)
825 s = getset(repo, fullreposet(repo), x)
826 cs = _children(repo, subset, s)
826 cs = _children(repo, subset, s)
827 return subset & cs
827 return subset & cs
828
828
829 def closed(repo, subset, x):
829 def closed(repo, subset, x):
830 """``closed()``
830 """``closed()``
831 Changeset is closed.
831 Changeset is closed.
832 """
832 """
833 # i18n: "closed" is a keyword
833 # i18n: "closed" is a keyword
834 getargs(x, 0, 0, _("closed takes no arguments"))
834 getargs(x, 0, 0, _("closed takes no arguments"))
835 return subset.filter(lambda r: repo[r].closesbranch())
835 return subset.filter(lambda r: repo[r].closesbranch())
836
836
837 def contains(repo, subset, x):
837 def contains(repo, subset, x):
838 """``contains(pattern)``
838 """``contains(pattern)``
839 The revision's manifest contains a file matching pattern (but might not
839 The revision's manifest contains a file matching pattern (but might not
840 modify it). See :hg:`help patterns` for information about file patterns.
840 modify it). See :hg:`help patterns` for information about file patterns.
841
841
842 The pattern without explicit kind like ``glob:`` is expected to be
842 The pattern without explicit kind like ``glob:`` is expected to be
843 relative to the current directory and match against a file exactly
843 relative to the current directory and match against a file exactly
844 for efficiency.
844 for efficiency.
845 """
845 """
846 # i18n: "contains" is a keyword
846 # i18n: "contains" is a keyword
847 pat = getstring(x, _("contains requires a pattern"))
847 pat = getstring(x, _("contains requires a pattern"))
848
848
849 def matches(x):
849 def matches(x):
850 if not matchmod.patkind(pat):
850 if not matchmod.patkind(pat):
851 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
851 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
852 if pats in repo[x]:
852 if pats in repo[x]:
853 return True
853 return True
854 else:
854 else:
855 c = repo[x]
855 c = repo[x]
856 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
856 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
857 for f in c.manifest():
857 for f in c.manifest():
858 if m(f):
858 if m(f):
859 return True
859 return True
860 return False
860 return False
861
861
862 return subset.filter(matches)
862 return subset.filter(matches)
863
863
864 def converted(repo, subset, x):
864 def converted(repo, subset, x):
865 """``converted([id])``
865 """``converted([id])``
866 Changesets converted from the given identifier in the old repository if
866 Changesets converted from the given identifier in the old repository if
867 present, or all converted changesets if no identifier is specified.
867 present, or all converted changesets if no identifier is specified.
868 """
868 """
869
869
870 # There is exactly no chance of resolving the revision, so do a simple
870 # There is exactly no chance of resolving the revision, so do a simple
871 # string compare and hope for the best
871 # string compare and hope for the best
872
872
873 rev = None
873 rev = None
874 # i18n: "converted" is a keyword
874 # i18n: "converted" is a keyword
875 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
875 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
876 if l:
876 if l:
877 # i18n: "converted" is a keyword
877 # i18n: "converted" is a keyword
878 rev = getstring(l[0], _('converted requires a revision'))
878 rev = getstring(l[0], _('converted requires a revision'))
879
879
880 def _matchvalue(r):
880 def _matchvalue(r):
881 source = repo[r].extra().get('convert_revision', None)
881 source = repo[r].extra().get('convert_revision', None)
882 return source is not None and (rev is None or source.startswith(rev))
882 return source is not None and (rev is None or source.startswith(rev))
883
883
884 return subset.filter(lambda r: _matchvalue(r))
884 return subset.filter(lambda r: _matchvalue(r))
885
885
886 def date(repo, subset, x):
886 def date(repo, subset, x):
887 """``date(interval)``
887 """``date(interval)``
888 Changesets within the interval, see :hg:`help dates`.
888 Changesets within the interval, see :hg:`help dates`.
889 """
889 """
890 # i18n: "date" is a keyword
890 # i18n: "date" is a keyword
891 ds = getstring(x, _("date requires a string"))
891 ds = getstring(x, _("date requires a string"))
892 dm = util.matchdate(ds)
892 dm = util.matchdate(ds)
893 return subset.filter(lambda x: dm(repo[x].date()[0]))
893 return subset.filter(lambda x: dm(repo[x].date()[0]))
894
894
895 def desc(repo, subset, x):
895 def desc(repo, subset, x):
896 """``desc(string)``
896 """``desc(string)``
897 Search commit message for string. The match is case-insensitive.
897 Search commit message for string. The match is case-insensitive.
898 """
898 """
899 # i18n: "desc" is a keyword
899 # i18n: "desc" is a keyword
900 ds = encoding.lower(getstring(x, _("desc requires a string")))
900 ds = encoding.lower(getstring(x, _("desc requires a string")))
901
901
902 def matches(x):
902 def matches(x):
903 c = repo[x]
903 c = repo[x]
904 return ds in encoding.lower(c.description())
904 return ds in encoding.lower(c.description())
905
905
906 return subset.filter(matches)
906 return subset.filter(matches)
907
907
908 def _descendants(repo, subset, x, followfirst=False):
908 def _descendants(repo, subset, x, followfirst=False):
909 roots = getset(repo, fullreposet(repo), x)
909 roots = getset(repo, fullreposet(repo), x)
910 if not roots:
910 if not roots:
911 return baseset()
911 return baseset()
912 s = _revdescendants(repo, roots, followfirst)
912 s = _revdescendants(repo, roots, followfirst)
913
913
914 # Both sets need to be ascending in order to lazily return the union
914 # Both sets need to be ascending in order to lazily return the union
915 # in the correct order.
915 # in the correct order.
916 base = subset & roots
916 base = subset & roots
917 desc = subset & s
917 desc = subset & s
918 result = base + desc
918 result = base + desc
919 if subset.isascending():
919 if subset.isascending():
920 result.sort()
920 result.sort()
921 elif subset.isdescending():
921 elif subset.isdescending():
922 result.sort(reverse=True)
922 result.sort(reverse=True)
923 else:
923 else:
924 result = subset & result
924 result = subset & result
925 return result
925 return result
926
926
927 def descendants(repo, subset, x):
927 def descendants(repo, subset, x):
928 """``descendants(set)``
928 """``descendants(set)``
929 Changesets which are descendants of changesets in set.
929 Changesets which are descendants of changesets in set.
930 """
930 """
931 return _descendants(repo, subset, x)
931 return _descendants(repo, subset, x)
932
932
933 def _firstdescendants(repo, subset, x):
933 def _firstdescendants(repo, subset, x):
934 # ``_firstdescendants(set)``
934 # ``_firstdescendants(set)``
935 # Like ``descendants(set)`` but follows only the first parents.
935 # Like ``descendants(set)`` but follows only the first parents.
936 return _descendants(repo, subset, x, followfirst=True)
936 return _descendants(repo, subset, x, followfirst=True)
937
937
938 def destination(repo, subset, x):
938 def destination(repo, subset, x):
939 """``destination([set])``
939 """``destination([set])``
940 Changesets that were created by a graft, transplant or rebase operation,
940 Changesets that were created by a graft, transplant or rebase operation,
941 with the given revisions specified as the source. Omitting the optional set
941 with the given revisions specified as the source. Omitting the optional set
942 is the same as passing all().
942 is the same as passing all().
943 """
943 """
944 if x is not None:
944 if x is not None:
945 sources = getset(repo, fullreposet(repo), x)
945 sources = getset(repo, fullreposet(repo), x)
946 else:
946 else:
947 sources = fullreposet(repo)
947 sources = fullreposet(repo)
948
948
949 dests = set()
949 dests = set()
950
950
951 # subset contains all of the possible destinations that can be returned, so
951 # subset contains all of the possible destinations that can be returned, so
952 # iterate over them and see if their source(s) were provided in the arg set.
952 # iterate over them and see if their source(s) were provided in the arg set.
953 # Even if the immediate src of r is not in the arg set, src's source (or
953 # Even if the immediate src of r is not in the arg set, src's source (or
954 # further back) may be. Scanning back further than the immediate src allows
954 # further back) may be. Scanning back further than the immediate src allows
955 # transitive transplants and rebases to yield the same results as transitive
955 # transitive transplants and rebases to yield the same results as transitive
956 # grafts.
956 # grafts.
957 for r in subset:
957 for r in subset:
958 src = _getrevsource(repo, r)
958 src = _getrevsource(repo, r)
959 lineage = None
959 lineage = None
960
960
961 while src is not None:
961 while src is not None:
962 if lineage is None:
962 if lineage is None:
963 lineage = list()
963 lineage = list()
964
964
965 lineage.append(r)
965 lineage.append(r)
966
966
967 # The visited lineage is a match if the current source is in the arg
967 # The visited lineage is a match if the current source is in the arg
968 # set. Since every candidate dest is visited by way of iterating
968 # set. Since every candidate dest is visited by way of iterating
969 # subset, any dests further back in the lineage will be tested by a
969 # subset, any dests further back in the lineage will be tested by a
970 # different iteration over subset. Likewise, if the src was already
970 # different iteration over subset. Likewise, if the src was already
971 # selected, the current lineage can be selected without going back
971 # selected, the current lineage can be selected without going back
972 # further.
972 # further.
973 if src in sources or src in dests:
973 if src in sources or src in dests:
974 dests.update(lineage)
974 dests.update(lineage)
975 break
975 break
976
976
977 r = src
977 r = src
978 src = _getrevsource(repo, r)
978 src = _getrevsource(repo, r)
979
979
980 return subset.filter(dests.__contains__)
980 return subset.filter(dests.__contains__)
981
981
982 def divergent(repo, subset, x):
982 def divergent(repo, subset, x):
983 """``divergent()``
983 """``divergent()``
984 Final successors of changesets with an alternative set of final successors.
984 Final successors of changesets with an alternative set of final successors.
985 """
985 """
986 # i18n: "divergent" is a keyword
986 # i18n: "divergent" is a keyword
987 getargs(x, 0, 0, _("divergent takes no arguments"))
987 getargs(x, 0, 0, _("divergent takes no arguments"))
988 divergent = obsmod.getrevs(repo, 'divergent')
988 divergent = obsmod.getrevs(repo, 'divergent')
989 return subset & divergent
989 return subset & divergent
990
990
991 def extinct(repo, subset, x):
991 def extinct(repo, subset, x):
992 """``extinct()``
992 """``extinct()``
993 Obsolete changesets with obsolete descendants only.
993 Obsolete changesets with obsolete descendants only.
994 """
994 """
995 # i18n: "extinct" is a keyword
995 # i18n: "extinct" is a keyword
996 getargs(x, 0, 0, _("extinct takes no arguments"))
996 getargs(x, 0, 0, _("extinct takes no arguments"))
997 extincts = obsmod.getrevs(repo, 'extinct')
997 extincts = obsmod.getrevs(repo, 'extinct')
998 return subset & extincts
998 return subset & extincts
999
999
1000 def extra(repo, subset, x):
1000 def extra(repo, subset, x):
1001 """``extra(label, [value])``
1001 """``extra(label, [value])``
1002 Changesets with the given label in the extra metadata, with the given
1002 Changesets with the given label in the extra metadata, with the given
1003 optional value.
1003 optional value.
1004
1004
1005 If `value` starts with `re:`, the remainder of the value is treated as
1005 If `value` starts with `re:`, the remainder of the value is treated as
1006 a regular expression. To match a value that actually starts with `re:`,
1006 a regular expression. To match a value that actually starts with `re:`,
1007 use the prefix `literal:`.
1007 use the prefix `literal:`.
1008 """
1008 """
1009 args = getargsdict(x, 'extra', 'label value')
1009 args = getargsdict(x, 'extra', 'label value')
1010 if 'label' not in args:
1010 if 'label' not in args:
1011 # i18n: "extra" is a keyword
1011 # i18n: "extra" is a keyword
1012 raise error.ParseError(_('extra takes at least 1 argument'))
1012 raise error.ParseError(_('extra takes at least 1 argument'))
1013 # i18n: "extra" is a keyword
1013 # i18n: "extra" is a keyword
1014 label = getstring(args['label'], _('first argument to extra must be '
1014 label = getstring(args['label'], _('first argument to extra must be '
1015 'a string'))
1015 'a string'))
1016 value = None
1016 value = None
1017
1017
1018 if 'value' in args:
1018 if 'value' in args:
1019 # i18n: "extra" is a keyword
1019 # i18n: "extra" is a keyword
1020 value = getstring(args['value'], _('second argument to extra must be '
1020 value = getstring(args['value'], _('second argument to extra must be '
1021 'a string'))
1021 'a string'))
1022 kind, value, matcher = _stringmatcher(value)
1022 kind, value, matcher = util.stringmatcher(value)
1023
1023
1024 def _matchvalue(r):
1024 def _matchvalue(r):
1025 extra = repo[r].extra()
1025 extra = repo[r].extra()
1026 return label in extra and (value is None or matcher(extra[label]))
1026 return label in extra and (value is None or matcher(extra[label]))
1027
1027
1028 return subset.filter(lambda r: _matchvalue(r))
1028 return subset.filter(lambda r: _matchvalue(r))
1029
1029
1030 def filelog(repo, subset, x):
1030 def filelog(repo, subset, x):
1031 """``filelog(pattern)``
1031 """``filelog(pattern)``
1032 Changesets connected to the specified filelog.
1032 Changesets connected to the specified filelog.
1033
1033
1034 For performance reasons, visits only revisions mentioned in the file-level
1034 For performance reasons, visits only revisions mentioned in the file-level
1035 filelog, rather than filtering through all changesets (much faster, but
1035 filelog, rather than filtering through all changesets (much faster, but
1036 doesn't include deletes or duplicate changes). For a slower, more accurate
1036 doesn't include deletes or duplicate changes). For a slower, more accurate
1037 result, use ``file()``.
1037 result, use ``file()``.
1038
1038
1039 The pattern without explicit kind like ``glob:`` is expected to be
1039 The pattern without explicit kind like ``glob:`` is expected to be
1040 relative to the current directory and match against a file exactly
1040 relative to the current directory and match against a file exactly
1041 for efficiency.
1041 for efficiency.
1042
1042
1043 If some linkrev points to revisions filtered by the current repoview, we'll
1043 If some linkrev points to revisions filtered by the current repoview, we'll
1044 work around it to return a non-filtered value.
1044 work around it to return a non-filtered value.
1045 """
1045 """
1046
1046
1047 # i18n: "filelog" is a keyword
1047 # i18n: "filelog" is a keyword
1048 pat = getstring(x, _("filelog requires a pattern"))
1048 pat = getstring(x, _("filelog requires a pattern"))
1049 s = set()
1049 s = set()
1050 cl = repo.changelog
1050 cl = repo.changelog
1051
1051
1052 if not matchmod.patkind(pat):
1052 if not matchmod.patkind(pat):
1053 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
1053 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
1054 files = [f]
1054 files = [f]
1055 else:
1055 else:
1056 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
1056 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
1057 files = (f for f in repo[None] if m(f))
1057 files = (f for f in repo[None] if m(f))
1058
1058
1059 for f in files:
1059 for f in files:
1060 backrevref = {} # final value for: filerev -> changerev
1060 backrevref = {} # final value for: filerev -> changerev
1061 lowestchild = {} # lowest known filerev child of a filerev
1061 lowestchild = {} # lowest known filerev child of a filerev
1062 delayed = [] # filerev with filtered linkrev, for post-processing
1062 delayed = [] # filerev with filtered linkrev, for post-processing
1063 lowesthead = None # cache for manifest content of all head revisions
1063 lowesthead = None # cache for manifest content of all head revisions
1064 fl = repo.file(f)
1064 fl = repo.file(f)
1065 for fr in list(fl):
1065 for fr in list(fl):
1066 rev = fl.linkrev(fr)
1066 rev = fl.linkrev(fr)
1067 if rev not in cl:
1067 if rev not in cl:
1068 # changerev pointed in linkrev is filtered
1068 # changerev pointed in linkrev is filtered
1069 # record it for post processing.
1069 # record it for post processing.
1070 delayed.append((fr, rev))
1070 delayed.append((fr, rev))
1071 continue
1071 continue
1072 for p in fl.parentrevs(fr):
1072 for p in fl.parentrevs(fr):
1073 if 0 <= p and p not in lowestchild:
1073 if 0 <= p and p not in lowestchild:
1074 lowestchild[p] = fr
1074 lowestchild[p] = fr
1075 backrevref[fr] = rev
1075 backrevref[fr] = rev
1076 s.add(rev)
1076 s.add(rev)
1077
1077
1078 # Post-processing of all filerevs we skipped because they were
1078 # Post-processing of all filerevs we skipped because they were
1079 # filtered. If such filerevs have known and unfiltered children, this
1079 # filtered. If such filerevs have known and unfiltered children, this
1080 # means they have an unfiltered appearance out there. We'll use linkrev
1080 # means they have an unfiltered appearance out there. We'll use linkrev
1081 # adjustment to find one of these appearances. The lowest known child
1081 # adjustment to find one of these appearances. The lowest known child
1082 # will be used as a starting point because it is the best upper-bound we
1082 # will be used as a starting point because it is the best upper-bound we
1083 # have.
1083 # have.
1084 #
1084 #
1085 # This approach will fail when an unfiltered but linkrev-shadowed
1085 # This approach will fail when an unfiltered but linkrev-shadowed
1086 # appearance exists in a head changeset without unfiltered filerev
1086 # appearance exists in a head changeset without unfiltered filerev
1087 # children anywhere.
1087 # children anywhere.
1088 while delayed:
1088 while delayed:
1089 # must be a descending iteration. To slowly fill lowest child
1089 # must be a descending iteration. To slowly fill lowest child
1090 # information that is of potential use by the next item.
1090 # information that is of potential use by the next item.
1091 fr, rev = delayed.pop()
1091 fr, rev = delayed.pop()
1092 lkr = rev
1092 lkr = rev
1093
1093
1094 child = lowestchild.get(fr)
1094 child = lowestchild.get(fr)
1095
1095
1096 if child is None:
1096 if child is None:
1097 # search for existence of this file revision in a head revision.
1097 # search for existence of this file revision in a head revision.
1098 # There are three possibilities:
1098 # There are three possibilities:
1099 # - the revision exists in a head and we can find an
1099 # - the revision exists in a head and we can find an
1100 # introduction from there,
1100 # introduction from there,
1101 # - the revision does not exist in a head because it has been
1101 # - the revision does not exist in a head because it has been
1102 # changed since its introduction: we would have found a child
1102 # changed since its introduction: we would have found a child
1103 # and be in the other 'else' clause,
1103 # and be in the other 'else' clause,
1104 # - all versions of the revision are hidden.
1104 # - all versions of the revision are hidden.
1105 if lowesthead is None:
1105 if lowesthead is None:
1106 lowesthead = {}
1106 lowesthead = {}
1107 for h in repo.heads():
1107 for h in repo.heads():
1108 fnode = repo[h].manifest().get(f)
1108 fnode = repo[h].manifest().get(f)
1109 if fnode is not None:
1109 if fnode is not None:
1110 lowesthead[fl.rev(fnode)] = h
1110 lowesthead[fl.rev(fnode)] = h
1111 headrev = lowesthead.get(fr)
1111 headrev = lowesthead.get(fr)
1112 if headrev is None:
1112 if headrev is None:
1113 # content is nowhere unfiltered
1113 # content is nowhere unfiltered
1114 continue
1114 continue
1115 rev = repo[headrev][f].introrev()
1115 rev = repo[headrev][f].introrev()
1116 else:
1116 else:
1117 # the lowest known child is a good upper bound
1117 # the lowest known child is a good upper bound
1118 childcrev = backrevref[child]
1118 childcrev = backrevref[child]
1119 # XXX this does not guarantee returning the lowest
1119 # XXX this does not guarantee returning the lowest
1120 # introduction of this revision, but this gives a
1120 # introduction of this revision, but this gives a
1121 # result which is a good start and will fit in most
1121 # result which is a good start and will fit in most
1122 # cases. We probably need to fix the multiple
1122 # cases. We probably need to fix the multiple
1123 # introductions case properly (report each
1123 # introductions case properly (report each
1124 # introduction, even for identical file revisions)
1124 # introduction, even for identical file revisions)
1125 # once and for all at some point anyway.
1125 # once and for all at some point anyway.
1126 for p in repo[childcrev][f].parents():
1126 for p in repo[childcrev][f].parents():
1127 if p.filerev() == fr:
1127 if p.filerev() == fr:
1128 rev = p.rev()
1128 rev = p.rev()
1129 break
1129 break
1130 if rev == lkr: # no shadowed entry found
1130 if rev == lkr: # no shadowed entry found
1131 # XXX This should never happen unless some manifest points
1131 # XXX This should never happen unless some manifest points
1132 # to biggish file revisions (like a revision that uses a
1132 # to biggish file revisions (like a revision that uses a
1133 # parent that never appears in the manifest ancestors)
1133 # parent that never appears in the manifest ancestors)
1134 continue
1134 continue
1135
1135
1136 # Fill the data for the next iteration.
1136 # Fill the data for the next iteration.
1137 for p in fl.parentrevs(fr):
1137 for p in fl.parentrevs(fr):
1138 if 0 <= p and p not in lowestchild:
1138 if 0 <= p and p not in lowestchild:
1139 lowestchild[p] = fr
1139 lowestchild[p] = fr
1140 backrevref[fr] = rev
1140 backrevref[fr] = rev
1141 s.add(rev)
1141 s.add(rev)
1142
1142
1143 return subset & s
1143 return subset & s
1144
1144
1145 def first(repo, subset, x):
1145 def first(repo, subset, x):
1146 """``first(set, [n])``
1146 """``first(set, [n])``
1147 An alias for limit().
1147 An alias for limit().
1148 """
1148 """
1149 return limit(repo, subset, x)
1149 return limit(repo, subset, x)
1150
1150
1151 def _follow(repo, subset, x, name, followfirst=False):
1151 def _follow(repo, subset, x, name, followfirst=False):
1152 l = getargs(x, 0, 1, _("%s takes no arguments or a pattern") % name)
1152 l = getargs(x, 0, 1, _("%s takes no arguments or a pattern") % name)
1153 c = repo['.']
1153 c = repo['.']
1154 if l:
1154 if l:
1155 x = getstring(l[0], _("%s expected a pattern") % name)
1155 x = getstring(l[0], _("%s expected a pattern") % name)
1156 matcher = matchmod.match(repo.root, repo.getcwd(), [x],
1156 matcher = matchmod.match(repo.root, repo.getcwd(), [x],
1157 ctx=repo[None], default='path')
1157 ctx=repo[None], default='path')
1158
1158
1159 s = set()
1159 s = set()
1160 for fname in c:
1160 for fname in c:
1161 if matcher(fname):
1161 if matcher(fname):
1162 fctx = c[fname]
1162 fctx = c[fname]
1163 s = s.union(set(c.rev() for c in fctx.ancestors(followfirst)))
1163 s = s.union(set(c.rev() for c in fctx.ancestors(followfirst)))
1164 # include the revision responsible for the most recent version
1164 # include the revision responsible for the most recent version
1165 s.add(fctx.introrev())
1165 s.add(fctx.introrev())
1166 else:
1166 else:
1167 s = _revancestors(repo, baseset([c.rev()]), followfirst)
1167 s = _revancestors(repo, baseset([c.rev()]), followfirst)
1168
1168
1169 return subset & s
1169 return subset & s
1170
1170
1171 def follow(repo, subset, x):
1171 def follow(repo, subset, x):
1172 """``follow([pattern])``
1172 """``follow([pattern])``
1173 An alias for ``::.`` (ancestors of the working directory's first parent).
1173 An alias for ``::.`` (ancestors of the working directory's first parent).
1174 If pattern is specified, the histories of files matching given
1174 If pattern is specified, the histories of files matching given
1175 pattern is followed, including copies.
1175 pattern is followed, including copies.
1176 """
1176 """
1177 return _follow(repo, subset, x, 'follow')
1177 return _follow(repo, subset, x, 'follow')
1178
1178
1179 def _followfirst(repo, subset, x):
1179 def _followfirst(repo, subset, x):
1180 # ``followfirst([pattern])``
1180 # ``followfirst([pattern])``
1181 # Like ``follow([pattern])`` but follows only the first parent of
1181 # Like ``follow([pattern])`` but follows only the first parent of
1182 # every revisions or files revisions.
1182 # every revisions or files revisions.
1183 return _follow(repo, subset, x, '_followfirst', followfirst=True)
1183 return _follow(repo, subset, x, '_followfirst', followfirst=True)
1184
1184
1185 def getall(repo, subset, x):
1185 def getall(repo, subset, x):
1186 """``all()``
1186 """``all()``
1187 All changesets, the same as ``0:tip``.
1187 All changesets, the same as ``0:tip``.
1188 """
1188 """
1189 # i18n: "all" is a keyword
1189 # i18n: "all" is a keyword
1190 getargs(x, 0, 0, _("all takes no arguments"))
1190 getargs(x, 0, 0, _("all takes no arguments"))
1191 return subset & spanset(repo) # drop "null" if any
1191 return subset & spanset(repo) # drop "null" if any
1192
1192
1193 def grep(repo, subset, x):
1193 def grep(repo, subset, x):
1194 """``grep(regex)``
1194 """``grep(regex)``
1195 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1195 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1196 to ensure special escape characters are handled correctly. Unlike
1196 to ensure special escape characters are handled correctly. Unlike
1197 ``keyword(string)``, the match is case-sensitive.
1197 ``keyword(string)``, the match is case-sensitive.
1198 """
1198 """
1199 try:
1199 try:
1200 # i18n: "grep" is a keyword
1200 # i18n: "grep" is a keyword
1201 gr = re.compile(getstring(x, _("grep requires a string")))
1201 gr = re.compile(getstring(x, _("grep requires a string")))
1202 except re.error as e:
1202 except re.error as e:
1203 raise error.ParseError(_('invalid match pattern: %s') % e)
1203 raise error.ParseError(_('invalid match pattern: %s') % e)
1204
1204
1205 def matches(x):
1205 def matches(x):
1206 c = repo[x]
1206 c = repo[x]
1207 for e in c.files() + [c.user(), c.description()]:
1207 for e in c.files() + [c.user(), c.description()]:
1208 if gr.search(e):
1208 if gr.search(e):
1209 return True
1209 return True
1210 return False
1210 return False
1211
1211
1212 return subset.filter(matches)
1212 return subset.filter(matches)
1213
1213
1214 def _matchfiles(repo, subset, x):
1214 def _matchfiles(repo, subset, x):
1215 # _matchfiles takes a revset list of prefixed arguments:
1215 # _matchfiles takes a revset list of prefixed arguments:
1216 #
1216 #
1217 # [p:foo, i:bar, x:baz]
1217 # [p:foo, i:bar, x:baz]
1218 #
1218 #
1219 # builds a match object from them and filters subset. Allowed
1219 # builds a match object from them and filters subset. Allowed
1220 # prefixes are 'p:' for regular patterns, 'i:' for include
1220 # prefixes are 'p:' for regular patterns, 'i:' for include
1221 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1221 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1222 # a revision identifier, or the empty string to reference the
1222 # a revision identifier, or the empty string to reference the
1223 # working directory, from which the match object is
1223 # working directory, from which the match object is
1224 # initialized. Use 'd:' to set the default matching mode, default
1224 # initialized. Use 'd:' to set the default matching mode, default
1225 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1225 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1226
1226
1227 # i18n: "_matchfiles" is a keyword
1227 # i18n: "_matchfiles" is a keyword
1228 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
1228 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
1229 pats, inc, exc = [], [], []
1229 pats, inc, exc = [], [], []
1230 rev, default = None, None
1230 rev, default = None, None
1231 for arg in l:
1231 for arg in l:
1232 # i18n: "_matchfiles" is a keyword
1232 # i18n: "_matchfiles" is a keyword
1233 s = getstring(arg, _("_matchfiles requires string arguments"))
1233 s = getstring(arg, _("_matchfiles requires string arguments"))
1234 prefix, value = s[:2], s[2:]
1234 prefix, value = s[:2], s[2:]
1235 if prefix == 'p:':
1235 if prefix == 'p:':
1236 pats.append(value)
1236 pats.append(value)
1237 elif prefix == 'i:':
1237 elif prefix == 'i:':
1238 inc.append(value)
1238 inc.append(value)
1239 elif prefix == 'x:':
1239 elif prefix == 'x:':
1240 exc.append(value)
1240 exc.append(value)
1241 elif prefix == 'r:':
1241 elif prefix == 'r:':
1242 if rev is not None:
1242 if rev is not None:
1243 # i18n: "_matchfiles" is a keyword
1243 # i18n: "_matchfiles" is a keyword
1244 raise error.ParseError(_('_matchfiles expected at most one '
1244 raise error.ParseError(_('_matchfiles expected at most one '
1245 'revision'))
1245 'revision'))
1246 if value != '': # empty means working directory; leave rev as None
1246 if value != '': # empty means working directory; leave rev as None
1247 rev = value
1247 rev = value
1248 elif prefix == 'd:':
1248 elif prefix == 'd:':
1249 if default is not None:
1249 if default is not None:
1250 # i18n: "_matchfiles" is a keyword
1250 # i18n: "_matchfiles" is a keyword
1251 raise error.ParseError(_('_matchfiles expected at most one '
1251 raise error.ParseError(_('_matchfiles expected at most one '
1252 'default mode'))
1252 'default mode'))
1253 default = value
1253 default = value
1254 else:
1254 else:
1255 # i18n: "_matchfiles" is a keyword
1255 # i18n: "_matchfiles" is a keyword
1256 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
1256 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
1257 if not default:
1257 if not default:
1258 default = 'glob'
1258 default = 'glob'
1259
1259
1260 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
1260 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
1261 exclude=exc, ctx=repo[rev], default=default)
1261 exclude=exc, ctx=repo[rev], default=default)
1262
1262
1263 def matches(x):
1263 def matches(x):
1264 for f in repo[x].files():
1264 for f in repo[x].files():
1265 if m(f):
1265 if m(f):
1266 return True
1266 return True
1267 return False
1267 return False
1268
1268
1269 return subset.filter(matches)
1269 return subset.filter(matches)
1270
1270
1271 def hasfile(repo, subset, x):
1271 def hasfile(repo, subset, x):
1272 """``file(pattern)``
1272 """``file(pattern)``
1273 Changesets affecting files matched by pattern.
1273 Changesets affecting files matched by pattern.
1274
1274
1275 For a faster but less accurate result, consider using ``filelog()``
1275 For a faster but less accurate result, consider using ``filelog()``
1276 instead.
1276 instead.
1277
1277
1278 This predicate uses ``glob:`` as the default kind of pattern.
1278 This predicate uses ``glob:`` as the default kind of pattern.
1279 """
1279 """
1280 # i18n: "file" is a keyword
1280 # i18n: "file" is a keyword
1281 pat = getstring(x, _("file requires a pattern"))
1281 pat = getstring(x, _("file requires a pattern"))
1282 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1282 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1283
1283
1284 def head(repo, subset, x):
1284 def head(repo, subset, x):
1285 """``head()``
1285 """``head()``
1286 Changeset is a named branch head.
1286 Changeset is a named branch head.
1287 """
1287 """
1288 # i18n: "head" is a keyword
1288 # i18n: "head" is a keyword
1289 getargs(x, 0, 0, _("head takes no arguments"))
1289 getargs(x, 0, 0, _("head takes no arguments"))
1290 hs = set()
1290 hs = set()
1291 cl = repo.changelog
1291 cl = repo.changelog
1292 for b, ls in repo.branchmap().iteritems():
1292 for b, ls in repo.branchmap().iteritems():
1293 hs.update(cl.rev(h) for h in ls)
1293 hs.update(cl.rev(h) for h in ls)
1294 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
1294 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
1295 # This does not break because of other fullreposet misbehavior.
1295 # This does not break because of other fullreposet misbehavior.
1296 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
1296 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
1297 # necessary to ensure we preserve the order in subset.
1297 # necessary to ensure we preserve the order in subset.
1298 return baseset(hs) & subset
1298 return baseset(hs) & subset
1299
1299
1300 def heads(repo, subset, x):
1300 def heads(repo, subset, x):
1301 """``heads(set)``
1301 """``heads(set)``
1302 Members of set with no children in set.
1302 Members of set with no children in set.
1303 """
1303 """
1304 s = getset(repo, subset, x)
1304 s = getset(repo, subset, x)
1305 ps = parents(repo, subset, x)
1305 ps = parents(repo, subset, x)
1306 return s - ps
1306 return s - ps
1307
1307
1308 def hidden(repo, subset, x):
1308 def hidden(repo, subset, x):
1309 """``hidden()``
1309 """``hidden()``
1310 Hidden changesets.
1310 Hidden changesets.
1311 """
1311 """
1312 # i18n: "hidden" is a keyword
1312 # i18n: "hidden" is a keyword
1313 getargs(x, 0, 0, _("hidden takes no arguments"))
1313 getargs(x, 0, 0, _("hidden takes no arguments"))
1314 hiddenrevs = repoview.filterrevs(repo, 'visible')
1314 hiddenrevs = repoview.filterrevs(repo, 'visible')
1315 return subset & hiddenrevs
1315 return subset & hiddenrevs
1316
1316
1317 def keyword(repo, subset, x):
1317 def keyword(repo, subset, x):
1318 """``keyword(string)``
1318 """``keyword(string)``
1319 Search commit message, user name, and names of changed files for
1319 Search commit message, user name, and names of changed files for
1320 string. The match is case-insensitive.
1320 string. The match is case-insensitive.
1321 """
1321 """
1322 # i18n: "keyword" is a keyword
1322 # i18n: "keyword" is a keyword
1323 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1323 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1324
1324
1325 def matches(r):
1325 def matches(r):
1326 c = repo[r]
1326 c = repo[r]
1327 return any(kw in encoding.lower(t)
1327 return any(kw in encoding.lower(t)
1328 for t in c.files() + [c.user(), c.description()])
1328 for t in c.files() + [c.user(), c.description()])
1329
1329
1330 return subset.filter(matches)
1330 return subset.filter(matches)
1331
1331
1332 def limit(repo, subset, x):
1332 def limit(repo, subset, x):
1333 """``limit(set, [n])``
1333 """``limit(set, [n])``
1334 First n members of set, defaulting to 1.
1334 First n members of set, defaulting to 1.
1335 """
1335 """
1336 # i18n: "limit" is a keyword
1336 # i18n: "limit" is a keyword
1337 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
1337 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
1338 try:
1338 try:
1339 lim = 1
1339 lim = 1
1340 if len(l) == 2:
1340 if len(l) == 2:
1341 # i18n: "limit" is a keyword
1341 # i18n: "limit" is a keyword
1342 lim = int(getstring(l[1], _("limit requires a number")))
1342 lim = int(getstring(l[1], _("limit requires a number")))
1343 except (TypeError, ValueError):
1343 except (TypeError, ValueError):
1344 # i18n: "limit" is a keyword
1344 # i18n: "limit" is a keyword
1345 raise error.ParseError(_("limit expects a number"))
1345 raise error.ParseError(_("limit expects a number"))
1346 ss = subset
1346 ss = subset
1347 os = getset(repo, fullreposet(repo), l[0])
1347 os = getset(repo, fullreposet(repo), l[0])
1348 result = []
1348 result = []
1349 it = iter(os)
1349 it = iter(os)
1350 for x in xrange(lim):
1350 for x in xrange(lim):
1351 y = next(it, None)
1351 y = next(it, None)
1352 if y is None:
1352 if y is None:
1353 break
1353 break
1354 elif y in ss:
1354 elif y in ss:
1355 result.append(y)
1355 result.append(y)
1356 return baseset(result)
1356 return baseset(result)
1357
1357
1358 def last(repo, subset, x):
1358 def last(repo, subset, x):
1359 """``last(set, [n])``
1359 """``last(set, [n])``
1360 Last n members of set, defaulting to 1.
1360 Last n members of set, defaulting to 1.
1361 """
1361 """
1362 # i18n: "last" is a keyword
1362 # i18n: "last" is a keyword
1363 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1363 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1364 try:
1364 try:
1365 lim = 1
1365 lim = 1
1366 if len(l) == 2:
1366 if len(l) == 2:
1367 # i18n: "last" is a keyword
1367 # i18n: "last" is a keyword
1368 lim = int(getstring(l[1], _("last requires a number")))
1368 lim = int(getstring(l[1], _("last requires a number")))
1369 except (TypeError, ValueError):
1369 except (TypeError, ValueError):
1370 # i18n: "last" is a keyword
1370 # i18n: "last" is a keyword
1371 raise error.ParseError(_("last expects a number"))
1371 raise error.ParseError(_("last expects a number"))
1372 ss = subset
1372 ss = subset
1373 os = getset(repo, fullreposet(repo), l[0])
1373 os = getset(repo, fullreposet(repo), l[0])
1374 os.reverse()
1374 os.reverse()
1375 result = []
1375 result = []
1376 it = iter(os)
1376 it = iter(os)
1377 for x in xrange(lim):
1377 for x in xrange(lim):
1378 y = next(it, None)
1378 y = next(it, None)
1379 if y is None:
1379 if y is None:
1380 break
1380 break
1381 elif y in ss:
1381 elif y in ss:
1382 result.append(y)
1382 result.append(y)
1383 return baseset(result)
1383 return baseset(result)
1384
1384
1385 def maxrev(repo, subset, x):
1385 def maxrev(repo, subset, x):
1386 """``max(set)``
1386 """``max(set)``
1387 Changeset with highest revision number in set.
1387 Changeset with highest revision number in set.
1388 """
1388 """
1389 os = getset(repo, fullreposet(repo), x)
1389 os = getset(repo, fullreposet(repo), x)
1390 try:
1390 try:
1391 m = os.max()
1391 m = os.max()
1392 if m in subset:
1392 if m in subset:
1393 return baseset([m])
1393 return baseset([m])
1394 except ValueError:
1394 except ValueError:
1395 # os.max() throws a ValueError when the collection is empty.
1395 # os.max() throws a ValueError when the collection is empty.
1396 # Same as python's max().
1396 # Same as python's max().
1397 pass
1397 pass
1398 return baseset()
1398 return baseset()
1399
1399
1400 def merge(repo, subset, x):
1400 def merge(repo, subset, x):
1401 """``merge()``
1401 """``merge()``
1402 Changeset is a merge changeset.
1402 Changeset is a merge changeset.
1403 """
1403 """
1404 # i18n: "merge" is a keyword
1404 # i18n: "merge" is a keyword
1405 getargs(x, 0, 0, _("merge takes no arguments"))
1405 getargs(x, 0, 0, _("merge takes no arguments"))
1406 cl = repo.changelog
1406 cl = repo.changelog
1407 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
1407 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
1408
1408
1409 def branchpoint(repo, subset, x):
1409 def branchpoint(repo, subset, x):
1410 """``branchpoint()``
1410 """``branchpoint()``
1411 Changesets with more than one child.
1411 Changesets with more than one child.
1412 """
1412 """
1413 # i18n: "branchpoint" is a keyword
1413 # i18n: "branchpoint" is a keyword
1414 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1414 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1415 cl = repo.changelog
1415 cl = repo.changelog
1416 if not subset:
1416 if not subset:
1417 return baseset()
1417 return baseset()
1418 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1418 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1419 # (and if it is not, it should.)
1419 # (and if it is not, it should.)
1420 baserev = min(subset)
1420 baserev = min(subset)
1421 parentscount = [0]*(len(repo) - baserev)
1421 parentscount = [0]*(len(repo) - baserev)
1422 for r in cl.revs(start=baserev + 1):
1422 for r in cl.revs(start=baserev + 1):
1423 for p in cl.parentrevs(r):
1423 for p in cl.parentrevs(r):
1424 if p >= baserev:
1424 if p >= baserev:
1425 parentscount[p - baserev] += 1
1425 parentscount[p - baserev] += 1
1426 return subset.filter(lambda r: parentscount[r - baserev] > 1)
1426 return subset.filter(lambda r: parentscount[r - baserev] > 1)
1427
1427
1428 def minrev(repo, subset, x):
1428 def minrev(repo, subset, x):
1429 """``min(set)``
1429 """``min(set)``
1430 Changeset with lowest revision number in set.
1430 Changeset with lowest revision number in set.
1431 """
1431 """
1432 os = getset(repo, fullreposet(repo), x)
1432 os = getset(repo, fullreposet(repo), x)
1433 try:
1433 try:
1434 m = os.min()
1434 m = os.min()
1435 if m in subset:
1435 if m in subset:
1436 return baseset([m])
1436 return baseset([m])
1437 except ValueError:
1437 except ValueError:
1438 # os.min() throws a ValueError when the collection is empty.
1438 # os.min() throws a ValueError when the collection is empty.
1439 # Same as python's min().
1439 # Same as python's min().
1440 pass
1440 pass
1441 return baseset()
1441 return baseset()
1442
1442
1443 def modifies(repo, subset, x):
1443 def modifies(repo, subset, x):
1444 """``modifies(pattern)``
1444 """``modifies(pattern)``
1445 Changesets modifying files matched by pattern.
1445 Changesets modifying files matched by pattern.
1446
1446
1447 The pattern without explicit kind like ``glob:`` is expected to be
1447 The pattern without explicit kind like ``glob:`` is expected to be
1448 relative to the current directory and match against a file or a
1448 relative to the current directory and match against a file or a
1449 directory.
1449 directory.
1450 """
1450 """
1451 # i18n: "modifies" is a keyword
1451 # i18n: "modifies" is a keyword
1452 pat = getstring(x, _("modifies requires a pattern"))
1452 pat = getstring(x, _("modifies requires a pattern"))
1453 return checkstatus(repo, subset, pat, 0)
1453 return checkstatus(repo, subset, pat, 0)
1454
1454
1455 def named(repo, subset, x):
1455 def named(repo, subset, x):
1456 """``named(namespace)``
1456 """``named(namespace)``
1457 The changesets in a given namespace.
1457 The changesets in a given namespace.
1458
1458
1459 If `namespace` starts with `re:`, the remainder of the string is treated as
1459 If `namespace` starts with `re:`, the remainder of the string is treated as
1460 a regular expression. To match a namespace that actually starts with `re:`,
1460 a regular expression. To match a namespace that actually starts with `re:`,
1461 use the prefix `literal:`.
1461 use the prefix `literal:`.
1462 """
1462 """
1463 # i18n: "named" is a keyword
1463 # i18n: "named" is a keyword
1464 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1464 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1465
1465
1466 ns = getstring(args[0],
1466 ns = getstring(args[0],
1467 # i18n: "named" is a keyword
1467 # i18n: "named" is a keyword
1468 _('the argument to named must be a string'))
1468 _('the argument to named must be a string'))
1469 kind, pattern, matcher = _stringmatcher(ns)
1469 kind, pattern, matcher = util.stringmatcher(ns)
1470 namespaces = set()
1470 namespaces = set()
1471 if kind == 'literal':
1471 if kind == 'literal':
1472 if pattern not in repo.names:
1472 if pattern not in repo.names:
1473 raise error.RepoLookupError(_("namespace '%s' does not exist")
1473 raise error.RepoLookupError(_("namespace '%s' does not exist")
1474 % ns)
1474 % ns)
1475 namespaces.add(repo.names[pattern])
1475 namespaces.add(repo.names[pattern])
1476 else:
1476 else:
1477 for name, ns in repo.names.iteritems():
1477 for name, ns in repo.names.iteritems():
1478 if matcher(name):
1478 if matcher(name):
1479 namespaces.add(ns)
1479 namespaces.add(ns)
1480 if not namespaces:
1480 if not namespaces:
1481 raise error.RepoLookupError(_("no namespace exists"
1481 raise error.RepoLookupError(_("no namespace exists"
1482 " that match '%s'") % pattern)
1482 " that match '%s'") % pattern)
1483
1483
1484 names = set()
1484 names = set()
1485 for ns in namespaces:
1485 for ns in namespaces:
1486 for name in ns.listnames(repo):
1486 for name in ns.listnames(repo):
1487 if name not in ns.deprecated:
1487 if name not in ns.deprecated:
1488 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1488 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1489
1489
1490 names -= set([node.nullrev])
1490 names -= set([node.nullrev])
1491 return subset & names
1491 return subset & names
1492
1492
1493 def node_(repo, subset, x):
1493 def node_(repo, subset, x):
1494 """``id(string)``
1494 """``id(string)``
1495 Revision non-ambiguously specified by the given hex string prefix.
1495 Revision non-ambiguously specified by the given hex string prefix.
1496 """
1496 """
1497 # i18n: "id" is a keyword
1497 # i18n: "id" is a keyword
1498 l = getargs(x, 1, 1, _("id requires one argument"))
1498 l = getargs(x, 1, 1, _("id requires one argument"))
1499 # i18n: "id" is a keyword
1499 # i18n: "id" is a keyword
1500 n = getstring(l[0], _("id requires a string"))
1500 n = getstring(l[0], _("id requires a string"))
1501 if len(n) == 40:
1501 if len(n) == 40:
1502 try:
1502 try:
1503 rn = repo.changelog.rev(node.bin(n))
1503 rn = repo.changelog.rev(node.bin(n))
1504 except (LookupError, TypeError):
1504 except (LookupError, TypeError):
1505 rn = None
1505 rn = None
1506 else:
1506 else:
1507 rn = None
1507 rn = None
1508 pm = repo.changelog._partialmatch(n)
1508 pm = repo.changelog._partialmatch(n)
1509 if pm is not None:
1509 if pm is not None:
1510 rn = repo.changelog.rev(pm)
1510 rn = repo.changelog.rev(pm)
1511
1511
1512 if rn is None:
1512 if rn is None:
1513 return baseset()
1513 return baseset()
1514 result = baseset([rn])
1514 result = baseset([rn])
1515 return result & subset
1515 return result & subset
1516
1516
1517 def obsolete(repo, subset, x):
1517 def obsolete(repo, subset, x):
1518 """``obsolete()``
1518 """``obsolete()``
1519 Mutable changeset with a newer version."""
1519 Mutable changeset with a newer version."""
1520 # i18n: "obsolete" is a keyword
1520 # i18n: "obsolete" is a keyword
1521 getargs(x, 0, 0, _("obsolete takes no arguments"))
1521 getargs(x, 0, 0, _("obsolete takes no arguments"))
1522 obsoletes = obsmod.getrevs(repo, 'obsolete')
1522 obsoletes = obsmod.getrevs(repo, 'obsolete')
1523 return subset & obsoletes
1523 return subset & obsoletes
1524
1524
1525 def only(repo, subset, x):
1525 def only(repo, subset, x):
1526 """``only(set, [set])``
1526 """``only(set, [set])``
1527 Changesets that are ancestors of the first set that are not ancestors
1527 Changesets that are ancestors of the first set that are not ancestors
1528 of any other head in the repo. If a second set is specified, the result
1528 of any other head in the repo. If a second set is specified, the result
1529 is ancestors of the first set that are not ancestors of the second set
1529 is ancestors of the first set that are not ancestors of the second set
1530 (i.e. ::<set1> - ::<set2>).
1530 (i.e. ::<set1> - ::<set2>).
1531 """
1531 """
1532 cl = repo.changelog
1532 cl = repo.changelog
1533 # i18n: "only" is a keyword
1533 # i18n: "only" is a keyword
1534 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1534 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1535 include = getset(repo, fullreposet(repo), args[0])
1535 include = getset(repo, fullreposet(repo), args[0])
1536 if len(args) == 1:
1536 if len(args) == 1:
1537 if not include:
1537 if not include:
1538 return baseset()
1538 return baseset()
1539
1539
1540 descendants = set(_revdescendants(repo, include, False))
1540 descendants = set(_revdescendants(repo, include, False))
1541 exclude = [rev for rev in cl.headrevs()
1541 exclude = [rev for rev in cl.headrevs()
1542 if not rev in descendants and not rev in include]
1542 if not rev in descendants and not rev in include]
1543 else:
1543 else:
1544 exclude = getset(repo, fullreposet(repo), args[1])
1544 exclude = getset(repo, fullreposet(repo), args[1])
1545
1545
1546 results = set(cl.findmissingrevs(common=exclude, heads=include))
1546 results = set(cl.findmissingrevs(common=exclude, heads=include))
1547 # XXX we should turn this into a baseset instead of a set, smartset may do
1547 # XXX we should turn this into a baseset instead of a set, smartset may do
1548 # some optimisations from the fact this is a baseset.
1548 # some optimisations from the fact this is a baseset.
1549 return subset & results
1549 return subset & results
1550
1550
1551 def origin(repo, subset, x):
1551 def origin(repo, subset, x):
1552 """``origin([set])``
1552 """``origin([set])``
1553 Changesets that were specified as a source for the grafts, transplants or
1553 Changesets that were specified as a source for the grafts, transplants or
1554 rebases that created the given revisions. Omitting the optional set is the
1554 rebases that created the given revisions. Omitting the optional set is the
1555 same as passing all(). If a changeset created by these operations is itself
1555 same as passing all(). If a changeset created by these operations is itself
1556 specified as a source for one of these operations, only the source changeset
1556 specified as a source for one of these operations, only the source changeset
1557 for the first operation is selected.
1557 for the first operation is selected.
1558 """
1558 """
1559 if x is not None:
1559 if x is not None:
1560 dests = getset(repo, fullreposet(repo), x)
1560 dests = getset(repo, fullreposet(repo), x)
1561 else:
1561 else:
1562 dests = fullreposet(repo)
1562 dests = fullreposet(repo)
1563
1563
1564 def _firstsrc(rev):
1564 def _firstsrc(rev):
1565 src = _getrevsource(repo, rev)
1565 src = _getrevsource(repo, rev)
1566 if src is None:
1566 if src is None:
1567 return None
1567 return None
1568
1568
1569 while True:
1569 while True:
1570 prev = _getrevsource(repo, src)
1570 prev = _getrevsource(repo, src)
1571
1571
1572 if prev is None:
1572 if prev is None:
1573 return src
1573 return src
1574 src = prev
1574 src = prev
1575
1575
1576 o = set([_firstsrc(r) for r in dests])
1576 o = set([_firstsrc(r) for r in dests])
1577 o -= set([None])
1577 o -= set([None])
1578 # XXX we should turn this into a baseset instead of a set, smartset may do
1578 # XXX we should turn this into a baseset instead of a set, smartset may do
1579 # some optimisations from the fact this is a baseset.
1579 # some optimisations from the fact this is a baseset.
1580 return subset & o
1580 return subset & o
1581
1581
1582 def outgoing(repo, subset, x):
1582 def outgoing(repo, subset, x):
1583 """``outgoing([path])``
1583 """``outgoing([path])``
1584 Changesets not found in the specified destination repository, or the
1584 Changesets not found in the specified destination repository, or the
1585 default push location.
1585 default push location.
1586 """
1586 """
1587 # Avoid cycles.
1587 # Avoid cycles.
1588 from . import (
1588 from . import (
1589 discovery,
1589 discovery,
1590 hg,
1590 hg,
1591 )
1591 )
1592 # i18n: "outgoing" is a keyword
1592 # i18n: "outgoing" is a keyword
1593 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1593 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1594 # i18n: "outgoing" is a keyword
1594 # i18n: "outgoing" is a keyword
1595 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1595 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1596 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1596 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1597 dest, branches = hg.parseurl(dest)
1597 dest, branches = hg.parseurl(dest)
1598 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1598 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1599 if revs:
1599 if revs:
1600 revs = [repo.lookup(rev) for rev in revs]
1600 revs = [repo.lookup(rev) for rev in revs]
1601 other = hg.peer(repo, {}, dest)
1601 other = hg.peer(repo, {}, dest)
1602 repo.ui.pushbuffer()
1602 repo.ui.pushbuffer()
1603 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1603 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1604 repo.ui.popbuffer()
1604 repo.ui.popbuffer()
1605 cl = repo.changelog
1605 cl = repo.changelog
1606 o = set([cl.rev(r) for r in outgoing.missing])
1606 o = set([cl.rev(r) for r in outgoing.missing])
1607 return subset & o
1607 return subset & o
1608
1608
1609 def p1(repo, subset, x):
1609 def p1(repo, subset, x):
1610 """``p1([set])``
1610 """``p1([set])``
1611 First parent of changesets in set, or the working directory.
1611 First parent of changesets in set, or the working directory.
1612 """
1612 """
1613 if x is None:
1613 if x is None:
1614 p = repo[x].p1().rev()
1614 p = repo[x].p1().rev()
1615 if p >= 0:
1615 if p >= 0:
1616 return subset & baseset([p])
1616 return subset & baseset([p])
1617 return baseset()
1617 return baseset()
1618
1618
1619 ps = set()
1619 ps = set()
1620 cl = repo.changelog
1620 cl = repo.changelog
1621 for r in getset(repo, fullreposet(repo), x):
1621 for r in getset(repo, fullreposet(repo), x):
1622 ps.add(cl.parentrevs(r)[0])
1622 ps.add(cl.parentrevs(r)[0])
1623 ps -= set([node.nullrev])
1623 ps -= set([node.nullrev])
1624 # XXX we should turn this into a baseset instead of a set, smartset may do
1624 # XXX we should turn this into a baseset instead of a set, smartset may do
1625 # some optimisations from the fact this is a baseset.
1625 # some optimisations from the fact this is a baseset.
1626 return subset & ps
1626 return subset & ps
1627
1627
1628 def p2(repo, subset, x):
1628 def p2(repo, subset, x):
1629 """``p2([set])``
1629 """``p2([set])``
1630 Second parent of changesets in set, or the working directory.
1630 Second parent of changesets in set, or the working directory.
1631 """
1631 """
1632 if x is None:
1632 if x is None:
1633 ps = repo[x].parents()
1633 ps = repo[x].parents()
1634 try:
1634 try:
1635 p = ps[1].rev()
1635 p = ps[1].rev()
1636 if p >= 0:
1636 if p >= 0:
1637 return subset & baseset([p])
1637 return subset & baseset([p])
1638 return baseset()
1638 return baseset()
1639 except IndexError:
1639 except IndexError:
1640 return baseset()
1640 return baseset()
1641
1641
1642 ps = set()
1642 ps = set()
1643 cl = repo.changelog
1643 cl = repo.changelog
1644 for r in getset(repo, fullreposet(repo), x):
1644 for r in getset(repo, fullreposet(repo), x):
1645 ps.add(cl.parentrevs(r)[1])
1645 ps.add(cl.parentrevs(r)[1])
1646 ps -= set([node.nullrev])
1646 ps -= set([node.nullrev])
1647 # XXX we should turn this into a baseset instead of a set, smartset may do
1647 # XXX we should turn this into a baseset instead of a set, smartset may do
1648 # some optimisations from the fact this is a baseset.
1648 # some optimisations from the fact this is a baseset.
1649 return subset & ps
1649 return subset & ps
1650
1650
1651 def parents(repo, subset, x):
1651 def parents(repo, subset, x):
1652 """``parents([set])``
1652 """``parents([set])``
1653 The set of all parents for all changesets in set, or the working directory.
1653 The set of all parents for all changesets in set, or the working directory.
1654 """
1654 """
1655 if x is None:
1655 if x is None:
1656 ps = set(p.rev() for p in repo[x].parents())
1656 ps = set(p.rev() for p in repo[x].parents())
1657 else:
1657 else:
1658 ps = set()
1658 ps = set()
1659 cl = repo.changelog
1659 cl = repo.changelog
1660 up = ps.update
1660 up = ps.update
1661 parentrevs = cl.parentrevs
1661 parentrevs = cl.parentrevs
1662 for r in getset(repo, fullreposet(repo), x):
1662 for r in getset(repo, fullreposet(repo), x):
1663 if r == node.wdirrev:
1663 if r == node.wdirrev:
1664 up(p.rev() for p in repo[r].parents())
1664 up(p.rev() for p in repo[r].parents())
1665 else:
1665 else:
1666 up(parentrevs(r))
1666 up(parentrevs(r))
1667 ps -= set([node.nullrev])
1667 ps -= set([node.nullrev])
1668 return subset & ps
1668 return subset & ps
1669
1669
1670 def _phase(repo, subset, target):
1670 def _phase(repo, subset, target):
1671 """helper to select all rev in phase <target>"""
1671 """helper to select all rev in phase <target>"""
1672 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1672 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1673 if repo._phasecache._phasesets:
1673 if repo._phasecache._phasesets:
1674 s = repo._phasecache._phasesets[target] - repo.changelog.filteredrevs
1674 s = repo._phasecache._phasesets[target] - repo.changelog.filteredrevs
1675 s = baseset(s)
1675 s = baseset(s)
1676 s.sort() # set are non ordered, so we enforce ascending
1676 s.sort() # set are non ordered, so we enforce ascending
1677 return subset & s
1677 return subset & s
1678 else:
1678 else:
1679 phase = repo._phasecache.phase
1679 phase = repo._phasecache.phase
1680 condition = lambda r: phase(repo, r) == target
1680 condition = lambda r: phase(repo, r) == target
1681 return subset.filter(condition, cache=False)
1681 return subset.filter(condition, cache=False)
1682
1682
1683 def draft(repo, subset, x):
1683 def draft(repo, subset, x):
1684 """``draft()``
1684 """``draft()``
1685 Changeset in draft phase."""
1685 Changeset in draft phase."""
1686 # i18n: "draft" is a keyword
1686 # i18n: "draft" is a keyword
1687 getargs(x, 0, 0, _("draft takes no arguments"))
1687 getargs(x, 0, 0, _("draft takes no arguments"))
1688 target = phases.draft
1688 target = phases.draft
1689 return _phase(repo, subset, target)
1689 return _phase(repo, subset, target)
1690
1690
1691 def secret(repo, subset, x):
1691 def secret(repo, subset, x):
1692 """``secret()``
1692 """``secret()``
1693 Changeset in secret phase."""
1693 Changeset in secret phase."""
1694 # i18n: "secret" is a keyword
1694 # i18n: "secret" is a keyword
1695 getargs(x, 0, 0, _("secret takes no arguments"))
1695 getargs(x, 0, 0, _("secret takes no arguments"))
1696 target = phases.secret
1696 target = phases.secret
1697 return _phase(repo, subset, target)
1697 return _phase(repo, subset, target)
1698
1698
1699 def parentspec(repo, subset, x, n):
1699 def parentspec(repo, subset, x, n):
1700 """``set^0``
1700 """``set^0``
1701 The set.
1701 The set.
1702 ``set^1`` (or ``set^``), ``set^2``
1702 ``set^1`` (or ``set^``), ``set^2``
1703 First or second parent, respectively, of all changesets in set.
1703 First or second parent, respectively, of all changesets in set.
1704 """
1704 """
1705 try:
1705 try:
1706 n = int(n[1])
1706 n = int(n[1])
1707 if n not in (0, 1, 2):
1707 if n not in (0, 1, 2):
1708 raise ValueError
1708 raise ValueError
1709 except (TypeError, ValueError):
1709 except (TypeError, ValueError):
1710 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1710 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1711 ps = set()
1711 ps = set()
1712 cl = repo.changelog
1712 cl = repo.changelog
1713 for r in getset(repo, fullreposet(repo), x):
1713 for r in getset(repo, fullreposet(repo), x):
1714 if n == 0:
1714 if n == 0:
1715 ps.add(r)
1715 ps.add(r)
1716 elif n == 1:
1716 elif n == 1:
1717 ps.add(cl.parentrevs(r)[0])
1717 ps.add(cl.parentrevs(r)[0])
1718 elif n == 2:
1718 elif n == 2:
1719 parents = cl.parentrevs(r)
1719 parents = cl.parentrevs(r)
1720 if len(parents) > 1:
1720 if len(parents) > 1:
1721 ps.add(parents[1])
1721 ps.add(parents[1])
1722 return subset & ps
1722 return subset & ps
1723
1723
1724 def present(repo, subset, x):
1724 def present(repo, subset, x):
1725 """``present(set)``
1725 """``present(set)``
1726 An empty set, if any revision in set isn't found; otherwise,
1726 An empty set, if any revision in set isn't found; otherwise,
1727 all revisions in set.
1727 all revisions in set.
1728
1728
1729 If any of specified revisions is not present in the local repository,
1729 If any of specified revisions is not present in the local repository,
1730 the query is normally aborted. But this predicate allows the query
1730 the query is normally aborted. But this predicate allows the query
1731 to continue even in such cases.
1731 to continue even in such cases.
1732 """
1732 """
1733 try:
1733 try:
1734 return getset(repo, subset, x)
1734 return getset(repo, subset, x)
1735 except error.RepoLookupError:
1735 except error.RepoLookupError:
1736 return baseset()
1736 return baseset()
1737
1737
1738 # for internal use
1738 # for internal use
1739 def _notpublic(repo, subset, x):
1739 def _notpublic(repo, subset, x):
1740 getargs(x, 0, 0, "_notpublic takes no arguments")
1740 getargs(x, 0, 0, "_notpublic takes no arguments")
1741 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1741 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1742 if repo._phasecache._phasesets:
1742 if repo._phasecache._phasesets:
1743 s = set()
1743 s = set()
1744 for u in repo._phasecache._phasesets[1:]:
1744 for u in repo._phasecache._phasesets[1:]:
1745 s.update(u)
1745 s.update(u)
1746 s = baseset(s - repo.changelog.filteredrevs)
1746 s = baseset(s - repo.changelog.filteredrevs)
1747 s.sort()
1747 s.sort()
1748 return subset & s
1748 return subset & s
1749 else:
1749 else:
1750 phase = repo._phasecache.phase
1750 phase = repo._phasecache.phase
1751 target = phases.public
1751 target = phases.public
1752 condition = lambda r: phase(repo, r) != target
1752 condition = lambda r: phase(repo, r) != target
1753 return subset.filter(condition, cache=False)
1753 return subset.filter(condition, cache=False)
1754
1754
1755 def public(repo, subset, x):
1755 def public(repo, subset, x):
1756 """``public()``
1756 """``public()``
1757 Changeset in public phase."""
1757 Changeset in public phase."""
1758 # i18n: "public" is a keyword
1758 # i18n: "public" is a keyword
1759 getargs(x, 0, 0, _("public takes no arguments"))
1759 getargs(x, 0, 0, _("public takes no arguments"))
1760 phase = repo._phasecache.phase
1760 phase = repo._phasecache.phase
1761 target = phases.public
1761 target = phases.public
1762 condition = lambda r: phase(repo, r) == target
1762 condition = lambda r: phase(repo, r) == target
1763 return subset.filter(condition, cache=False)
1763 return subset.filter(condition, cache=False)
1764
1764
1765 def remote(repo, subset, x):
1765 def remote(repo, subset, x):
1766 """``remote([id [,path]])``
1766 """``remote([id [,path]])``
1767 Local revision that corresponds to the given identifier in a
1767 Local revision that corresponds to the given identifier in a
1768 remote repository, if present. Here, the '.' identifier is a
1768 remote repository, if present. Here, the '.' identifier is a
1769 synonym for the current local branch.
1769 synonym for the current local branch.
1770 """
1770 """
1771
1771
1772 from . import hg # avoid start-up nasties
1772 from . import hg # avoid start-up nasties
1773 # i18n: "remote" is a keyword
1773 # i18n: "remote" is a keyword
1774 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1774 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1775
1775
1776 q = '.'
1776 q = '.'
1777 if len(l) > 0:
1777 if len(l) > 0:
1778 # i18n: "remote" is a keyword
1778 # i18n: "remote" is a keyword
1779 q = getstring(l[0], _("remote requires a string id"))
1779 q = getstring(l[0], _("remote requires a string id"))
1780 if q == '.':
1780 if q == '.':
1781 q = repo['.'].branch()
1781 q = repo['.'].branch()
1782
1782
1783 dest = ''
1783 dest = ''
1784 if len(l) > 1:
1784 if len(l) > 1:
1785 # i18n: "remote" is a keyword
1785 # i18n: "remote" is a keyword
1786 dest = getstring(l[1], _("remote requires a repository path"))
1786 dest = getstring(l[1], _("remote requires a repository path"))
1787 dest = repo.ui.expandpath(dest or 'default')
1787 dest = repo.ui.expandpath(dest or 'default')
1788 dest, branches = hg.parseurl(dest)
1788 dest, branches = hg.parseurl(dest)
1789 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1789 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1790 if revs:
1790 if revs:
1791 revs = [repo.lookup(rev) for rev in revs]
1791 revs = [repo.lookup(rev) for rev in revs]
1792 other = hg.peer(repo, {}, dest)
1792 other = hg.peer(repo, {}, dest)
1793 n = other.lookup(q)
1793 n = other.lookup(q)
1794 if n in repo:
1794 if n in repo:
1795 r = repo[n].rev()
1795 r = repo[n].rev()
1796 if r in subset:
1796 if r in subset:
1797 return baseset([r])
1797 return baseset([r])
1798 return baseset()
1798 return baseset()
1799
1799
1800 def removes(repo, subset, x):
1800 def removes(repo, subset, x):
1801 """``removes(pattern)``
1801 """``removes(pattern)``
1802 Changesets which remove files matching pattern.
1802 Changesets which remove files matching pattern.
1803
1803
1804 The pattern without explicit kind like ``glob:`` is expected to be
1804 The pattern without explicit kind like ``glob:`` is expected to be
1805 relative to the current directory and match against a file or a
1805 relative to the current directory and match against a file or a
1806 directory.
1806 directory.
1807 """
1807 """
1808 # i18n: "removes" is a keyword
1808 # i18n: "removes" is a keyword
1809 pat = getstring(x, _("removes requires a pattern"))
1809 pat = getstring(x, _("removes requires a pattern"))
1810 return checkstatus(repo, subset, pat, 2)
1810 return checkstatus(repo, subset, pat, 2)
1811
1811
1812 def rev(repo, subset, x):
1812 def rev(repo, subset, x):
1813 """``rev(number)``
1813 """``rev(number)``
1814 Revision with the given numeric identifier.
1814 Revision with the given numeric identifier.
1815 """
1815 """
1816 # i18n: "rev" is a keyword
1816 # i18n: "rev" is a keyword
1817 l = getargs(x, 1, 1, _("rev requires one argument"))
1817 l = getargs(x, 1, 1, _("rev requires one argument"))
1818 try:
1818 try:
1819 # i18n: "rev" is a keyword
1819 # i18n: "rev" is a keyword
1820 l = int(getstring(l[0], _("rev requires a number")))
1820 l = int(getstring(l[0], _("rev requires a number")))
1821 except (TypeError, ValueError):
1821 except (TypeError, ValueError):
1822 # i18n: "rev" is a keyword
1822 # i18n: "rev" is a keyword
1823 raise error.ParseError(_("rev expects a number"))
1823 raise error.ParseError(_("rev expects a number"))
1824 if l not in repo.changelog and l != node.nullrev:
1824 if l not in repo.changelog and l != node.nullrev:
1825 return baseset()
1825 return baseset()
1826 return subset & baseset([l])
1826 return subset & baseset([l])
1827
1827
1828 def matching(repo, subset, x):
1828 def matching(repo, subset, x):
1829 """``matching(revision [, field])``
1829 """``matching(revision [, field])``
1830 Changesets in which a given set of fields match the set of fields in the
1830 Changesets in which a given set of fields match the set of fields in the
1831 selected revision or set.
1831 selected revision or set.
1832
1832
1833 To match more than one field pass the list of fields to match separated
1833 To match more than one field pass the list of fields to match separated
1834 by spaces (e.g. ``author description``).
1834 by spaces (e.g. ``author description``).
1835
1835
1836 Valid fields are most regular revision fields and some special fields.
1836 Valid fields are most regular revision fields and some special fields.
1837
1837
1838 Regular revision fields are ``description``, ``author``, ``branch``,
1838 Regular revision fields are ``description``, ``author``, ``branch``,
1839 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1839 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1840 and ``diff``.
1840 and ``diff``.
1841 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1841 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1842 contents of the revision. Two revisions matching their ``diff`` will
1842 contents of the revision. Two revisions matching their ``diff`` will
1843 also match their ``files``.
1843 also match their ``files``.
1844
1844
1845 Special fields are ``summary`` and ``metadata``:
1845 Special fields are ``summary`` and ``metadata``:
1846 ``summary`` matches the first line of the description.
1846 ``summary`` matches the first line of the description.
1847 ``metadata`` is equivalent to matching ``description user date``
1847 ``metadata`` is equivalent to matching ``description user date``
1848 (i.e. it matches the main metadata fields).
1848 (i.e. it matches the main metadata fields).
1849
1849
1850 ``metadata`` is the default field which is used when no fields are
1850 ``metadata`` is the default field which is used when no fields are
1851 specified. You can match more than one field at a time.
1851 specified. You can match more than one field at a time.
1852 """
1852 """
1853 # i18n: "matching" is a keyword
1853 # i18n: "matching" is a keyword
1854 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1854 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1855
1855
1856 revs = getset(repo, fullreposet(repo), l[0])
1856 revs = getset(repo, fullreposet(repo), l[0])
1857
1857
1858 fieldlist = ['metadata']
1858 fieldlist = ['metadata']
1859 if len(l) > 1:
1859 if len(l) > 1:
1860 fieldlist = getstring(l[1],
1860 fieldlist = getstring(l[1],
1861 # i18n: "matching" is a keyword
1861 # i18n: "matching" is a keyword
1862 _("matching requires a string "
1862 _("matching requires a string "
1863 "as its second argument")).split()
1863 "as its second argument")).split()
1864
1864
1865 # Make sure that there are no repeated fields,
1865 # Make sure that there are no repeated fields,
1866 # expand the 'special' 'metadata' field type
1866 # expand the 'special' 'metadata' field type
1867 # and check the 'files' whenever we check the 'diff'
1867 # and check the 'files' whenever we check the 'diff'
1868 fields = []
1868 fields = []
1869 for field in fieldlist:
1869 for field in fieldlist:
1870 if field == 'metadata':
1870 if field == 'metadata':
1871 fields += ['user', 'description', 'date']
1871 fields += ['user', 'description', 'date']
1872 elif field == 'diff':
1872 elif field == 'diff':
1873 # a revision matching the diff must also match the files
1873 # a revision matching the diff must also match the files
1874 # since matching the diff is very costly, make sure to
1874 # since matching the diff is very costly, make sure to
1875 # also match the files first
1875 # also match the files first
1876 fields += ['files', 'diff']
1876 fields += ['files', 'diff']
1877 else:
1877 else:
1878 if field == 'author':
1878 if field == 'author':
1879 field = 'user'
1879 field = 'user'
1880 fields.append(field)
1880 fields.append(field)
1881 fields = set(fields)
1881 fields = set(fields)
1882 if 'summary' in fields and 'description' in fields:
1882 if 'summary' in fields and 'description' in fields:
1883 # If a revision matches its description it also matches its summary
1883 # If a revision matches its description it also matches its summary
1884 fields.discard('summary')
1884 fields.discard('summary')
1885
1885
1886 # We may want to match more than one field
1886 # We may want to match more than one field
1887 # Not all fields take the same amount of time to be matched
1887 # Not all fields take the same amount of time to be matched
1888 # Sort the selected fields in order of increasing matching cost
1888 # Sort the selected fields in order of increasing matching cost
1889 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1889 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1890 'files', 'description', 'substate', 'diff']
1890 'files', 'description', 'substate', 'diff']
1891 def fieldkeyfunc(f):
1891 def fieldkeyfunc(f):
1892 try:
1892 try:
1893 return fieldorder.index(f)
1893 return fieldorder.index(f)
1894 except ValueError:
1894 except ValueError:
1895 # assume an unknown field is very costly
1895 # assume an unknown field is very costly
1896 return len(fieldorder)
1896 return len(fieldorder)
1897 fields = list(fields)
1897 fields = list(fields)
1898 fields.sort(key=fieldkeyfunc)
1898 fields.sort(key=fieldkeyfunc)
1899
1899
1900 # Each field will be matched with its own "getfield" function
1900 # Each field will be matched with its own "getfield" function
1901 # which will be added to the getfieldfuncs array of functions
1901 # which will be added to the getfieldfuncs array of functions
1902 getfieldfuncs = []
1902 getfieldfuncs = []
1903 _funcs = {
1903 _funcs = {
1904 'user': lambda r: repo[r].user(),
1904 'user': lambda r: repo[r].user(),
1905 'branch': lambda r: repo[r].branch(),
1905 'branch': lambda r: repo[r].branch(),
1906 'date': lambda r: repo[r].date(),
1906 'date': lambda r: repo[r].date(),
1907 'description': lambda r: repo[r].description(),
1907 'description': lambda r: repo[r].description(),
1908 'files': lambda r: repo[r].files(),
1908 'files': lambda r: repo[r].files(),
1909 'parents': lambda r: repo[r].parents(),
1909 'parents': lambda r: repo[r].parents(),
1910 'phase': lambda r: repo[r].phase(),
1910 'phase': lambda r: repo[r].phase(),
1911 'substate': lambda r: repo[r].substate,
1911 'substate': lambda r: repo[r].substate,
1912 'summary': lambda r: repo[r].description().splitlines()[0],
1912 'summary': lambda r: repo[r].description().splitlines()[0],
1913 'diff': lambda r: list(repo[r].diff(git=True),)
1913 'diff': lambda r: list(repo[r].diff(git=True),)
1914 }
1914 }
1915 for info in fields:
1915 for info in fields:
1916 getfield = _funcs.get(info, None)
1916 getfield = _funcs.get(info, None)
1917 if getfield is None:
1917 if getfield is None:
1918 raise error.ParseError(
1918 raise error.ParseError(
1919 # i18n: "matching" is a keyword
1919 # i18n: "matching" is a keyword
1920 _("unexpected field name passed to matching: %s") % info)
1920 _("unexpected field name passed to matching: %s") % info)
1921 getfieldfuncs.append(getfield)
1921 getfieldfuncs.append(getfield)
1922 # convert the getfield array of functions into a "getinfo" function
1922 # convert the getfield array of functions into a "getinfo" function
1923 # which returns an array of field values (or a single value if there
1923 # which returns an array of field values (or a single value if there
1924 # is only one field to match)
1924 # is only one field to match)
1925 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1925 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1926
1926
1927 def matches(x):
1927 def matches(x):
1928 for rev in revs:
1928 for rev in revs:
1929 target = getinfo(rev)
1929 target = getinfo(rev)
1930 match = True
1930 match = True
1931 for n, f in enumerate(getfieldfuncs):
1931 for n, f in enumerate(getfieldfuncs):
1932 if target[n] != f(x):
1932 if target[n] != f(x):
1933 match = False
1933 match = False
1934 if match:
1934 if match:
1935 return True
1935 return True
1936 return False
1936 return False
1937
1937
1938 return subset.filter(matches)
1938 return subset.filter(matches)
1939
1939
1940 def reverse(repo, subset, x):
1940 def reverse(repo, subset, x):
1941 """``reverse(set)``
1941 """``reverse(set)``
1942 Reverse order of set.
1942 Reverse order of set.
1943 """
1943 """
1944 l = getset(repo, subset, x)
1944 l = getset(repo, subset, x)
1945 l.reverse()
1945 l.reverse()
1946 return l
1946 return l
1947
1947
1948 def roots(repo, subset, x):
1948 def roots(repo, subset, x):
1949 """``roots(set)``
1949 """``roots(set)``
1950 Changesets in set with no parent changeset in set.
1950 Changesets in set with no parent changeset in set.
1951 """
1951 """
1952 s = getset(repo, fullreposet(repo), x)
1952 s = getset(repo, fullreposet(repo), x)
1953 parents = repo.changelog.parentrevs
1953 parents = repo.changelog.parentrevs
1954 def filter(r):
1954 def filter(r):
1955 for p in parents(r):
1955 for p in parents(r):
1956 if 0 <= p and p in s:
1956 if 0 <= p and p in s:
1957 return False
1957 return False
1958 return True
1958 return True
1959 return subset & s.filter(filter)
1959 return subset & s.filter(filter)
1960
1960
1961 def sort(repo, subset, x):
1961 def sort(repo, subset, x):
1962 """``sort(set[, [-]key...])``
1962 """``sort(set[, [-]key...])``
1963 Sort set by keys. The default sort order is ascending, specify a key
1963 Sort set by keys. The default sort order is ascending, specify a key
1964 as ``-key`` to sort in descending order.
1964 as ``-key`` to sort in descending order.
1965
1965
1966 The keys can be:
1966 The keys can be:
1967
1967
1968 - ``rev`` for the revision number,
1968 - ``rev`` for the revision number,
1969 - ``branch`` for the branch name,
1969 - ``branch`` for the branch name,
1970 - ``desc`` for the commit message (description),
1970 - ``desc`` for the commit message (description),
1971 - ``user`` for user name (``author`` can be used as an alias),
1971 - ``user`` for user name (``author`` can be used as an alias),
1972 - ``date`` for the commit date
1972 - ``date`` for the commit date
1973 """
1973 """
1974 # i18n: "sort" is a keyword
1974 # i18n: "sort" is a keyword
1975 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1975 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1976 keys = "rev"
1976 keys = "rev"
1977 if len(l) == 2:
1977 if len(l) == 2:
1978 # i18n: "sort" is a keyword
1978 # i18n: "sort" is a keyword
1979 keys = getstring(l[1], _("sort spec must be a string"))
1979 keys = getstring(l[1], _("sort spec must be a string"))
1980
1980
1981 s = l[0]
1981 s = l[0]
1982 keys = keys.split()
1982 keys = keys.split()
1983 l = []
1983 l = []
1984 def invert(s):
1984 def invert(s):
1985 return "".join(chr(255 - ord(c)) for c in s)
1985 return "".join(chr(255 - ord(c)) for c in s)
1986 revs = getset(repo, subset, s)
1986 revs = getset(repo, subset, s)
1987 if keys == ["rev"]:
1987 if keys == ["rev"]:
1988 revs.sort()
1988 revs.sort()
1989 return revs
1989 return revs
1990 elif keys == ["-rev"]:
1990 elif keys == ["-rev"]:
1991 revs.sort(reverse=True)
1991 revs.sort(reverse=True)
1992 return revs
1992 return revs
1993 for r in revs:
1993 for r in revs:
1994 c = repo[r]
1994 c = repo[r]
1995 e = []
1995 e = []
1996 for k in keys:
1996 for k in keys:
1997 if k == 'rev':
1997 if k == 'rev':
1998 e.append(r)
1998 e.append(r)
1999 elif k == '-rev':
1999 elif k == '-rev':
2000 e.append(-r)
2000 e.append(-r)
2001 elif k == 'branch':
2001 elif k == 'branch':
2002 e.append(c.branch())
2002 e.append(c.branch())
2003 elif k == '-branch':
2003 elif k == '-branch':
2004 e.append(invert(c.branch()))
2004 e.append(invert(c.branch()))
2005 elif k == 'desc':
2005 elif k == 'desc':
2006 e.append(c.description())
2006 e.append(c.description())
2007 elif k == '-desc':
2007 elif k == '-desc':
2008 e.append(invert(c.description()))
2008 e.append(invert(c.description()))
2009 elif k in 'user author':
2009 elif k in 'user author':
2010 e.append(c.user())
2010 e.append(c.user())
2011 elif k in '-user -author':
2011 elif k in '-user -author':
2012 e.append(invert(c.user()))
2012 e.append(invert(c.user()))
2013 elif k == 'date':
2013 elif k == 'date':
2014 e.append(c.date()[0])
2014 e.append(c.date()[0])
2015 elif k == '-date':
2015 elif k == '-date':
2016 e.append(-c.date()[0])
2016 e.append(-c.date()[0])
2017 else:
2017 else:
2018 raise error.ParseError(_("unknown sort key %r") % k)
2018 raise error.ParseError(_("unknown sort key %r") % k)
2019 e.append(r)
2019 e.append(r)
2020 l.append(e)
2020 l.append(e)
2021 l.sort()
2021 l.sort()
2022 return baseset([e[-1] for e in l])
2022 return baseset([e[-1] for e in l])
2023
2023
2024 def subrepo(repo, subset, x):
2024 def subrepo(repo, subset, x):
2025 """``subrepo([pattern])``
2025 """``subrepo([pattern])``
2026 Changesets that add, modify or remove the given subrepo. If no subrepo
2026 Changesets that add, modify or remove the given subrepo. If no subrepo
2027 pattern is named, any subrepo changes are returned.
2027 pattern is named, any subrepo changes are returned.
2028 """
2028 """
2029 # i18n: "subrepo" is a keyword
2029 # i18n: "subrepo" is a keyword
2030 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
2030 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
2031 if len(args) != 0:
2031 if len(args) != 0:
2032 pat = getstring(args[0], _("subrepo requires a pattern"))
2032 pat = getstring(args[0], _("subrepo requires a pattern"))
2033
2033
2034 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
2034 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
2035
2035
2036 def submatches(names):
2036 def submatches(names):
2037 k, p, m = _stringmatcher(pat)
2037 k, p, m = util.stringmatcher(pat)
2038 for name in names:
2038 for name in names:
2039 if m(name):
2039 if m(name):
2040 yield name
2040 yield name
2041
2041
2042 def matches(x):
2042 def matches(x):
2043 c = repo[x]
2043 c = repo[x]
2044 s = repo.status(c.p1().node(), c.node(), match=m)
2044 s = repo.status(c.p1().node(), c.node(), match=m)
2045
2045
2046 if len(args) == 0:
2046 if len(args) == 0:
2047 return s.added or s.modified or s.removed
2047 return s.added or s.modified or s.removed
2048
2048
2049 if s.added:
2049 if s.added:
2050 return any(submatches(c.substate.keys()))
2050 return any(submatches(c.substate.keys()))
2051
2051
2052 if s.modified:
2052 if s.modified:
2053 subs = set(c.p1().substate.keys())
2053 subs = set(c.p1().substate.keys())
2054 subs.update(c.substate.keys())
2054 subs.update(c.substate.keys())
2055
2055
2056 for path in submatches(subs):
2056 for path in submatches(subs):
2057 if c.p1().substate.get(path) != c.substate.get(path):
2057 if c.p1().substate.get(path) != c.substate.get(path):
2058 return True
2058 return True
2059
2059
2060 if s.removed:
2060 if s.removed:
2061 return any(submatches(c.p1().substate.keys()))
2061 return any(submatches(c.p1().substate.keys()))
2062
2062
2063 return False
2063 return False
2064
2064
2065 return subset.filter(matches)
2065 return subset.filter(matches)
2066
2066
2067 def _stringmatcher(pattern):
2068 """
2069 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2070 returns the matcher name, pattern, and matcher function.
2071 missing or unknown prefixes are treated as literal matches.
2072
2073 helper for tests:
2074 >>> def test(pattern, *tests):
2075 ... kind, pattern, matcher = _stringmatcher(pattern)
2076 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2077
2078 exact matching (no prefix):
2079 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2080 ('literal', 'abcdefg', [False, False, True])
2081
2082 regex matching ('re:' prefix)
2083 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2084 ('re', 'a.+b', [False, False, True])
2085
2086 force exact matches ('literal:' prefix)
2087 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2088 ('literal', 're:foobar', [False, True])
2089
2090 unknown prefixes are ignored and treated as literals
2091 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2092 ('literal', 'foo:bar', [False, False, True])
2093 """
2094 if pattern.startswith('re:'):
2095 pattern = pattern[3:]
2096 try:
2097 regex = re.compile(pattern)
2098 except re.error as e:
2099 raise error.ParseError(_('invalid regular expression: %s')
2100 % e)
2101 return 're', pattern, regex.search
2102 elif pattern.startswith('literal:'):
2103 pattern = pattern[8:]
2104 return 'literal', pattern, pattern.__eq__
2105
2106 def _substringmatcher(pattern):
2067 def _substringmatcher(pattern):
2107 kind, pattern, matcher = _stringmatcher(pattern)
2068 kind, pattern, matcher = util.stringmatcher(pattern)
2108 if kind == 'literal':
2069 if kind == 'literal':
2109 matcher = lambda s: pattern in s
2070 matcher = lambda s: pattern in s
2110 return kind, pattern, matcher
2071 return kind, pattern, matcher
2111
2072
2112 def tag(repo, subset, x):
2073 def tag(repo, subset, x):
2113 """``tag([name])``
2074 """``tag([name])``
2114 The specified tag by name, or all tagged revisions if no name is given.
2075 The specified tag by name, or all tagged revisions if no name is given.
2115
2076
2116 If `name` starts with `re:`, the remainder of the name is treated as
2077 If `name` starts with `re:`, the remainder of the name is treated as
2117 a regular expression. To match a tag that actually starts with `re:`,
2078 a regular expression. To match a tag that actually starts with `re:`,
2118 use the prefix `literal:`.
2079 use the prefix `literal:`.
2119 """
2080 """
2120 # i18n: "tag" is a keyword
2081 # i18n: "tag" is a keyword
2121 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
2082 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
2122 cl = repo.changelog
2083 cl = repo.changelog
2123 if args:
2084 if args:
2124 pattern = getstring(args[0],
2085 pattern = getstring(args[0],
2125 # i18n: "tag" is a keyword
2086 # i18n: "tag" is a keyword
2126 _('the argument to tag must be a string'))
2087 _('the argument to tag must be a string'))
2127 kind, pattern, matcher = _stringmatcher(pattern)
2088 kind, pattern, matcher = util.stringmatcher(pattern)
2128 if kind == 'literal':
2089 if kind == 'literal':
2129 # avoid resolving all tags
2090 # avoid resolving all tags
2130 tn = repo._tagscache.tags.get(pattern, None)
2091 tn = repo._tagscache.tags.get(pattern, None)
2131 if tn is None:
2092 if tn is None:
2132 raise error.RepoLookupError(_("tag '%s' does not exist")
2093 raise error.RepoLookupError(_("tag '%s' does not exist")
2133 % pattern)
2094 % pattern)
2134 s = set([repo[tn].rev()])
2095 s = set([repo[tn].rev()])
2135 else:
2096 else:
2136 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
2097 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
2137 else:
2098 else:
2138 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
2099 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
2139 return subset & s
2100 return subset & s
2140
2101
2141 def tagged(repo, subset, x):
2102 def tagged(repo, subset, x):
2142 return tag(repo, subset, x)
2103 return tag(repo, subset, x)
2143
2104
2144 def unstable(repo, subset, x):
2105 def unstable(repo, subset, x):
2145 """``unstable()``
2106 """``unstable()``
2146 Non-obsolete changesets with obsolete ancestors.
2107 Non-obsolete changesets with obsolete ancestors.
2147 """
2108 """
2148 # i18n: "unstable" is a keyword
2109 # i18n: "unstable" is a keyword
2149 getargs(x, 0, 0, _("unstable takes no arguments"))
2110 getargs(x, 0, 0, _("unstable takes no arguments"))
2150 unstables = obsmod.getrevs(repo, 'unstable')
2111 unstables = obsmod.getrevs(repo, 'unstable')
2151 return subset & unstables
2112 return subset & unstables
2152
2113
2153
2114
2154 def user(repo, subset, x):
2115 def user(repo, subset, x):
2155 """``user(string)``
2116 """``user(string)``
2156 User name contains string. The match is case-insensitive.
2117 User name contains string. The match is case-insensitive.
2157
2118
2158 If `string` starts with `re:`, the remainder of the string is treated as
2119 If `string` starts with `re:`, the remainder of the string is treated as
2159 a regular expression. To match a user that actually contains `re:`, use
2120 a regular expression. To match a user that actually contains `re:`, use
2160 the prefix `literal:`.
2121 the prefix `literal:`.
2161 """
2122 """
2162 return author(repo, subset, x)
2123 return author(repo, subset, x)
2163
2124
2164 # experimental
2125 # experimental
2165 def wdir(repo, subset, x):
2126 def wdir(repo, subset, x):
2166 # i18n: "wdir" is a keyword
2127 # i18n: "wdir" is a keyword
2167 getargs(x, 0, 0, _("wdir takes no arguments"))
2128 getargs(x, 0, 0, _("wdir takes no arguments"))
2168 if node.wdirrev in subset or isinstance(subset, fullreposet):
2129 if node.wdirrev in subset or isinstance(subset, fullreposet):
2169 return baseset([node.wdirrev])
2130 return baseset([node.wdirrev])
2170 return baseset()
2131 return baseset()
2171
2132
2172 # for internal use
2133 # for internal use
2173 def _list(repo, subset, x):
2134 def _list(repo, subset, x):
2174 s = getstring(x, "internal error")
2135 s = getstring(x, "internal error")
2175 if not s:
2136 if not s:
2176 return baseset()
2137 return baseset()
2177 # remove duplicates here. it's difficult for caller to deduplicate sets
2138 # remove duplicates here. it's difficult for caller to deduplicate sets
2178 # because different symbols can point to the same rev.
2139 # because different symbols can point to the same rev.
2179 cl = repo.changelog
2140 cl = repo.changelog
2180 ls = []
2141 ls = []
2181 seen = set()
2142 seen = set()
2182 for t in s.split('\0'):
2143 for t in s.split('\0'):
2183 try:
2144 try:
2184 # fast path for integer revision
2145 # fast path for integer revision
2185 r = int(t)
2146 r = int(t)
2186 if str(r) != t or r not in cl:
2147 if str(r) != t or r not in cl:
2187 raise ValueError
2148 raise ValueError
2188 revs = [r]
2149 revs = [r]
2189 except ValueError:
2150 except ValueError:
2190 revs = stringset(repo, subset, t)
2151 revs = stringset(repo, subset, t)
2191
2152
2192 for r in revs:
2153 for r in revs:
2193 if r in seen:
2154 if r in seen:
2194 continue
2155 continue
2195 if (r in subset
2156 if (r in subset
2196 or r == node.nullrev and isinstance(subset, fullreposet)):
2157 or r == node.nullrev and isinstance(subset, fullreposet)):
2197 ls.append(r)
2158 ls.append(r)
2198 seen.add(r)
2159 seen.add(r)
2199 return baseset(ls)
2160 return baseset(ls)
2200
2161
2201 # for internal use
2162 # for internal use
2202 def _intlist(repo, subset, x):
2163 def _intlist(repo, subset, x):
2203 s = getstring(x, "internal error")
2164 s = getstring(x, "internal error")
2204 if not s:
2165 if not s:
2205 return baseset()
2166 return baseset()
2206 ls = [int(r) for r in s.split('\0')]
2167 ls = [int(r) for r in s.split('\0')]
2207 s = subset
2168 s = subset
2208 return baseset([r for r in ls if r in s])
2169 return baseset([r for r in ls if r in s])
2209
2170
2210 # for internal use
2171 # for internal use
2211 def _hexlist(repo, subset, x):
2172 def _hexlist(repo, subset, x):
2212 s = getstring(x, "internal error")
2173 s = getstring(x, "internal error")
2213 if not s:
2174 if not s:
2214 return baseset()
2175 return baseset()
2215 cl = repo.changelog
2176 cl = repo.changelog
2216 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
2177 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
2217 s = subset
2178 s = subset
2218 return baseset([r for r in ls if r in s])
2179 return baseset([r for r in ls if r in s])
2219
2180
2220 symbols = {
2181 symbols = {
2221 "_mergedefaultdest": _mergedefaultdest,
2182 "_mergedefaultdest": _mergedefaultdest,
2222 "_updatedefaultdest": _updatedefaultdest,
2183 "_updatedefaultdest": _updatedefaultdest,
2223 "adds": adds,
2184 "adds": adds,
2224 "all": getall,
2185 "all": getall,
2225 "ancestor": ancestor,
2186 "ancestor": ancestor,
2226 "ancestors": ancestors,
2187 "ancestors": ancestors,
2227 "_firstancestors": _firstancestors,
2188 "_firstancestors": _firstancestors,
2228 "author": author,
2189 "author": author,
2229 "bisect": bisect,
2190 "bisect": bisect,
2230 "bisected": bisected,
2191 "bisected": bisected,
2231 "bookmark": bookmark,
2192 "bookmark": bookmark,
2232 "branch": branch,
2193 "branch": branch,
2233 "branchpoint": branchpoint,
2194 "branchpoint": branchpoint,
2234 "bumped": bumped,
2195 "bumped": bumped,
2235 "bundle": bundle,
2196 "bundle": bundle,
2236 "children": children,
2197 "children": children,
2237 "closed": closed,
2198 "closed": closed,
2238 "contains": contains,
2199 "contains": contains,
2239 "converted": converted,
2200 "converted": converted,
2240 "date": date,
2201 "date": date,
2241 "desc": desc,
2202 "desc": desc,
2242 "descendants": descendants,
2203 "descendants": descendants,
2243 "_firstdescendants": _firstdescendants,
2204 "_firstdescendants": _firstdescendants,
2244 "destination": destination,
2205 "destination": destination,
2245 "divergent": divergent,
2206 "divergent": divergent,
2246 "draft": draft,
2207 "draft": draft,
2247 "extinct": extinct,
2208 "extinct": extinct,
2248 "extra": extra,
2209 "extra": extra,
2249 "file": hasfile,
2210 "file": hasfile,
2250 "filelog": filelog,
2211 "filelog": filelog,
2251 "first": first,
2212 "first": first,
2252 "follow": follow,
2213 "follow": follow,
2253 "_followfirst": _followfirst,
2214 "_followfirst": _followfirst,
2254 "grep": grep,
2215 "grep": grep,
2255 "head": head,
2216 "head": head,
2256 "heads": heads,
2217 "heads": heads,
2257 "hidden": hidden,
2218 "hidden": hidden,
2258 "id": node_,
2219 "id": node_,
2259 "keyword": keyword,
2220 "keyword": keyword,
2260 "last": last,
2221 "last": last,
2261 "limit": limit,
2222 "limit": limit,
2262 "_matchfiles": _matchfiles,
2223 "_matchfiles": _matchfiles,
2263 "max": maxrev,
2224 "max": maxrev,
2264 "merge": merge,
2225 "merge": merge,
2265 "min": minrev,
2226 "min": minrev,
2266 "modifies": modifies,
2227 "modifies": modifies,
2267 "named": named,
2228 "named": named,
2268 "obsolete": obsolete,
2229 "obsolete": obsolete,
2269 "only": only,
2230 "only": only,
2270 "origin": origin,
2231 "origin": origin,
2271 "outgoing": outgoing,
2232 "outgoing": outgoing,
2272 "p1": p1,
2233 "p1": p1,
2273 "p2": p2,
2234 "p2": p2,
2274 "parents": parents,
2235 "parents": parents,
2275 "present": present,
2236 "present": present,
2276 "public": public,
2237 "public": public,
2277 "_notpublic": _notpublic,
2238 "_notpublic": _notpublic,
2278 "remote": remote,
2239 "remote": remote,
2279 "removes": removes,
2240 "removes": removes,
2280 "rev": rev,
2241 "rev": rev,
2281 "reverse": reverse,
2242 "reverse": reverse,
2282 "roots": roots,
2243 "roots": roots,
2283 "sort": sort,
2244 "sort": sort,
2284 "secret": secret,
2245 "secret": secret,
2285 "subrepo": subrepo,
2246 "subrepo": subrepo,
2286 "matching": matching,
2247 "matching": matching,
2287 "tag": tag,
2248 "tag": tag,
2288 "tagged": tagged,
2249 "tagged": tagged,
2289 "user": user,
2250 "user": user,
2290 "unstable": unstable,
2251 "unstable": unstable,
2291 "wdir": wdir,
2252 "wdir": wdir,
2292 "_list": _list,
2253 "_list": _list,
2293 "_intlist": _intlist,
2254 "_intlist": _intlist,
2294 "_hexlist": _hexlist,
2255 "_hexlist": _hexlist,
2295 }
2256 }
2296
2257
2297 # symbols which can't be used for a DoS attack for any given input
2258 # symbols which can't be used for a DoS attack for any given input
2298 # (e.g. those which accept regexes as plain strings shouldn't be included)
2259 # (e.g. those which accept regexes as plain strings shouldn't be included)
2299 # functions that just return a lot of changesets (like all) don't count here
2260 # functions that just return a lot of changesets (like all) don't count here
2300 safesymbols = set([
2261 safesymbols = set([
2301 "adds",
2262 "adds",
2302 "all",
2263 "all",
2303 "ancestor",
2264 "ancestor",
2304 "ancestors",
2265 "ancestors",
2305 "_firstancestors",
2266 "_firstancestors",
2306 "author",
2267 "author",
2307 "bisect",
2268 "bisect",
2308 "bisected",
2269 "bisected",
2309 "bookmark",
2270 "bookmark",
2310 "branch",
2271 "branch",
2311 "branchpoint",
2272 "branchpoint",
2312 "bumped",
2273 "bumped",
2313 "bundle",
2274 "bundle",
2314 "children",
2275 "children",
2315 "closed",
2276 "closed",
2316 "converted",
2277 "converted",
2317 "date",
2278 "date",
2318 "desc",
2279 "desc",
2319 "descendants",
2280 "descendants",
2320 "_firstdescendants",
2281 "_firstdescendants",
2321 "destination",
2282 "destination",
2322 "divergent",
2283 "divergent",
2323 "draft",
2284 "draft",
2324 "extinct",
2285 "extinct",
2325 "extra",
2286 "extra",
2326 "file",
2287 "file",
2327 "filelog",
2288 "filelog",
2328 "first",
2289 "first",
2329 "follow",
2290 "follow",
2330 "_followfirst",
2291 "_followfirst",
2331 "head",
2292 "head",
2332 "heads",
2293 "heads",
2333 "hidden",
2294 "hidden",
2334 "id",
2295 "id",
2335 "keyword",
2296 "keyword",
2336 "last",
2297 "last",
2337 "limit",
2298 "limit",
2338 "_matchfiles",
2299 "_matchfiles",
2339 "max",
2300 "max",
2340 "merge",
2301 "merge",
2341 "min",
2302 "min",
2342 "modifies",
2303 "modifies",
2343 "obsolete",
2304 "obsolete",
2344 "only",
2305 "only",
2345 "origin",
2306 "origin",
2346 "outgoing",
2307 "outgoing",
2347 "p1",
2308 "p1",
2348 "p2",
2309 "p2",
2349 "parents",
2310 "parents",
2350 "present",
2311 "present",
2351 "public",
2312 "public",
2352 "_notpublic",
2313 "_notpublic",
2353 "remote",
2314 "remote",
2354 "removes",
2315 "removes",
2355 "rev",
2316 "rev",
2356 "reverse",
2317 "reverse",
2357 "roots",
2318 "roots",
2358 "sort",
2319 "sort",
2359 "secret",
2320 "secret",
2360 "matching",
2321 "matching",
2361 "tag",
2322 "tag",
2362 "tagged",
2323 "tagged",
2363 "user",
2324 "user",
2364 "unstable",
2325 "unstable",
2365 "wdir",
2326 "wdir",
2366 "_list",
2327 "_list",
2367 "_intlist",
2328 "_intlist",
2368 "_hexlist",
2329 "_hexlist",
2369 ])
2330 ])
2370
2331
2371 methods = {
2332 methods = {
2372 "range": rangeset,
2333 "range": rangeset,
2373 "dagrange": dagrange,
2334 "dagrange": dagrange,
2374 "string": stringset,
2335 "string": stringset,
2375 "symbol": stringset,
2336 "symbol": stringset,
2376 "and": andset,
2337 "and": andset,
2377 "or": orset,
2338 "or": orset,
2378 "not": notset,
2339 "not": notset,
2379 "list": listset,
2340 "list": listset,
2380 "keyvalue": keyvaluepair,
2341 "keyvalue": keyvaluepair,
2381 "func": func,
2342 "func": func,
2382 "ancestor": ancestorspec,
2343 "ancestor": ancestorspec,
2383 "parent": parentspec,
2344 "parent": parentspec,
2384 "parentpost": p1,
2345 "parentpost": p1,
2385 }
2346 }
2386
2347
2387 def optimize(x, small):
2348 def optimize(x, small):
2388 if x is None:
2349 if x is None:
2389 return 0, x
2350 return 0, x
2390
2351
2391 smallbonus = 1
2352 smallbonus = 1
2392 if small:
2353 if small:
2393 smallbonus = .5
2354 smallbonus = .5
2394
2355
2395 op = x[0]
2356 op = x[0]
2396 if op == 'minus':
2357 if op == 'minus':
2397 return optimize(('and', x[1], ('not', x[2])), small)
2358 return optimize(('and', x[1], ('not', x[2])), small)
2398 elif op == 'only':
2359 elif op == 'only':
2399 return optimize(('func', ('symbol', 'only'),
2360 return optimize(('func', ('symbol', 'only'),
2400 ('list', x[1], x[2])), small)
2361 ('list', x[1], x[2])), small)
2401 elif op == 'onlypost':
2362 elif op == 'onlypost':
2402 return optimize(('func', ('symbol', 'only'), x[1]), small)
2363 return optimize(('func', ('symbol', 'only'), x[1]), small)
2403 elif op == 'dagrangepre':
2364 elif op == 'dagrangepre':
2404 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
2365 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
2405 elif op == 'dagrangepost':
2366 elif op == 'dagrangepost':
2406 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
2367 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
2407 elif op == 'rangeall':
2368 elif op == 'rangeall':
2408 return optimize(('range', ('string', '0'), ('string', 'tip')), small)
2369 return optimize(('range', ('string', '0'), ('string', 'tip')), small)
2409 elif op == 'rangepre':
2370 elif op == 'rangepre':
2410 return optimize(('range', ('string', '0'), x[1]), small)
2371 return optimize(('range', ('string', '0'), x[1]), small)
2411 elif op == 'rangepost':
2372 elif op == 'rangepost':
2412 return optimize(('range', x[1], ('string', 'tip')), small)
2373 return optimize(('range', x[1], ('string', 'tip')), small)
2413 elif op == 'negate':
2374 elif op == 'negate':
2414 return optimize(('string',
2375 return optimize(('string',
2415 '-' + getstring(x[1], _("can't negate that"))), small)
2376 '-' + getstring(x[1], _("can't negate that"))), small)
2416 elif op in 'string symbol negate':
2377 elif op in 'string symbol negate':
2417 return smallbonus, x # single revisions are small
2378 return smallbonus, x # single revisions are small
2418 elif op == 'and':
2379 elif op == 'and':
2419 wa, ta = optimize(x[1], True)
2380 wa, ta = optimize(x[1], True)
2420 wb, tb = optimize(x[2], True)
2381 wb, tb = optimize(x[2], True)
2421
2382
2422 # (::x and not ::y)/(not ::y and ::x) have a fast path
2383 # (::x and not ::y)/(not ::y and ::x) have a fast path
2423 def isonly(revs, bases):
2384 def isonly(revs, bases):
2424 return (
2385 return (
2425 revs is not None
2386 revs is not None
2426 and revs[0] == 'func'
2387 and revs[0] == 'func'
2427 and getstring(revs[1], _('not a symbol')) == 'ancestors'
2388 and getstring(revs[1], _('not a symbol')) == 'ancestors'
2428 and bases is not None
2389 and bases is not None
2429 and bases[0] == 'not'
2390 and bases[0] == 'not'
2430 and bases[1][0] == 'func'
2391 and bases[1][0] == 'func'
2431 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
2392 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
2432
2393
2433 w = min(wa, wb)
2394 w = min(wa, wb)
2434 if isonly(ta, tb):
2395 if isonly(ta, tb):
2435 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
2396 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
2436 if isonly(tb, ta):
2397 if isonly(tb, ta):
2437 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
2398 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
2438
2399
2439 if wa > wb:
2400 if wa > wb:
2440 return w, (op, tb, ta)
2401 return w, (op, tb, ta)
2441 return w, (op, ta, tb)
2402 return w, (op, ta, tb)
2442 elif op == 'or':
2403 elif op == 'or':
2443 # fast path for machine-generated expression, that is likely to have
2404 # fast path for machine-generated expression, that is likely to have
2444 # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
2405 # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
2445 ws, ts, ss = [], [], []
2406 ws, ts, ss = [], [], []
2446 def flushss():
2407 def flushss():
2447 if not ss:
2408 if not ss:
2448 return
2409 return
2449 if len(ss) == 1:
2410 if len(ss) == 1:
2450 w, t = ss[0]
2411 w, t = ss[0]
2451 else:
2412 else:
2452 s = '\0'.join(t[1] for w, t in ss)
2413 s = '\0'.join(t[1] for w, t in ss)
2453 y = ('func', ('symbol', '_list'), ('string', s))
2414 y = ('func', ('symbol', '_list'), ('string', s))
2454 w, t = optimize(y, False)
2415 w, t = optimize(y, False)
2455 ws.append(w)
2416 ws.append(w)
2456 ts.append(t)
2417 ts.append(t)
2457 del ss[:]
2418 del ss[:]
2458 for y in x[1:]:
2419 for y in x[1:]:
2459 w, t = optimize(y, False)
2420 w, t = optimize(y, False)
2460 if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
2421 if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
2461 ss.append((w, t))
2422 ss.append((w, t))
2462 continue
2423 continue
2463 flushss()
2424 flushss()
2464 ws.append(w)
2425 ws.append(w)
2465 ts.append(t)
2426 ts.append(t)
2466 flushss()
2427 flushss()
2467 if len(ts) == 1:
2428 if len(ts) == 1:
2468 return ws[0], ts[0] # 'or' operation is fully optimized out
2429 return ws[0], ts[0] # 'or' operation is fully optimized out
2469 # we can't reorder trees by weight because it would change the order.
2430 # we can't reorder trees by weight because it would change the order.
2470 # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
2431 # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
2471 # ts = tuple(t for w, t in sorted(zip(ws, ts), key=lambda wt: wt[0]))
2432 # ts = tuple(t for w, t in sorted(zip(ws, ts), key=lambda wt: wt[0]))
2472 return max(ws), (op,) + tuple(ts)
2433 return max(ws), (op,) + tuple(ts)
2473 elif op == 'not':
2434 elif op == 'not':
2474 # Optimize not public() to _notpublic() because we have a fast version
2435 # Optimize not public() to _notpublic() because we have a fast version
2475 if x[1] == ('func', ('symbol', 'public'), None):
2436 if x[1] == ('func', ('symbol', 'public'), None):
2476 newsym = ('func', ('symbol', '_notpublic'), None)
2437 newsym = ('func', ('symbol', '_notpublic'), None)
2477 o = optimize(newsym, not small)
2438 o = optimize(newsym, not small)
2478 return o[0], o[1]
2439 return o[0], o[1]
2479 else:
2440 else:
2480 o = optimize(x[1], not small)
2441 o = optimize(x[1], not small)
2481 return o[0], (op, o[1])
2442 return o[0], (op, o[1])
2482 elif op == 'parentpost':
2443 elif op == 'parentpost':
2483 o = optimize(x[1], small)
2444 o = optimize(x[1], small)
2484 return o[0], (op, o[1])
2445 return o[0], (op, o[1])
2485 elif op == 'group':
2446 elif op == 'group':
2486 return optimize(x[1], small)
2447 return optimize(x[1], small)
2487 elif op in 'dagrange range list parent ancestorspec':
2448 elif op in 'dagrange range list parent ancestorspec':
2488 if op == 'parent':
2449 if op == 'parent':
2489 # x^:y means (x^) : y, not x ^ (:y)
2450 # x^:y means (x^) : y, not x ^ (:y)
2490 post = ('parentpost', x[1])
2451 post = ('parentpost', x[1])
2491 if x[2][0] == 'dagrangepre':
2452 if x[2][0] == 'dagrangepre':
2492 return optimize(('dagrange', post, x[2][1]), small)
2453 return optimize(('dagrange', post, x[2][1]), small)
2493 elif x[2][0] == 'rangepre':
2454 elif x[2][0] == 'rangepre':
2494 return optimize(('range', post, x[2][1]), small)
2455 return optimize(('range', post, x[2][1]), small)
2495
2456
2496 wa, ta = optimize(x[1], small)
2457 wa, ta = optimize(x[1], small)
2497 wb, tb = optimize(x[2], small)
2458 wb, tb = optimize(x[2], small)
2498 return wa + wb, (op, ta, tb)
2459 return wa + wb, (op, ta, tb)
2499 elif op == 'func':
2460 elif op == 'func':
2500 f = getstring(x[1], _("not a symbol"))
2461 f = getstring(x[1], _("not a symbol"))
2501 wa, ta = optimize(x[2], small)
2462 wa, ta = optimize(x[2], small)
2502 if f in ("author branch closed date desc file grep keyword "
2463 if f in ("author branch closed date desc file grep keyword "
2503 "outgoing user"):
2464 "outgoing user"):
2504 w = 10 # slow
2465 w = 10 # slow
2505 elif f in "modifies adds removes":
2466 elif f in "modifies adds removes":
2506 w = 30 # slower
2467 w = 30 # slower
2507 elif f == "contains":
2468 elif f == "contains":
2508 w = 100 # very slow
2469 w = 100 # very slow
2509 elif f == "ancestor":
2470 elif f == "ancestor":
2510 w = 1 * smallbonus
2471 w = 1 * smallbonus
2511 elif f in "reverse limit first _intlist":
2472 elif f in "reverse limit first _intlist":
2512 w = 0
2473 w = 0
2513 elif f in "sort":
2474 elif f in "sort":
2514 w = 10 # assume most sorts look at changelog
2475 w = 10 # assume most sorts look at changelog
2515 else:
2476 else:
2516 w = 1
2477 w = 1
2517 return w + wa, (op, x[1], ta)
2478 return w + wa, (op, x[1], ta)
2518 return 1, x
2479 return 1, x
2519
2480
2520 _aliasarg = ('func', ('symbol', '_aliasarg'))
2481 _aliasarg = ('func', ('symbol', '_aliasarg'))
2521 def _getaliasarg(tree):
2482 def _getaliasarg(tree):
2522 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
2483 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
2523 return X, None otherwise.
2484 return X, None otherwise.
2524 """
2485 """
2525 if (len(tree) == 3 and tree[:2] == _aliasarg
2486 if (len(tree) == 3 and tree[:2] == _aliasarg
2526 and tree[2][0] == 'string'):
2487 and tree[2][0] == 'string'):
2527 return tree[2][1]
2488 return tree[2][1]
2528 return None
2489 return None
2529
2490
2530 def _checkaliasarg(tree, known=None):
2491 def _checkaliasarg(tree, known=None):
2531 """Check tree contains no _aliasarg construct or only ones which
2492 """Check tree contains no _aliasarg construct or only ones which
2532 value is in known. Used to avoid alias placeholders injection.
2493 value is in known. Used to avoid alias placeholders injection.
2533 """
2494 """
2534 if isinstance(tree, tuple):
2495 if isinstance(tree, tuple):
2535 arg = _getaliasarg(tree)
2496 arg = _getaliasarg(tree)
2536 if arg is not None and (not known or arg not in known):
2497 if arg is not None and (not known or arg not in known):
2537 raise error.UnknownIdentifier('_aliasarg', [])
2498 raise error.UnknownIdentifier('_aliasarg', [])
2538 for t in tree:
2499 for t in tree:
2539 _checkaliasarg(t, known)
2500 _checkaliasarg(t, known)
2540
2501
2541 # the set of valid characters for the initial letter of symbols in
2502 # the set of valid characters for the initial letter of symbols in
2542 # alias declarations and definitions
2503 # alias declarations and definitions
2543 _aliassyminitletters = set(c for c in [chr(i) for i in xrange(256)]
2504 _aliassyminitletters = set(c for c in [chr(i) for i in xrange(256)]
2544 if c.isalnum() or c in '._@$' or ord(c) > 127)
2505 if c.isalnum() or c in '._@$' or ord(c) > 127)
2545
2506
2546 def _tokenizealias(program, lookup=None):
2507 def _tokenizealias(program, lookup=None):
2547 """Parse alias declaration/definition into a stream of tokens
2508 """Parse alias declaration/definition into a stream of tokens
2548
2509
2549 This allows symbol names to use also ``$`` as an initial letter
2510 This allows symbol names to use also ``$`` as an initial letter
2550 (for backward compatibility), and callers of this function should
2511 (for backward compatibility), and callers of this function should
2551 examine whether ``$`` is used also for unexpected symbols or not.
2512 examine whether ``$`` is used also for unexpected symbols or not.
2552 """
2513 """
2553 return tokenize(program, lookup=lookup,
2514 return tokenize(program, lookup=lookup,
2554 syminitletters=_aliassyminitletters)
2515 syminitletters=_aliassyminitletters)
2555
2516
2556 def _parsealiasdecl(decl):
2517 def _parsealiasdecl(decl):
2557 """Parse alias declaration ``decl``
2518 """Parse alias declaration ``decl``
2558
2519
2559 This returns ``(name, tree, args, errorstr)`` tuple:
2520 This returns ``(name, tree, args, errorstr)`` tuple:
2560
2521
2561 - ``name``: of declared alias (may be ``decl`` itself at error)
2522 - ``name``: of declared alias (may be ``decl`` itself at error)
2562 - ``tree``: parse result (or ``None`` at error)
2523 - ``tree``: parse result (or ``None`` at error)
2563 - ``args``: list of alias argument names (or None for symbol declaration)
2524 - ``args``: list of alias argument names (or None for symbol declaration)
2564 - ``errorstr``: detail about detected error (or None)
2525 - ``errorstr``: detail about detected error (or None)
2565
2526
2566 >>> _parsealiasdecl('foo')
2527 >>> _parsealiasdecl('foo')
2567 ('foo', ('symbol', 'foo'), None, None)
2528 ('foo', ('symbol', 'foo'), None, None)
2568 >>> _parsealiasdecl('$foo')
2529 >>> _parsealiasdecl('$foo')
2569 ('$foo', None, None, "'$' not for alias arguments")
2530 ('$foo', None, None, "'$' not for alias arguments")
2570 >>> _parsealiasdecl('foo::bar')
2531 >>> _parsealiasdecl('foo::bar')
2571 ('foo::bar', None, None, 'invalid format')
2532 ('foo::bar', None, None, 'invalid format')
2572 >>> _parsealiasdecl('foo bar')
2533 >>> _parsealiasdecl('foo bar')
2573 ('foo bar', None, None, 'at 4: invalid token')
2534 ('foo bar', None, None, 'at 4: invalid token')
2574 >>> _parsealiasdecl('foo()')
2535 >>> _parsealiasdecl('foo()')
2575 ('foo', ('func', ('symbol', 'foo')), [], None)
2536 ('foo', ('func', ('symbol', 'foo')), [], None)
2576 >>> _parsealiasdecl('$foo()')
2537 >>> _parsealiasdecl('$foo()')
2577 ('$foo()', None, None, "'$' not for alias arguments")
2538 ('$foo()', None, None, "'$' not for alias arguments")
2578 >>> _parsealiasdecl('foo($1, $2)')
2539 >>> _parsealiasdecl('foo($1, $2)')
2579 ('foo', ('func', ('symbol', 'foo')), ['$1', '$2'], None)
2540 ('foo', ('func', ('symbol', 'foo')), ['$1', '$2'], None)
2580 >>> _parsealiasdecl('foo(bar_bar, baz.baz)')
2541 >>> _parsealiasdecl('foo(bar_bar, baz.baz)')
2581 ('foo', ('func', ('symbol', 'foo')), ['bar_bar', 'baz.baz'], None)
2542 ('foo', ('func', ('symbol', 'foo')), ['bar_bar', 'baz.baz'], None)
2582 >>> _parsealiasdecl('foo($1, $2, nested($1, $2))')
2543 >>> _parsealiasdecl('foo($1, $2, nested($1, $2))')
2583 ('foo($1, $2, nested($1, $2))', None, None, 'invalid argument list')
2544 ('foo($1, $2, nested($1, $2))', None, None, 'invalid argument list')
2584 >>> _parsealiasdecl('foo(bar($1, $2))')
2545 >>> _parsealiasdecl('foo(bar($1, $2))')
2585 ('foo(bar($1, $2))', None, None, 'invalid argument list')
2546 ('foo(bar($1, $2))', None, None, 'invalid argument list')
2586 >>> _parsealiasdecl('foo("string")')
2547 >>> _parsealiasdecl('foo("string")')
2587 ('foo("string")', None, None, 'invalid argument list')
2548 ('foo("string")', None, None, 'invalid argument list')
2588 >>> _parsealiasdecl('foo($1, $2')
2549 >>> _parsealiasdecl('foo($1, $2')
2589 ('foo($1, $2', None, None, 'at 10: unexpected token: end')
2550 ('foo($1, $2', None, None, 'at 10: unexpected token: end')
2590 >>> _parsealiasdecl('foo("string')
2551 >>> _parsealiasdecl('foo("string')
2591 ('foo("string', None, None, 'at 5: unterminated string')
2552 ('foo("string', None, None, 'at 5: unterminated string')
2592 >>> _parsealiasdecl('foo($1, $2, $1)')
2553 >>> _parsealiasdecl('foo($1, $2, $1)')
2593 ('foo', None, None, 'argument names collide with each other')
2554 ('foo', None, None, 'argument names collide with each other')
2594 """
2555 """
2595 p = parser.parser(elements)
2556 p = parser.parser(elements)
2596 try:
2557 try:
2597 tree, pos = p.parse(_tokenizealias(decl))
2558 tree, pos = p.parse(_tokenizealias(decl))
2598 if (pos != len(decl)):
2559 if (pos != len(decl)):
2599 raise error.ParseError(_('invalid token'), pos)
2560 raise error.ParseError(_('invalid token'), pos)
2600
2561
2601 if isvalidsymbol(tree):
2562 if isvalidsymbol(tree):
2602 # "name = ...." style
2563 # "name = ...." style
2603 name = getsymbol(tree)
2564 name = getsymbol(tree)
2604 if name.startswith('$'):
2565 if name.startswith('$'):
2605 return (decl, None, None, _("'$' not for alias arguments"))
2566 return (decl, None, None, _("'$' not for alias arguments"))
2606 return (name, ('symbol', name), None, None)
2567 return (name, ('symbol', name), None, None)
2607
2568
2608 if isvalidfunc(tree):
2569 if isvalidfunc(tree):
2609 # "name(arg, ....) = ...." style
2570 # "name(arg, ....) = ...." style
2610 name = getfuncname(tree)
2571 name = getfuncname(tree)
2611 if name.startswith('$'):
2572 if name.startswith('$'):
2612 return (decl, None, None, _("'$' not for alias arguments"))
2573 return (decl, None, None, _("'$' not for alias arguments"))
2613 args = []
2574 args = []
2614 for arg in getfuncargs(tree):
2575 for arg in getfuncargs(tree):
2615 if not isvalidsymbol(arg):
2576 if not isvalidsymbol(arg):
2616 return (decl, None, None, _("invalid argument list"))
2577 return (decl, None, None, _("invalid argument list"))
2617 args.append(getsymbol(arg))
2578 args.append(getsymbol(arg))
2618 if len(args) != len(set(args)):
2579 if len(args) != len(set(args)):
2619 return (name, None, None,
2580 return (name, None, None,
2620 _("argument names collide with each other"))
2581 _("argument names collide with each other"))
2621 return (name, ('func', ('symbol', name)), args, None)
2582 return (name, ('func', ('symbol', name)), args, None)
2622
2583
2623 return (decl, None, None, _("invalid format"))
2584 return (decl, None, None, _("invalid format"))
2624 except error.ParseError as inst:
2585 except error.ParseError as inst:
2625 return (decl, None, None, parseerrordetail(inst))
2586 return (decl, None, None, parseerrordetail(inst))
2626
2587
2627 def _parsealiasdefn(defn, args):
2588 def _parsealiasdefn(defn, args):
2628 """Parse alias definition ``defn``
2589 """Parse alias definition ``defn``
2629
2590
2630 This function also replaces alias argument references in the
2591 This function also replaces alias argument references in the
2631 specified definition by ``_aliasarg(ARGNAME)``.
2592 specified definition by ``_aliasarg(ARGNAME)``.
2632
2593
2633 ``args`` is a list of alias argument names, or None if the alias
2594 ``args`` is a list of alias argument names, or None if the alias
2634 is declared as a symbol.
2595 is declared as a symbol.
2635
2596
2636 This returns "tree" as parsing result.
2597 This returns "tree" as parsing result.
2637
2598
2638 >>> args = ['$1', '$2', 'foo']
2599 >>> args = ['$1', '$2', 'foo']
2639 >>> print prettyformat(_parsealiasdefn('$1 or foo', args))
2600 >>> print prettyformat(_parsealiasdefn('$1 or foo', args))
2640 (or
2601 (or
2641 (func
2602 (func
2642 ('symbol', '_aliasarg')
2603 ('symbol', '_aliasarg')
2643 ('string', '$1'))
2604 ('string', '$1'))
2644 (func
2605 (func
2645 ('symbol', '_aliasarg')
2606 ('symbol', '_aliasarg')
2646 ('string', 'foo')))
2607 ('string', 'foo')))
2647 >>> try:
2608 >>> try:
2648 ... _parsealiasdefn('$1 or $bar', args)
2609 ... _parsealiasdefn('$1 or $bar', args)
2649 ... except error.ParseError, inst:
2610 ... except error.ParseError, inst:
2650 ... print parseerrordetail(inst)
2611 ... print parseerrordetail(inst)
2651 at 6: '$' not for alias arguments
2612 at 6: '$' not for alias arguments
2652 >>> args = ['$1', '$10', 'foo']
2613 >>> args = ['$1', '$10', 'foo']
2653 >>> print prettyformat(_parsealiasdefn('$10 or foobar', args))
2614 >>> print prettyformat(_parsealiasdefn('$10 or foobar', args))
2654 (or
2615 (or
2655 (func
2616 (func
2656 ('symbol', '_aliasarg')
2617 ('symbol', '_aliasarg')
2657 ('string', '$10'))
2618 ('string', '$10'))
2658 ('symbol', 'foobar'))
2619 ('symbol', 'foobar'))
2659 >>> print prettyformat(_parsealiasdefn('"$1" or "foo"', args))
2620 >>> print prettyformat(_parsealiasdefn('"$1" or "foo"', args))
2660 (or
2621 (or
2661 ('string', '$1')
2622 ('string', '$1')
2662 ('string', 'foo'))
2623 ('string', 'foo'))
2663 """
2624 """
2664 def tokenizedefn(program, lookup=None):
2625 def tokenizedefn(program, lookup=None):
2665 if args:
2626 if args:
2666 argset = set(args)
2627 argset = set(args)
2667 else:
2628 else:
2668 argset = set()
2629 argset = set()
2669
2630
2670 for t, value, pos in _tokenizealias(program, lookup=lookup):
2631 for t, value, pos in _tokenizealias(program, lookup=lookup):
2671 if t == 'symbol':
2632 if t == 'symbol':
2672 if value in argset:
2633 if value in argset:
2673 # emulate tokenization of "_aliasarg('ARGNAME')":
2634 # emulate tokenization of "_aliasarg('ARGNAME')":
2674 # "_aliasarg()" is an unknown symbol only used separate
2635 # "_aliasarg()" is an unknown symbol only used separate
2675 # alias argument placeholders from regular strings.
2636 # alias argument placeholders from regular strings.
2676 yield ('symbol', '_aliasarg', pos)
2637 yield ('symbol', '_aliasarg', pos)
2677 yield ('(', None, pos)
2638 yield ('(', None, pos)
2678 yield ('string', value, pos)
2639 yield ('string', value, pos)
2679 yield (')', None, pos)
2640 yield (')', None, pos)
2680 continue
2641 continue
2681 elif value.startswith('$'):
2642 elif value.startswith('$'):
2682 raise error.ParseError(_("'$' not for alias arguments"),
2643 raise error.ParseError(_("'$' not for alias arguments"),
2683 pos)
2644 pos)
2684 yield (t, value, pos)
2645 yield (t, value, pos)
2685
2646
2686 p = parser.parser(elements)
2647 p = parser.parser(elements)
2687 tree, pos = p.parse(tokenizedefn(defn))
2648 tree, pos = p.parse(tokenizedefn(defn))
2688 if pos != len(defn):
2649 if pos != len(defn):
2689 raise error.ParseError(_('invalid token'), pos)
2650 raise error.ParseError(_('invalid token'), pos)
2690 return parser.simplifyinfixops(tree, ('or',))
2651 return parser.simplifyinfixops(tree, ('or',))
2691
2652
2692 class revsetalias(object):
2653 class revsetalias(object):
2693 # whether own `error` information is already shown or not.
2654 # whether own `error` information is already shown or not.
2694 # this avoids showing same warning multiple times at each `findaliases`.
2655 # this avoids showing same warning multiple times at each `findaliases`.
2695 warned = False
2656 warned = False
2696
2657
2697 def __init__(self, name, value):
2658 def __init__(self, name, value):
2698 '''Aliases like:
2659 '''Aliases like:
2699
2660
2700 h = heads(default)
2661 h = heads(default)
2701 b($1) = ancestors($1) - ancestors(default)
2662 b($1) = ancestors($1) - ancestors(default)
2702 '''
2663 '''
2703 self.name, self.tree, self.args, self.error = _parsealiasdecl(name)
2664 self.name, self.tree, self.args, self.error = _parsealiasdecl(name)
2704 if self.error:
2665 if self.error:
2705 self.error = _('failed to parse the declaration of revset alias'
2666 self.error = _('failed to parse the declaration of revset alias'
2706 ' "%s": %s') % (self.name, self.error)
2667 ' "%s": %s') % (self.name, self.error)
2707 return
2668 return
2708
2669
2709 try:
2670 try:
2710 self.replacement = _parsealiasdefn(value, self.args)
2671 self.replacement = _parsealiasdefn(value, self.args)
2711 # Check for placeholder injection
2672 # Check for placeholder injection
2712 _checkaliasarg(self.replacement, self.args)
2673 _checkaliasarg(self.replacement, self.args)
2713 except error.ParseError as inst:
2674 except error.ParseError as inst:
2714 self.error = _('failed to parse the definition of revset alias'
2675 self.error = _('failed to parse the definition of revset alias'
2715 ' "%s": %s') % (self.name, parseerrordetail(inst))
2676 ' "%s": %s') % (self.name, parseerrordetail(inst))
2716
2677
2717 def _getalias(aliases, tree):
2678 def _getalias(aliases, tree):
2718 """If tree looks like an unexpanded alias, return it. Return None
2679 """If tree looks like an unexpanded alias, return it. Return None
2719 otherwise.
2680 otherwise.
2720 """
2681 """
2721 if isinstance(tree, tuple) and tree:
2682 if isinstance(tree, tuple) and tree:
2722 if tree[0] == 'symbol' and len(tree) == 2:
2683 if tree[0] == 'symbol' and len(tree) == 2:
2723 name = tree[1]
2684 name = tree[1]
2724 alias = aliases.get(name)
2685 alias = aliases.get(name)
2725 if alias and alias.args is None and alias.tree == tree:
2686 if alias and alias.args is None and alias.tree == tree:
2726 return alias
2687 return alias
2727 if tree[0] == 'func' and len(tree) > 1:
2688 if tree[0] == 'func' and len(tree) > 1:
2728 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
2689 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
2729 name = tree[1][1]
2690 name = tree[1][1]
2730 alias = aliases.get(name)
2691 alias = aliases.get(name)
2731 if alias and alias.args is not None and alias.tree == tree[:2]:
2692 if alias and alias.args is not None and alias.tree == tree[:2]:
2732 return alias
2693 return alias
2733 return None
2694 return None
2734
2695
2735 def _expandargs(tree, args):
2696 def _expandargs(tree, args):
2736 """Replace _aliasarg instances with the substitution value of the
2697 """Replace _aliasarg instances with the substitution value of the
2737 same name in args, recursively.
2698 same name in args, recursively.
2738 """
2699 """
2739 if not tree or not isinstance(tree, tuple):
2700 if not tree or not isinstance(tree, tuple):
2740 return tree
2701 return tree
2741 arg = _getaliasarg(tree)
2702 arg = _getaliasarg(tree)
2742 if arg is not None:
2703 if arg is not None:
2743 return args[arg]
2704 return args[arg]
2744 return tuple(_expandargs(t, args) for t in tree)
2705 return tuple(_expandargs(t, args) for t in tree)
2745
2706
2746 def _expandaliases(aliases, tree, expanding, cache):
2707 def _expandaliases(aliases, tree, expanding, cache):
2747 """Expand aliases in tree, recursively.
2708 """Expand aliases in tree, recursively.
2748
2709
2749 'aliases' is a dictionary mapping user defined aliases to
2710 'aliases' is a dictionary mapping user defined aliases to
2750 revsetalias objects.
2711 revsetalias objects.
2751 """
2712 """
2752 if not isinstance(tree, tuple):
2713 if not isinstance(tree, tuple):
2753 # Do not expand raw strings
2714 # Do not expand raw strings
2754 return tree
2715 return tree
2755 alias = _getalias(aliases, tree)
2716 alias = _getalias(aliases, tree)
2756 if alias is not None:
2717 if alias is not None:
2757 if alias.error:
2718 if alias.error:
2758 raise util.Abort(alias.error)
2719 raise util.Abort(alias.error)
2759 if alias in expanding:
2720 if alias in expanding:
2760 raise error.ParseError(_('infinite expansion of revset alias "%s" '
2721 raise error.ParseError(_('infinite expansion of revset alias "%s" '
2761 'detected') % alias.name)
2722 'detected') % alias.name)
2762 expanding.append(alias)
2723 expanding.append(alias)
2763 if alias.name not in cache:
2724 if alias.name not in cache:
2764 cache[alias.name] = _expandaliases(aliases, alias.replacement,
2725 cache[alias.name] = _expandaliases(aliases, alias.replacement,
2765 expanding, cache)
2726 expanding, cache)
2766 result = cache[alias.name]
2727 result = cache[alias.name]
2767 expanding.pop()
2728 expanding.pop()
2768 if alias.args is not None:
2729 if alias.args is not None:
2769 l = getlist(tree[2])
2730 l = getlist(tree[2])
2770 if len(l) != len(alias.args):
2731 if len(l) != len(alias.args):
2771 raise error.ParseError(
2732 raise error.ParseError(
2772 _('invalid number of arguments: %s') % len(l))
2733 _('invalid number of arguments: %s') % len(l))
2773 l = [_expandaliases(aliases, a, [], cache) for a in l]
2734 l = [_expandaliases(aliases, a, [], cache) for a in l]
2774 result = _expandargs(result, dict(zip(alias.args, l)))
2735 result = _expandargs(result, dict(zip(alias.args, l)))
2775 else:
2736 else:
2776 result = tuple(_expandaliases(aliases, t, expanding, cache)
2737 result = tuple(_expandaliases(aliases, t, expanding, cache)
2777 for t in tree)
2738 for t in tree)
2778 return result
2739 return result
2779
2740
2780 def findaliases(ui, tree, showwarning=None):
2741 def findaliases(ui, tree, showwarning=None):
2781 _checkaliasarg(tree)
2742 _checkaliasarg(tree)
2782 aliases = {}
2743 aliases = {}
2783 for k, v in ui.configitems('revsetalias'):
2744 for k, v in ui.configitems('revsetalias'):
2784 alias = revsetalias(k, v)
2745 alias = revsetalias(k, v)
2785 aliases[alias.name] = alias
2746 aliases[alias.name] = alias
2786 tree = _expandaliases(aliases, tree, [], {})
2747 tree = _expandaliases(aliases, tree, [], {})
2787 if showwarning:
2748 if showwarning:
2788 # warn about problematic (but not referred) aliases
2749 # warn about problematic (but not referred) aliases
2789 for name, alias in sorted(aliases.iteritems()):
2750 for name, alias in sorted(aliases.iteritems()):
2790 if alias.error and not alias.warned:
2751 if alias.error and not alias.warned:
2791 showwarning(_('warning: %s\n') % (alias.error))
2752 showwarning(_('warning: %s\n') % (alias.error))
2792 alias.warned = True
2753 alias.warned = True
2793 return tree
2754 return tree
2794
2755
2795 def foldconcat(tree):
2756 def foldconcat(tree):
2796 """Fold elements to be concatenated by `##`
2757 """Fold elements to be concatenated by `##`
2797 """
2758 """
2798 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2759 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2799 return tree
2760 return tree
2800 if tree[0] == '_concat':
2761 if tree[0] == '_concat':
2801 pending = [tree]
2762 pending = [tree]
2802 l = []
2763 l = []
2803 while pending:
2764 while pending:
2804 e = pending.pop()
2765 e = pending.pop()
2805 if e[0] == '_concat':
2766 if e[0] == '_concat':
2806 pending.extend(reversed(e[1:]))
2767 pending.extend(reversed(e[1:]))
2807 elif e[0] in ('string', 'symbol'):
2768 elif e[0] in ('string', 'symbol'):
2808 l.append(e[1])
2769 l.append(e[1])
2809 else:
2770 else:
2810 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2771 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2811 raise error.ParseError(msg)
2772 raise error.ParseError(msg)
2812 return ('string', ''.join(l))
2773 return ('string', ''.join(l))
2813 else:
2774 else:
2814 return tuple(foldconcat(t) for t in tree)
2775 return tuple(foldconcat(t) for t in tree)
2815
2776
2816 def parse(spec, lookup=None):
2777 def parse(spec, lookup=None):
2817 p = parser.parser(elements)
2778 p = parser.parser(elements)
2818 tree, pos = p.parse(tokenize(spec, lookup=lookup))
2779 tree, pos = p.parse(tokenize(spec, lookup=lookup))
2819 if pos != len(spec):
2780 if pos != len(spec):
2820 raise error.ParseError(_("invalid token"), pos)
2781 raise error.ParseError(_("invalid token"), pos)
2821 return parser.simplifyinfixops(tree, ('or',))
2782 return parser.simplifyinfixops(tree, ('or',))
2822
2783
2823 def posttreebuilthook(tree, repo):
2784 def posttreebuilthook(tree, repo):
2824 # hook for extensions to execute code on the optimized tree
2785 # hook for extensions to execute code on the optimized tree
2825 pass
2786 pass
2826
2787
2827 def match(ui, spec, repo=None):
2788 def match(ui, spec, repo=None):
2828 if not spec:
2789 if not spec:
2829 raise error.ParseError(_("empty query"))
2790 raise error.ParseError(_("empty query"))
2830 lookup = None
2791 lookup = None
2831 if repo:
2792 if repo:
2832 lookup = repo.__contains__
2793 lookup = repo.__contains__
2833 tree = parse(spec, lookup)
2794 tree = parse(spec, lookup)
2834 return _makematcher(ui, tree, repo)
2795 return _makematcher(ui, tree, repo)
2835
2796
2836 def matchany(ui, specs, repo=None):
2797 def matchany(ui, specs, repo=None):
2837 """Create a matcher that will include any revisions matching one of the
2798 """Create a matcher that will include any revisions matching one of the
2838 given specs"""
2799 given specs"""
2839 if not specs:
2800 if not specs:
2840 def mfunc(repo, subset=None):
2801 def mfunc(repo, subset=None):
2841 return baseset()
2802 return baseset()
2842 return mfunc
2803 return mfunc
2843 if not all(specs):
2804 if not all(specs):
2844 raise error.ParseError(_("empty query"))
2805 raise error.ParseError(_("empty query"))
2845 lookup = None
2806 lookup = None
2846 if repo:
2807 if repo:
2847 lookup = repo.__contains__
2808 lookup = repo.__contains__
2848 if len(specs) == 1:
2809 if len(specs) == 1:
2849 tree = parse(specs[0], lookup)
2810 tree = parse(specs[0], lookup)
2850 else:
2811 else:
2851 tree = ('or',) + tuple(parse(s, lookup) for s in specs)
2812 tree = ('or',) + tuple(parse(s, lookup) for s in specs)
2852 return _makematcher(ui, tree, repo)
2813 return _makematcher(ui, tree, repo)
2853
2814
2854 def _makematcher(ui, tree, repo):
2815 def _makematcher(ui, tree, repo):
2855 if ui:
2816 if ui:
2856 tree = findaliases(ui, tree, showwarning=ui.warn)
2817 tree = findaliases(ui, tree, showwarning=ui.warn)
2857 tree = foldconcat(tree)
2818 tree = foldconcat(tree)
2858 weight, tree = optimize(tree, True)
2819 weight, tree = optimize(tree, True)
2859 posttreebuilthook(tree, repo)
2820 posttreebuilthook(tree, repo)
2860 def mfunc(repo, subset=None):
2821 def mfunc(repo, subset=None):
2861 if subset is None:
2822 if subset is None:
2862 subset = fullreposet(repo)
2823 subset = fullreposet(repo)
2863 if util.safehasattr(subset, 'isascending'):
2824 if util.safehasattr(subset, 'isascending'):
2864 result = getset(repo, subset, tree)
2825 result = getset(repo, subset, tree)
2865 else:
2826 else:
2866 result = getset(repo, baseset(subset), tree)
2827 result = getset(repo, baseset(subset), tree)
2867 return result
2828 return result
2868 return mfunc
2829 return mfunc
2869
2830
2870 def formatspec(expr, *args):
2831 def formatspec(expr, *args):
2871 '''
2832 '''
2872 This is a convenience function for using revsets internally, and
2833 This is a convenience function for using revsets internally, and
2873 escapes arguments appropriately. Aliases are intentionally ignored
2834 escapes arguments appropriately. Aliases are intentionally ignored
2874 so that intended expression behavior isn't accidentally subverted.
2835 so that intended expression behavior isn't accidentally subverted.
2875
2836
2876 Supported arguments:
2837 Supported arguments:
2877
2838
2878 %r = revset expression, parenthesized
2839 %r = revset expression, parenthesized
2879 %d = int(arg), no quoting
2840 %d = int(arg), no quoting
2880 %s = string(arg), escaped and single-quoted
2841 %s = string(arg), escaped and single-quoted
2881 %b = arg.branch(), escaped and single-quoted
2842 %b = arg.branch(), escaped and single-quoted
2882 %n = hex(arg), single-quoted
2843 %n = hex(arg), single-quoted
2883 %% = a literal '%'
2844 %% = a literal '%'
2884
2845
2885 Prefixing the type with 'l' specifies a parenthesized list of that type.
2846 Prefixing the type with 'l' specifies a parenthesized list of that type.
2886
2847
2887 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2848 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2888 '(10 or 11):: and ((this()) or (that()))'
2849 '(10 or 11):: and ((this()) or (that()))'
2889 >>> formatspec('%d:: and not %d::', 10, 20)
2850 >>> formatspec('%d:: and not %d::', 10, 20)
2890 '10:: and not 20::'
2851 '10:: and not 20::'
2891 >>> formatspec('%ld or %ld', [], [1])
2852 >>> formatspec('%ld or %ld', [], [1])
2892 "_list('') or 1"
2853 "_list('') or 1"
2893 >>> formatspec('keyword(%s)', 'foo\\xe9')
2854 >>> formatspec('keyword(%s)', 'foo\\xe9')
2894 "keyword('foo\\\\xe9')"
2855 "keyword('foo\\\\xe9')"
2895 >>> b = lambda: 'default'
2856 >>> b = lambda: 'default'
2896 >>> b.branch = b
2857 >>> b.branch = b
2897 >>> formatspec('branch(%b)', b)
2858 >>> formatspec('branch(%b)', b)
2898 "branch('default')"
2859 "branch('default')"
2899 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2860 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2900 "root(_list('a\\x00b\\x00c\\x00d'))"
2861 "root(_list('a\\x00b\\x00c\\x00d'))"
2901 '''
2862 '''
2902
2863
2903 def quote(s):
2864 def quote(s):
2904 return repr(str(s))
2865 return repr(str(s))
2905
2866
2906 def argtype(c, arg):
2867 def argtype(c, arg):
2907 if c == 'd':
2868 if c == 'd':
2908 return str(int(arg))
2869 return str(int(arg))
2909 elif c == 's':
2870 elif c == 's':
2910 return quote(arg)
2871 return quote(arg)
2911 elif c == 'r':
2872 elif c == 'r':
2912 parse(arg) # make sure syntax errors are confined
2873 parse(arg) # make sure syntax errors are confined
2913 return '(%s)' % arg
2874 return '(%s)' % arg
2914 elif c == 'n':
2875 elif c == 'n':
2915 return quote(node.hex(arg))
2876 return quote(node.hex(arg))
2916 elif c == 'b':
2877 elif c == 'b':
2917 return quote(arg.branch())
2878 return quote(arg.branch())
2918
2879
2919 def listexp(s, t):
2880 def listexp(s, t):
2920 l = len(s)
2881 l = len(s)
2921 if l == 0:
2882 if l == 0:
2922 return "_list('')"
2883 return "_list('')"
2923 elif l == 1:
2884 elif l == 1:
2924 return argtype(t, s[0])
2885 return argtype(t, s[0])
2925 elif t == 'd':
2886 elif t == 'd':
2926 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2887 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2927 elif t == 's':
2888 elif t == 's':
2928 return "_list('%s')" % "\0".join(s)
2889 return "_list('%s')" % "\0".join(s)
2929 elif t == 'n':
2890 elif t == 'n':
2930 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2891 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2931 elif t == 'b':
2892 elif t == 'b':
2932 return "_list('%s')" % "\0".join(a.branch() for a in s)
2893 return "_list('%s')" % "\0".join(a.branch() for a in s)
2933
2894
2934 m = l // 2
2895 m = l // 2
2935 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2896 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2936
2897
2937 ret = ''
2898 ret = ''
2938 pos = 0
2899 pos = 0
2939 arg = 0
2900 arg = 0
2940 while pos < len(expr):
2901 while pos < len(expr):
2941 c = expr[pos]
2902 c = expr[pos]
2942 if c == '%':
2903 if c == '%':
2943 pos += 1
2904 pos += 1
2944 d = expr[pos]
2905 d = expr[pos]
2945 if d == '%':
2906 if d == '%':
2946 ret += d
2907 ret += d
2947 elif d in 'dsnbr':
2908 elif d in 'dsnbr':
2948 ret += argtype(d, args[arg])
2909 ret += argtype(d, args[arg])
2949 arg += 1
2910 arg += 1
2950 elif d == 'l':
2911 elif d == 'l':
2951 # a list of some type
2912 # a list of some type
2952 pos += 1
2913 pos += 1
2953 d = expr[pos]
2914 d = expr[pos]
2954 ret += listexp(list(args[arg]), d)
2915 ret += listexp(list(args[arg]), d)
2955 arg += 1
2916 arg += 1
2956 else:
2917 else:
2957 raise util.Abort('unexpected revspec format character %s' % d)
2918 raise util.Abort('unexpected revspec format character %s' % d)
2958 else:
2919 else:
2959 ret += c
2920 ret += c
2960 pos += 1
2921 pos += 1
2961
2922
2962 return ret
2923 return ret
2963
2924
2964 def prettyformat(tree):
2925 def prettyformat(tree):
2965 return parser.prettyformat(tree, ('string', 'symbol'))
2926 return parser.prettyformat(tree, ('string', 'symbol'))
2966
2927
2967 def depth(tree):
2928 def depth(tree):
2968 if isinstance(tree, tuple):
2929 if isinstance(tree, tuple):
2969 return max(map(depth, tree)) + 1
2930 return max(map(depth, tree)) + 1
2970 else:
2931 else:
2971 return 0
2932 return 0
2972
2933
2973 def funcsused(tree):
2934 def funcsused(tree):
2974 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2935 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2975 return set()
2936 return set()
2976 else:
2937 else:
2977 funcs = set()
2938 funcs = set()
2978 for s in tree[1:]:
2939 for s in tree[1:]:
2979 funcs |= funcsused(s)
2940 funcs |= funcsused(s)
2980 if tree[0] == 'func':
2941 if tree[0] == 'func':
2981 funcs.add(tree[1][1])
2942 funcs.add(tree[1][1])
2982 return funcs
2943 return funcs
2983
2944
2984 class abstractsmartset(object):
2945 class abstractsmartset(object):
2985
2946
2986 def __nonzero__(self):
2947 def __nonzero__(self):
2987 """True if the smartset is not empty"""
2948 """True if the smartset is not empty"""
2988 raise NotImplementedError()
2949 raise NotImplementedError()
2989
2950
2990 def __contains__(self, rev):
2951 def __contains__(self, rev):
2991 """provide fast membership testing"""
2952 """provide fast membership testing"""
2992 raise NotImplementedError()
2953 raise NotImplementedError()
2993
2954
2994 def __iter__(self):
2955 def __iter__(self):
2995 """iterate the set in the order it is supposed to be iterated"""
2956 """iterate the set in the order it is supposed to be iterated"""
2996 raise NotImplementedError()
2957 raise NotImplementedError()
2997
2958
2998 # Attributes containing a function to perform a fast iteration in a given
2959 # Attributes containing a function to perform a fast iteration in a given
2999 # direction. A smartset can have none, one, or both defined.
2960 # direction. A smartset can have none, one, or both defined.
3000 #
2961 #
3001 # Default value is None instead of a function returning None to avoid
2962 # Default value is None instead of a function returning None to avoid
3002 # initializing an iterator just for testing if a fast method exists.
2963 # initializing an iterator just for testing if a fast method exists.
3003 fastasc = None
2964 fastasc = None
3004 fastdesc = None
2965 fastdesc = None
3005
2966
3006 def isascending(self):
2967 def isascending(self):
3007 """True if the set will iterate in ascending order"""
2968 """True if the set will iterate in ascending order"""
3008 raise NotImplementedError()
2969 raise NotImplementedError()
3009
2970
3010 def isdescending(self):
2971 def isdescending(self):
3011 """True if the set will iterate in descending order"""
2972 """True if the set will iterate in descending order"""
3012 raise NotImplementedError()
2973 raise NotImplementedError()
3013
2974
3014 @util.cachefunc
2975 @util.cachefunc
3015 def min(self):
2976 def min(self):
3016 """return the minimum element in the set"""
2977 """return the minimum element in the set"""
3017 if self.fastasc is not None:
2978 if self.fastasc is not None:
3018 for r in self.fastasc():
2979 for r in self.fastasc():
3019 return r
2980 return r
3020 raise ValueError('arg is an empty sequence')
2981 raise ValueError('arg is an empty sequence')
3021 return min(self)
2982 return min(self)
3022
2983
3023 @util.cachefunc
2984 @util.cachefunc
3024 def max(self):
2985 def max(self):
3025 """return the maximum element in the set"""
2986 """return the maximum element in the set"""
3026 if self.fastdesc is not None:
2987 if self.fastdesc is not None:
3027 for r in self.fastdesc():
2988 for r in self.fastdesc():
3028 return r
2989 return r
3029 raise ValueError('arg is an empty sequence')
2990 raise ValueError('arg is an empty sequence')
3030 return max(self)
2991 return max(self)
3031
2992
3032 def first(self):
2993 def first(self):
3033 """return the first element in the set (user iteration perspective)
2994 """return the first element in the set (user iteration perspective)
3034
2995
3035 Return None if the set is empty"""
2996 Return None if the set is empty"""
3036 raise NotImplementedError()
2997 raise NotImplementedError()
3037
2998
3038 def last(self):
2999 def last(self):
3039 """return the last element in the set (user iteration perspective)
3000 """return the last element in the set (user iteration perspective)
3040
3001
3041 Return None if the set is empty"""
3002 Return None if the set is empty"""
3042 raise NotImplementedError()
3003 raise NotImplementedError()
3043
3004
3044 def __len__(self):
3005 def __len__(self):
3045 """return the length of the smartsets
3006 """return the length of the smartsets
3046
3007
3047 This can be expensive on smartset that could be lazy otherwise."""
3008 This can be expensive on smartset that could be lazy otherwise."""
3048 raise NotImplementedError()
3009 raise NotImplementedError()
3049
3010
3050 def reverse(self):
3011 def reverse(self):
3051 """reverse the expected iteration order"""
3012 """reverse the expected iteration order"""
3052 raise NotImplementedError()
3013 raise NotImplementedError()
3053
3014
3054 def sort(self, reverse=True):
3015 def sort(self, reverse=True):
3055 """get the set to iterate in an ascending or descending order"""
3016 """get the set to iterate in an ascending or descending order"""
3056 raise NotImplementedError()
3017 raise NotImplementedError()
3057
3018
3058 def __and__(self, other):
3019 def __and__(self, other):
3059 """Returns a new object with the intersection of the two collections.
3020 """Returns a new object with the intersection of the two collections.
3060
3021
3061 This is part of the mandatory API for smartset."""
3022 This is part of the mandatory API for smartset."""
3062 if isinstance(other, fullreposet):
3023 if isinstance(other, fullreposet):
3063 return self
3024 return self
3064 return self.filter(other.__contains__, cache=False)
3025 return self.filter(other.__contains__, cache=False)
3065
3026
3066 def __add__(self, other):
3027 def __add__(self, other):
3067 """Returns a new object with the union of the two collections.
3028 """Returns a new object with the union of the two collections.
3068
3029
3069 This is part of the mandatory API for smartset."""
3030 This is part of the mandatory API for smartset."""
3070 return addset(self, other)
3031 return addset(self, other)
3071
3032
3072 def __sub__(self, other):
3033 def __sub__(self, other):
3073 """Returns a new object with the substraction of the two collections.
3034 """Returns a new object with the substraction of the two collections.
3074
3035
3075 This is part of the mandatory API for smartset."""
3036 This is part of the mandatory API for smartset."""
3076 c = other.__contains__
3037 c = other.__contains__
3077 return self.filter(lambda r: not c(r), cache=False)
3038 return self.filter(lambda r: not c(r), cache=False)
3078
3039
3079 def filter(self, condition, cache=True):
3040 def filter(self, condition, cache=True):
3080 """Returns this smartset filtered by condition as a new smartset.
3041 """Returns this smartset filtered by condition as a new smartset.
3081
3042
3082 `condition` is a callable which takes a revision number and returns a
3043 `condition` is a callable which takes a revision number and returns a
3083 boolean.
3044 boolean.
3084
3045
3085 This is part of the mandatory API for smartset."""
3046 This is part of the mandatory API for smartset."""
3086 # builtin cannot be cached. but do not needs to
3047 # builtin cannot be cached. but do not needs to
3087 if cache and util.safehasattr(condition, 'func_code'):
3048 if cache and util.safehasattr(condition, 'func_code'):
3088 condition = util.cachefunc(condition)
3049 condition = util.cachefunc(condition)
3089 return filteredset(self, condition)
3050 return filteredset(self, condition)
3090
3051
3091 class baseset(abstractsmartset):
3052 class baseset(abstractsmartset):
3092 """Basic data structure that represents a revset and contains the basic
3053 """Basic data structure that represents a revset and contains the basic
3093 operation that it should be able to perform.
3054 operation that it should be able to perform.
3094
3055
3095 Every method in this class should be implemented by any smartset class.
3056 Every method in this class should be implemented by any smartset class.
3096 """
3057 """
3097 def __init__(self, data=()):
3058 def __init__(self, data=()):
3098 if not isinstance(data, list):
3059 if not isinstance(data, list):
3099 if isinstance(data, set):
3060 if isinstance(data, set):
3100 self._set = data
3061 self._set = data
3101 data = list(data)
3062 data = list(data)
3102 self._list = data
3063 self._list = data
3103 self._ascending = None
3064 self._ascending = None
3104
3065
3105 @util.propertycache
3066 @util.propertycache
3106 def _set(self):
3067 def _set(self):
3107 return set(self._list)
3068 return set(self._list)
3108
3069
3109 @util.propertycache
3070 @util.propertycache
3110 def _asclist(self):
3071 def _asclist(self):
3111 asclist = self._list[:]
3072 asclist = self._list[:]
3112 asclist.sort()
3073 asclist.sort()
3113 return asclist
3074 return asclist
3114
3075
3115 def __iter__(self):
3076 def __iter__(self):
3116 if self._ascending is None:
3077 if self._ascending is None:
3117 return iter(self._list)
3078 return iter(self._list)
3118 elif self._ascending:
3079 elif self._ascending:
3119 return iter(self._asclist)
3080 return iter(self._asclist)
3120 else:
3081 else:
3121 return reversed(self._asclist)
3082 return reversed(self._asclist)
3122
3083
3123 def fastasc(self):
3084 def fastasc(self):
3124 return iter(self._asclist)
3085 return iter(self._asclist)
3125
3086
3126 def fastdesc(self):
3087 def fastdesc(self):
3127 return reversed(self._asclist)
3088 return reversed(self._asclist)
3128
3089
3129 @util.propertycache
3090 @util.propertycache
3130 def __contains__(self):
3091 def __contains__(self):
3131 return self._set.__contains__
3092 return self._set.__contains__
3132
3093
3133 def __nonzero__(self):
3094 def __nonzero__(self):
3134 return bool(self._list)
3095 return bool(self._list)
3135
3096
3136 def sort(self, reverse=False):
3097 def sort(self, reverse=False):
3137 self._ascending = not bool(reverse)
3098 self._ascending = not bool(reverse)
3138
3099
3139 def reverse(self):
3100 def reverse(self):
3140 if self._ascending is None:
3101 if self._ascending is None:
3141 self._list.reverse()
3102 self._list.reverse()
3142 else:
3103 else:
3143 self._ascending = not self._ascending
3104 self._ascending = not self._ascending
3144
3105
3145 def __len__(self):
3106 def __len__(self):
3146 return len(self._list)
3107 return len(self._list)
3147
3108
3148 def isascending(self):
3109 def isascending(self):
3149 """Returns True if the collection is ascending order, False if not.
3110 """Returns True if the collection is ascending order, False if not.
3150
3111
3151 This is part of the mandatory API for smartset."""
3112 This is part of the mandatory API for smartset."""
3152 if len(self) <= 1:
3113 if len(self) <= 1:
3153 return True
3114 return True
3154 return self._ascending is not None and self._ascending
3115 return self._ascending is not None and self._ascending
3155
3116
3156 def isdescending(self):
3117 def isdescending(self):
3157 """Returns True if the collection is descending order, False if not.
3118 """Returns True if the collection is descending order, False if not.
3158
3119
3159 This is part of the mandatory API for smartset."""
3120 This is part of the mandatory API for smartset."""
3160 if len(self) <= 1:
3121 if len(self) <= 1:
3161 return True
3122 return True
3162 return self._ascending is not None and not self._ascending
3123 return self._ascending is not None and not self._ascending
3163
3124
3164 def first(self):
3125 def first(self):
3165 if self:
3126 if self:
3166 if self._ascending is None:
3127 if self._ascending is None:
3167 return self._list[0]
3128 return self._list[0]
3168 elif self._ascending:
3129 elif self._ascending:
3169 return self._asclist[0]
3130 return self._asclist[0]
3170 else:
3131 else:
3171 return self._asclist[-1]
3132 return self._asclist[-1]
3172 return None
3133 return None
3173
3134
3174 def last(self):
3135 def last(self):
3175 if self:
3136 if self:
3176 if self._ascending is None:
3137 if self._ascending is None:
3177 return self._list[-1]
3138 return self._list[-1]
3178 elif self._ascending:
3139 elif self._ascending:
3179 return self._asclist[-1]
3140 return self._asclist[-1]
3180 else:
3141 else:
3181 return self._asclist[0]
3142 return self._asclist[0]
3182 return None
3143 return None
3183
3144
3184 def __repr__(self):
3145 def __repr__(self):
3185 d = {None: '', False: '-', True: '+'}[self._ascending]
3146 d = {None: '', False: '-', True: '+'}[self._ascending]
3186 return '<%s%s %r>' % (type(self).__name__, d, self._list)
3147 return '<%s%s %r>' % (type(self).__name__, d, self._list)
3187
3148
3188 class filteredset(abstractsmartset):
3149 class filteredset(abstractsmartset):
3189 """Duck type for baseset class which iterates lazily over the revisions in
3150 """Duck type for baseset class which iterates lazily over the revisions in
3190 the subset and contains a function which tests for membership in the
3151 the subset and contains a function which tests for membership in the
3191 revset
3152 revset
3192 """
3153 """
3193 def __init__(self, subset, condition=lambda x: True):
3154 def __init__(self, subset, condition=lambda x: True):
3194 """
3155 """
3195 condition: a function that decide whether a revision in the subset
3156 condition: a function that decide whether a revision in the subset
3196 belongs to the revset or not.
3157 belongs to the revset or not.
3197 """
3158 """
3198 self._subset = subset
3159 self._subset = subset
3199 self._condition = condition
3160 self._condition = condition
3200
3161
3201 def __contains__(self, x):
3162 def __contains__(self, x):
3202 return x in self._subset and self._condition(x)
3163 return x in self._subset and self._condition(x)
3203
3164
3204 def __iter__(self):
3165 def __iter__(self):
3205 return self._iterfilter(self._subset)
3166 return self._iterfilter(self._subset)
3206
3167
3207 def _iterfilter(self, it):
3168 def _iterfilter(self, it):
3208 cond = self._condition
3169 cond = self._condition
3209 for x in it:
3170 for x in it:
3210 if cond(x):
3171 if cond(x):
3211 yield x
3172 yield x
3212
3173
3213 @property
3174 @property
3214 def fastasc(self):
3175 def fastasc(self):
3215 it = self._subset.fastasc
3176 it = self._subset.fastasc
3216 if it is None:
3177 if it is None:
3217 return None
3178 return None
3218 return lambda: self._iterfilter(it())
3179 return lambda: self._iterfilter(it())
3219
3180
3220 @property
3181 @property
3221 def fastdesc(self):
3182 def fastdesc(self):
3222 it = self._subset.fastdesc
3183 it = self._subset.fastdesc
3223 if it is None:
3184 if it is None:
3224 return None
3185 return None
3225 return lambda: self._iterfilter(it())
3186 return lambda: self._iterfilter(it())
3226
3187
3227 def __nonzero__(self):
3188 def __nonzero__(self):
3228 fast = self.fastasc
3189 fast = self.fastasc
3229 if fast is None:
3190 if fast is None:
3230 fast = self.fastdesc
3191 fast = self.fastdesc
3231 if fast is not None:
3192 if fast is not None:
3232 it = fast()
3193 it = fast()
3233 else:
3194 else:
3234 it = self
3195 it = self
3235
3196
3236 for r in it:
3197 for r in it:
3237 return True
3198 return True
3238 return False
3199 return False
3239
3200
3240 def __len__(self):
3201 def __len__(self):
3241 # Basic implementation to be changed in future patches.
3202 # Basic implementation to be changed in future patches.
3242 l = baseset([r for r in self])
3203 l = baseset([r for r in self])
3243 return len(l)
3204 return len(l)
3244
3205
3245 def sort(self, reverse=False):
3206 def sort(self, reverse=False):
3246 self._subset.sort(reverse=reverse)
3207 self._subset.sort(reverse=reverse)
3247
3208
3248 def reverse(self):
3209 def reverse(self):
3249 self._subset.reverse()
3210 self._subset.reverse()
3250
3211
3251 def isascending(self):
3212 def isascending(self):
3252 return self._subset.isascending()
3213 return self._subset.isascending()
3253
3214
3254 def isdescending(self):
3215 def isdescending(self):
3255 return self._subset.isdescending()
3216 return self._subset.isdescending()
3256
3217
3257 def first(self):
3218 def first(self):
3258 for x in self:
3219 for x in self:
3259 return x
3220 return x
3260 return None
3221 return None
3261
3222
3262 def last(self):
3223 def last(self):
3263 it = None
3224 it = None
3264 if self.isascending():
3225 if self.isascending():
3265 it = self.fastdesc
3226 it = self.fastdesc
3266 elif self.isdescending():
3227 elif self.isdescending():
3267 it = self.fastasc
3228 it = self.fastasc
3268 if it is not None:
3229 if it is not None:
3269 for x in it():
3230 for x in it():
3270 return x
3231 return x
3271 return None #empty case
3232 return None #empty case
3272 else:
3233 else:
3273 x = None
3234 x = None
3274 for x in self:
3235 for x in self:
3275 pass
3236 pass
3276 return x
3237 return x
3277
3238
3278 def __repr__(self):
3239 def __repr__(self):
3279 return '<%s %r>' % (type(self).__name__, self._subset)
3240 return '<%s %r>' % (type(self).__name__, self._subset)
3280
3241
3281 def _iterordered(ascending, iter1, iter2):
3242 def _iterordered(ascending, iter1, iter2):
3282 """produce an ordered iteration from two iterators with the same order
3243 """produce an ordered iteration from two iterators with the same order
3283
3244
3284 The ascending is used to indicated the iteration direction.
3245 The ascending is used to indicated the iteration direction.
3285 """
3246 """
3286 choice = max
3247 choice = max
3287 if ascending:
3248 if ascending:
3288 choice = min
3249 choice = min
3289
3250
3290 val1 = None
3251 val1 = None
3291 val2 = None
3252 val2 = None
3292 try:
3253 try:
3293 # Consume both iterators in an ordered way until one is empty
3254 # Consume both iterators in an ordered way until one is empty
3294 while True:
3255 while True:
3295 if val1 is None:
3256 if val1 is None:
3296 val1 = iter1.next()
3257 val1 = iter1.next()
3297 if val2 is None:
3258 if val2 is None:
3298 val2 = iter2.next()
3259 val2 = iter2.next()
3299 next = choice(val1, val2)
3260 next = choice(val1, val2)
3300 yield next
3261 yield next
3301 if val1 == next:
3262 if val1 == next:
3302 val1 = None
3263 val1 = None
3303 if val2 == next:
3264 if val2 == next:
3304 val2 = None
3265 val2 = None
3305 except StopIteration:
3266 except StopIteration:
3306 # Flush any remaining values and consume the other one
3267 # Flush any remaining values and consume the other one
3307 it = iter2
3268 it = iter2
3308 if val1 is not None:
3269 if val1 is not None:
3309 yield val1
3270 yield val1
3310 it = iter1
3271 it = iter1
3311 elif val2 is not None:
3272 elif val2 is not None:
3312 # might have been equality and both are empty
3273 # might have been equality and both are empty
3313 yield val2
3274 yield val2
3314 for val in it:
3275 for val in it:
3315 yield val
3276 yield val
3316
3277
3317 class addset(abstractsmartset):
3278 class addset(abstractsmartset):
3318 """Represent the addition of two sets
3279 """Represent the addition of two sets
3319
3280
3320 Wrapper structure for lazily adding two structures without losing much
3281 Wrapper structure for lazily adding two structures without losing much
3321 performance on the __contains__ method
3282 performance on the __contains__ method
3322
3283
3323 If the ascending attribute is set, that means the two structures are
3284 If the ascending attribute is set, that means the two structures are
3324 ordered in either an ascending or descending way. Therefore, we can add
3285 ordered in either an ascending or descending way. Therefore, we can add
3325 them maintaining the order by iterating over both at the same time
3286 them maintaining the order by iterating over both at the same time
3326
3287
3327 >>> xs = baseset([0, 3, 2])
3288 >>> xs = baseset([0, 3, 2])
3328 >>> ys = baseset([5, 2, 4])
3289 >>> ys = baseset([5, 2, 4])
3329
3290
3330 >>> rs = addset(xs, ys)
3291 >>> rs = addset(xs, ys)
3331 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
3292 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
3332 (True, True, False, True, 0, 4)
3293 (True, True, False, True, 0, 4)
3333 >>> rs = addset(xs, baseset([]))
3294 >>> rs = addset(xs, baseset([]))
3334 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
3295 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
3335 (True, True, False, 0, 2)
3296 (True, True, False, 0, 2)
3336 >>> rs = addset(baseset([]), baseset([]))
3297 >>> rs = addset(baseset([]), baseset([]))
3337 >>> bool(rs), 0 in rs, rs.first(), rs.last()
3298 >>> bool(rs), 0 in rs, rs.first(), rs.last()
3338 (False, False, None, None)
3299 (False, False, None, None)
3339
3300
3340 iterate unsorted:
3301 iterate unsorted:
3341 >>> rs = addset(xs, ys)
3302 >>> rs = addset(xs, ys)
3342 >>> [x for x in rs] # without _genlist
3303 >>> [x for x in rs] # without _genlist
3343 [0, 3, 2, 5, 4]
3304 [0, 3, 2, 5, 4]
3344 >>> assert not rs._genlist
3305 >>> assert not rs._genlist
3345 >>> len(rs)
3306 >>> len(rs)
3346 5
3307 5
3347 >>> [x for x in rs] # with _genlist
3308 >>> [x for x in rs] # with _genlist
3348 [0, 3, 2, 5, 4]
3309 [0, 3, 2, 5, 4]
3349 >>> assert rs._genlist
3310 >>> assert rs._genlist
3350
3311
3351 iterate ascending:
3312 iterate ascending:
3352 >>> rs = addset(xs, ys, ascending=True)
3313 >>> rs = addset(xs, ys, ascending=True)
3353 >>> [x for x in rs], [x for x in rs.fastasc()] # without _asclist
3314 >>> [x for x in rs], [x for x in rs.fastasc()] # without _asclist
3354 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3315 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3355 >>> assert not rs._asclist
3316 >>> assert not rs._asclist
3356 >>> len(rs)
3317 >>> len(rs)
3357 5
3318 5
3358 >>> [x for x in rs], [x for x in rs.fastasc()]
3319 >>> [x for x in rs], [x for x in rs.fastasc()]
3359 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3320 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3360 >>> assert rs._asclist
3321 >>> assert rs._asclist
3361
3322
3362 iterate descending:
3323 iterate descending:
3363 >>> rs = addset(xs, ys, ascending=False)
3324 >>> rs = addset(xs, ys, ascending=False)
3364 >>> [x for x in rs], [x for x in rs.fastdesc()] # without _asclist
3325 >>> [x for x in rs], [x for x in rs.fastdesc()] # without _asclist
3365 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3326 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3366 >>> assert not rs._asclist
3327 >>> assert not rs._asclist
3367 >>> len(rs)
3328 >>> len(rs)
3368 5
3329 5
3369 >>> [x for x in rs], [x for x in rs.fastdesc()]
3330 >>> [x for x in rs], [x for x in rs.fastdesc()]
3370 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3331 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3371 >>> assert rs._asclist
3332 >>> assert rs._asclist
3372
3333
3373 iterate ascending without fastasc:
3334 iterate ascending without fastasc:
3374 >>> rs = addset(xs, generatorset(ys), ascending=True)
3335 >>> rs = addset(xs, generatorset(ys), ascending=True)
3375 >>> assert rs.fastasc is None
3336 >>> assert rs.fastasc is None
3376 >>> [x for x in rs]
3337 >>> [x for x in rs]
3377 [0, 2, 3, 4, 5]
3338 [0, 2, 3, 4, 5]
3378
3339
3379 iterate descending without fastdesc:
3340 iterate descending without fastdesc:
3380 >>> rs = addset(generatorset(xs), ys, ascending=False)
3341 >>> rs = addset(generatorset(xs), ys, ascending=False)
3381 >>> assert rs.fastdesc is None
3342 >>> assert rs.fastdesc is None
3382 >>> [x for x in rs]
3343 >>> [x for x in rs]
3383 [5, 4, 3, 2, 0]
3344 [5, 4, 3, 2, 0]
3384 """
3345 """
3385 def __init__(self, revs1, revs2, ascending=None):
3346 def __init__(self, revs1, revs2, ascending=None):
3386 self._r1 = revs1
3347 self._r1 = revs1
3387 self._r2 = revs2
3348 self._r2 = revs2
3388 self._iter = None
3349 self._iter = None
3389 self._ascending = ascending
3350 self._ascending = ascending
3390 self._genlist = None
3351 self._genlist = None
3391 self._asclist = None
3352 self._asclist = None
3392
3353
3393 def __len__(self):
3354 def __len__(self):
3394 return len(self._list)
3355 return len(self._list)
3395
3356
3396 def __nonzero__(self):
3357 def __nonzero__(self):
3397 return bool(self._r1) or bool(self._r2)
3358 return bool(self._r1) or bool(self._r2)
3398
3359
3399 @util.propertycache
3360 @util.propertycache
3400 def _list(self):
3361 def _list(self):
3401 if not self._genlist:
3362 if not self._genlist:
3402 self._genlist = baseset(iter(self))
3363 self._genlist = baseset(iter(self))
3403 return self._genlist
3364 return self._genlist
3404
3365
3405 def __iter__(self):
3366 def __iter__(self):
3406 """Iterate over both collections without repeating elements
3367 """Iterate over both collections without repeating elements
3407
3368
3408 If the ascending attribute is not set, iterate over the first one and
3369 If the ascending attribute is not set, iterate over the first one and
3409 then over the second one checking for membership on the first one so we
3370 then over the second one checking for membership on the first one so we
3410 dont yield any duplicates.
3371 dont yield any duplicates.
3411
3372
3412 If the ascending attribute is set, iterate over both collections at the
3373 If the ascending attribute is set, iterate over both collections at the
3413 same time, yielding only one value at a time in the given order.
3374 same time, yielding only one value at a time in the given order.
3414 """
3375 """
3415 if self._ascending is None:
3376 if self._ascending is None:
3416 if self._genlist:
3377 if self._genlist:
3417 return iter(self._genlist)
3378 return iter(self._genlist)
3418 def arbitraryordergen():
3379 def arbitraryordergen():
3419 for r in self._r1:
3380 for r in self._r1:
3420 yield r
3381 yield r
3421 inr1 = self._r1.__contains__
3382 inr1 = self._r1.__contains__
3422 for r in self._r2:
3383 for r in self._r2:
3423 if not inr1(r):
3384 if not inr1(r):
3424 yield r
3385 yield r
3425 return arbitraryordergen()
3386 return arbitraryordergen()
3426 # try to use our own fast iterator if it exists
3387 # try to use our own fast iterator if it exists
3427 self._trysetasclist()
3388 self._trysetasclist()
3428 if self._ascending:
3389 if self._ascending:
3429 attr = 'fastasc'
3390 attr = 'fastasc'
3430 else:
3391 else:
3431 attr = 'fastdesc'
3392 attr = 'fastdesc'
3432 it = getattr(self, attr)
3393 it = getattr(self, attr)
3433 if it is not None:
3394 if it is not None:
3434 return it()
3395 return it()
3435 # maybe half of the component supports fast
3396 # maybe half of the component supports fast
3436 # get iterator for _r1
3397 # get iterator for _r1
3437 iter1 = getattr(self._r1, attr)
3398 iter1 = getattr(self._r1, attr)
3438 if iter1 is None:
3399 if iter1 is None:
3439 # let's avoid side effect (not sure it matters)
3400 # let's avoid side effect (not sure it matters)
3440 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
3401 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
3441 else:
3402 else:
3442 iter1 = iter1()
3403 iter1 = iter1()
3443 # get iterator for _r2
3404 # get iterator for _r2
3444 iter2 = getattr(self._r2, attr)
3405 iter2 = getattr(self._r2, attr)
3445 if iter2 is None:
3406 if iter2 is None:
3446 # let's avoid side effect (not sure it matters)
3407 # let's avoid side effect (not sure it matters)
3447 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
3408 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
3448 else:
3409 else:
3449 iter2 = iter2()
3410 iter2 = iter2()
3450 return _iterordered(self._ascending, iter1, iter2)
3411 return _iterordered(self._ascending, iter1, iter2)
3451
3412
3452 def _trysetasclist(self):
3413 def _trysetasclist(self):
3453 """populate the _asclist attribute if possible and necessary"""
3414 """populate the _asclist attribute if possible and necessary"""
3454 if self._genlist is not None and self._asclist is None:
3415 if self._genlist is not None and self._asclist is None:
3455 self._asclist = sorted(self._genlist)
3416 self._asclist = sorted(self._genlist)
3456
3417
3457 @property
3418 @property
3458 def fastasc(self):
3419 def fastasc(self):
3459 self._trysetasclist()
3420 self._trysetasclist()
3460 if self._asclist is not None:
3421 if self._asclist is not None:
3461 return self._asclist.__iter__
3422 return self._asclist.__iter__
3462 iter1 = self._r1.fastasc
3423 iter1 = self._r1.fastasc
3463 iter2 = self._r2.fastasc
3424 iter2 = self._r2.fastasc
3464 if None in (iter1, iter2):
3425 if None in (iter1, iter2):
3465 return None
3426 return None
3466 return lambda: _iterordered(True, iter1(), iter2())
3427 return lambda: _iterordered(True, iter1(), iter2())
3467
3428
3468 @property
3429 @property
3469 def fastdesc(self):
3430 def fastdesc(self):
3470 self._trysetasclist()
3431 self._trysetasclist()
3471 if self._asclist is not None:
3432 if self._asclist is not None:
3472 return self._asclist.__reversed__
3433 return self._asclist.__reversed__
3473 iter1 = self._r1.fastdesc
3434 iter1 = self._r1.fastdesc
3474 iter2 = self._r2.fastdesc
3435 iter2 = self._r2.fastdesc
3475 if None in (iter1, iter2):
3436 if None in (iter1, iter2):
3476 return None
3437 return None
3477 return lambda: _iterordered(False, iter1(), iter2())
3438 return lambda: _iterordered(False, iter1(), iter2())
3478
3439
3479 def __contains__(self, x):
3440 def __contains__(self, x):
3480 return x in self._r1 or x in self._r2
3441 return x in self._r1 or x in self._r2
3481
3442
3482 def sort(self, reverse=False):
3443 def sort(self, reverse=False):
3483 """Sort the added set
3444 """Sort the added set
3484
3445
3485 For this we use the cached list with all the generated values and if we
3446 For this we use the cached list with all the generated values and if we
3486 know they are ascending or descending we can sort them in a smart way.
3447 know they are ascending or descending we can sort them in a smart way.
3487 """
3448 """
3488 self._ascending = not reverse
3449 self._ascending = not reverse
3489
3450
3490 def isascending(self):
3451 def isascending(self):
3491 return self._ascending is not None and self._ascending
3452 return self._ascending is not None and self._ascending
3492
3453
3493 def isdescending(self):
3454 def isdescending(self):
3494 return self._ascending is not None and not self._ascending
3455 return self._ascending is not None and not self._ascending
3495
3456
3496 def reverse(self):
3457 def reverse(self):
3497 if self._ascending is None:
3458 if self._ascending is None:
3498 self._list.reverse()
3459 self._list.reverse()
3499 else:
3460 else:
3500 self._ascending = not self._ascending
3461 self._ascending = not self._ascending
3501
3462
3502 def first(self):
3463 def first(self):
3503 for x in self:
3464 for x in self:
3504 return x
3465 return x
3505 return None
3466 return None
3506
3467
3507 def last(self):
3468 def last(self):
3508 self.reverse()
3469 self.reverse()
3509 val = self.first()
3470 val = self.first()
3510 self.reverse()
3471 self.reverse()
3511 return val
3472 return val
3512
3473
3513 def __repr__(self):
3474 def __repr__(self):
3514 d = {None: '', False: '-', True: '+'}[self._ascending]
3475 d = {None: '', False: '-', True: '+'}[self._ascending]
3515 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
3476 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
3516
3477
3517 class generatorset(abstractsmartset):
3478 class generatorset(abstractsmartset):
3518 """Wrap a generator for lazy iteration
3479 """Wrap a generator for lazy iteration
3519
3480
3520 Wrapper structure for generators that provides lazy membership and can
3481 Wrapper structure for generators that provides lazy membership and can
3521 be iterated more than once.
3482 be iterated more than once.
3522 When asked for membership it generates values until either it finds the
3483 When asked for membership it generates values until either it finds the
3523 requested one or has gone through all the elements in the generator
3484 requested one or has gone through all the elements in the generator
3524 """
3485 """
3525 def __init__(self, gen, iterasc=None):
3486 def __init__(self, gen, iterasc=None):
3526 """
3487 """
3527 gen: a generator producing the values for the generatorset.
3488 gen: a generator producing the values for the generatorset.
3528 """
3489 """
3529 self._gen = gen
3490 self._gen = gen
3530 self._asclist = None
3491 self._asclist = None
3531 self._cache = {}
3492 self._cache = {}
3532 self._genlist = []
3493 self._genlist = []
3533 self._finished = False
3494 self._finished = False
3534 self._ascending = True
3495 self._ascending = True
3535 if iterasc is not None:
3496 if iterasc is not None:
3536 if iterasc:
3497 if iterasc:
3537 self.fastasc = self._iterator
3498 self.fastasc = self._iterator
3538 self.__contains__ = self._asccontains
3499 self.__contains__ = self._asccontains
3539 else:
3500 else:
3540 self.fastdesc = self._iterator
3501 self.fastdesc = self._iterator
3541 self.__contains__ = self._desccontains
3502 self.__contains__ = self._desccontains
3542
3503
3543 def __nonzero__(self):
3504 def __nonzero__(self):
3544 # Do not use 'for r in self' because it will enforce the iteration
3505 # Do not use 'for r in self' because it will enforce the iteration
3545 # order (default ascending), possibly unrolling a whole descending
3506 # order (default ascending), possibly unrolling a whole descending
3546 # iterator.
3507 # iterator.
3547 if self._genlist:
3508 if self._genlist:
3548 return True
3509 return True
3549 for r in self._consumegen():
3510 for r in self._consumegen():
3550 return True
3511 return True
3551 return False
3512 return False
3552
3513
3553 def __contains__(self, x):
3514 def __contains__(self, x):
3554 if x in self._cache:
3515 if x in self._cache:
3555 return self._cache[x]
3516 return self._cache[x]
3556
3517
3557 # Use new values only, as existing values would be cached.
3518 # Use new values only, as existing values would be cached.
3558 for l in self._consumegen():
3519 for l in self._consumegen():
3559 if l == x:
3520 if l == x:
3560 return True
3521 return True
3561
3522
3562 self._cache[x] = False
3523 self._cache[x] = False
3563 return False
3524 return False
3564
3525
3565 def _asccontains(self, x):
3526 def _asccontains(self, x):
3566 """version of contains optimised for ascending generator"""
3527 """version of contains optimised for ascending generator"""
3567 if x in self._cache:
3528 if x in self._cache:
3568 return self._cache[x]
3529 return self._cache[x]
3569
3530
3570 # Use new values only, as existing values would be cached.
3531 # Use new values only, as existing values would be cached.
3571 for l in self._consumegen():
3532 for l in self._consumegen():
3572 if l == x:
3533 if l == x:
3573 return True
3534 return True
3574 if l > x:
3535 if l > x:
3575 break
3536 break
3576
3537
3577 self._cache[x] = False
3538 self._cache[x] = False
3578 return False
3539 return False
3579
3540
3580 def _desccontains(self, x):
3541 def _desccontains(self, x):
3581 """version of contains optimised for descending generator"""
3542 """version of contains optimised for descending generator"""
3582 if x in self._cache:
3543 if x in self._cache:
3583 return self._cache[x]
3544 return self._cache[x]
3584
3545
3585 # Use new values only, as existing values would be cached.
3546 # Use new values only, as existing values would be cached.
3586 for l in self._consumegen():
3547 for l in self._consumegen():
3587 if l == x:
3548 if l == x:
3588 return True
3549 return True
3589 if l < x:
3550 if l < x:
3590 break
3551 break
3591
3552
3592 self._cache[x] = False
3553 self._cache[x] = False
3593 return False
3554 return False
3594
3555
3595 def __iter__(self):
3556 def __iter__(self):
3596 if self._ascending:
3557 if self._ascending:
3597 it = self.fastasc
3558 it = self.fastasc
3598 else:
3559 else:
3599 it = self.fastdesc
3560 it = self.fastdesc
3600 if it is not None:
3561 if it is not None:
3601 return it()
3562 return it()
3602 # we need to consume the iterator
3563 # we need to consume the iterator
3603 for x in self._consumegen():
3564 for x in self._consumegen():
3604 pass
3565 pass
3605 # recall the same code
3566 # recall the same code
3606 return iter(self)
3567 return iter(self)
3607
3568
3608 def _iterator(self):
3569 def _iterator(self):
3609 if self._finished:
3570 if self._finished:
3610 return iter(self._genlist)
3571 return iter(self._genlist)
3611
3572
3612 # We have to use this complex iteration strategy to allow multiple
3573 # We have to use this complex iteration strategy to allow multiple
3613 # iterations at the same time. We need to be able to catch revision
3574 # iterations at the same time. We need to be able to catch revision
3614 # removed from _consumegen and added to genlist in another instance.
3575 # removed from _consumegen and added to genlist in another instance.
3615 #
3576 #
3616 # Getting rid of it would provide an about 15% speed up on this
3577 # Getting rid of it would provide an about 15% speed up on this
3617 # iteration.
3578 # iteration.
3618 genlist = self._genlist
3579 genlist = self._genlist
3619 nextrev = self._consumegen().next
3580 nextrev = self._consumegen().next
3620 _len = len # cache global lookup
3581 _len = len # cache global lookup
3621 def gen():
3582 def gen():
3622 i = 0
3583 i = 0
3623 while True:
3584 while True:
3624 if i < _len(genlist):
3585 if i < _len(genlist):
3625 yield genlist[i]
3586 yield genlist[i]
3626 else:
3587 else:
3627 yield nextrev()
3588 yield nextrev()
3628 i += 1
3589 i += 1
3629 return gen()
3590 return gen()
3630
3591
3631 def _consumegen(self):
3592 def _consumegen(self):
3632 cache = self._cache
3593 cache = self._cache
3633 genlist = self._genlist.append
3594 genlist = self._genlist.append
3634 for item in self._gen:
3595 for item in self._gen:
3635 cache[item] = True
3596 cache[item] = True
3636 genlist(item)
3597 genlist(item)
3637 yield item
3598 yield item
3638 if not self._finished:
3599 if not self._finished:
3639 self._finished = True
3600 self._finished = True
3640 asc = self._genlist[:]
3601 asc = self._genlist[:]
3641 asc.sort()
3602 asc.sort()
3642 self._asclist = asc
3603 self._asclist = asc
3643 self.fastasc = asc.__iter__
3604 self.fastasc = asc.__iter__
3644 self.fastdesc = asc.__reversed__
3605 self.fastdesc = asc.__reversed__
3645
3606
3646 def __len__(self):
3607 def __len__(self):
3647 for x in self._consumegen():
3608 for x in self._consumegen():
3648 pass
3609 pass
3649 return len(self._genlist)
3610 return len(self._genlist)
3650
3611
3651 def sort(self, reverse=False):
3612 def sort(self, reverse=False):
3652 self._ascending = not reverse
3613 self._ascending = not reverse
3653
3614
3654 def reverse(self):
3615 def reverse(self):
3655 self._ascending = not self._ascending
3616 self._ascending = not self._ascending
3656
3617
3657 def isascending(self):
3618 def isascending(self):
3658 return self._ascending
3619 return self._ascending
3659
3620
3660 def isdescending(self):
3621 def isdescending(self):
3661 return not self._ascending
3622 return not self._ascending
3662
3623
3663 def first(self):
3624 def first(self):
3664 if self._ascending:
3625 if self._ascending:
3665 it = self.fastasc
3626 it = self.fastasc
3666 else:
3627 else:
3667 it = self.fastdesc
3628 it = self.fastdesc
3668 if it is None:
3629 if it is None:
3669 # we need to consume all and try again
3630 # we need to consume all and try again
3670 for x in self._consumegen():
3631 for x in self._consumegen():
3671 pass
3632 pass
3672 return self.first()
3633 return self.first()
3673 return next(it(), None)
3634 return next(it(), None)
3674
3635
3675 def last(self):
3636 def last(self):
3676 if self._ascending:
3637 if self._ascending:
3677 it = self.fastdesc
3638 it = self.fastdesc
3678 else:
3639 else:
3679 it = self.fastasc
3640 it = self.fastasc
3680 if it is None:
3641 if it is None:
3681 # we need to consume all and try again
3642 # we need to consume all and try again
3682 for x in self._consumegen():
3643 for x in self._consumegen():
3683 pass
3644 pass
3684 return self.first()
3645 return self.first()
3685 return next(it(), None)
3646 return next(it(), None)
3686
3647
3687 def __repr__(self):
3648 def __repr__(self):
3688 d = {False: '-', True: '+'}[self._ascending]
3649 d = {False: '-', True: '+'}[self._ascending]
3689 return '<%s%s>' % (type(self).__name__, d)
3650 return '<%s%s>' % (type(self).__name__, d)
3690
3651
3691 class spanset(abstractsmartset):
3652 class spanset(abstractsmartset):
3692 """Duck type for baseset class which represents a range of revisions and
3653 """Duck type for baseset class which represents a range of revisions and
3693 can work lazily and without having all the range in memory
3654 can work lazily and without having all the range in memory
3694
3655
3695 Note that spanset(x, y) behave almost like xrange(x, y) except for two
3656 Note that spanset(x, y) behave almost like xrange(x, y) except for two
3696 notable points:
3657 notable points:
3697 - when x < y it will be automatically descending,
3658 - when x < y it will be automatically descending,
3698 - revision filtered with this repoview will be skipped.
3659 - revision filtered with this repoview will be skipped.
3699
3660
3700 """
3661 """
3701 def __init__(self, repo, start=0, end=None):
3662 def __init__(self, repo, start=0, end=None):
3702 """
3663 """
3703 start: first revision included the set
3664 start: first revision included the set
3704 (default to 0)
3665 (default to 0)
3705 end: first revision excluded (last+1)
3666 end: first revision excluded (last+1)
3706 (default to len(repo)
3667 (default to len(repo)
3707
3668
3708 Spanset will be descending if `end` < `start`.
3669 Spanset will be descending if `end` < `start`.
3709 """
3670 """
3710 if end is None:
3671 if end is None:
3711 end = len(repo)
3672 end = len(repo)
3712 self._ascending = start <= end
3673 self._ascending = start <= end
3713 if not self._ascending:
3674 if not self._ascending:
3714 start, end = end + 1, start +1
3675 start, end = end + 1, start +1
3715 self._start = start
3676 self._start = start
3716 self._end = end
3677 self._end = end
3717 self._hiddenrevs = repo.changelog.filteredrevs
3678 self._hiddenrevs = repo.changelog.filteredrevs
3718
3679
3719 def sort(self, reverse=False):
3680 def sort(self, reverse=False):
3720 self._ascending = not reverse
3681 self._ascending = not reverse
3721
3682
3722 def reverse(self):
3683 def reverse(self):
3723 self._ascending = not self._ascending
3684 self._ascending = not self._ascending
3724
3685
3725 def _iterfilter(self, iterrange):
3686 def _iterfilter(self, iterrange):
3726 s = self._hiddenrevs
3687 s = self._hiddenrevs
3727 for r in iterrange:
3688 for r in iterrange:
3728 if r not in s:
3689 if r not in s:
3729 yield r
3690 yield r
3730
3691
3731 def __iter__(self):
3692 def __iter__(self):
3732 if self._ascending:
3693 if self._ascending:
3733 return self.fastasc()
3694 return self.fastasc()
3734 else:
3695 else:
3735 return self.fastdesc()
3696 return self.fastdesc()
3736
3697
3737 def fastasc(self):
3698 def fastasc(self):
3738 iterrange = xrange(self._start, self._end)
3699 iterrange = xrange(self._start, self._end)
3739 if self._hiddenrevs:
3700 if self._hiddenrevs:
3740 return self._iterfilter(iterrange)
3701 return self._iterfilter(iterrange)
3741 return iter(iterrange)
3702 return iter(iterrange)
3742
3703
3743 def fastdesc(self):
3704 def fastdesc(self):
3744 iterrange = xrange(self._end - 1, self._start - 1, -1)
3705 iterrange = xrange(self._end - 1, self._start - 1, -1)
3745 if self._hiddenrevs:
3706 if self._hiddenrevs:
3746 return self._iterfilter(iterrange)
3707 return self._iterfilter(iterrange)
3747 return iter(iterrange)
3708 return iter(iterrange)
3748
3709
3749 def __contains__(self, rev):
3710 def __contains__(self, rev):
3750 hidden = self._hiddenrevs
3711 hidden = self._hiddenrevs
3751 return ((self._start <= rev < self._end)
3712 return ((self._start <= rev < self._end)
3752 and not (hidden and rev in hidden))
3713 and not (hidden and rev in hidden))
3753
3714
3754 def __nonzero__(self):
3715 def __nonzero__(self):
3755 for r in self:
3716 for r in self:
3756 return True
3717 return True
3757 return False
3718 return False
3758
3719
3759 def __len__(self):
3720 def __len__(self):
3760 if not self._hiddenrevs:
3721 if not self._hiddenrevs:
3761 return abs(self._end - self._start)
3722 return abs(self._end - self._start)
3762 else:
3723 else:
3763 count = 0
3724 count = 0
3764 start = self._start
3725 start = self._start
3765 end = self._end
3726 end = self._end
3766 for rev in self._hiddenrevs:
3727 for rev in self._hiddenrevs:
3767 if (end < rev <= start) or (start <= rev < end):
3728 if (end < rev <= start) or (start <= rev < end):
3768 count += 1
3729 count += 1
3769 return abs(self._end - self._start) - count
3730 return abs(self._end - self._start) - count
3770
3731
3771 def isascending(self):
3732 def isascending(self):
3772 return self._ascending
3733 return self._ascending
3773
3734
3774 def isdescending(self):
3735 def isdescending(self):
3775 return not self._ascending
3736 return not self._ascending
3776
3737
3777 def first(self):
3738 def first(self):
3778 if self._ascending:
3739 if self._ascending:
3779 it = self.fastasc
3740 it = self.fastasc
3780 else:
3741 else:
3781 it = self.fastdesc
3742 it = self.fastdesc
3782 for x in it():
3743 for x in it():
3783 return x
3744 return x
3784 return None
3745 return None
3785
3746
3786 def last(self):
3747 def last(self):
3787 if self._ascending:
3748 if self._ascending:
3788 it = self.fastdesc
3749 it = self.fastdesc
3789 else:
3750 else:
3790 it = self.fastasc
3751 it = self.fastasc
3791 for x in it():
3752 for x in it():
3792 return x
3753 return x
3793 return None
3754 return None
3794
3755
3795 def __repr__(self):
3756 def __repr__(self):
3796 d = {False: '-', True: '+'}[self._ascending]
3757 d = {False: '-', True: '+'}[self._ascending]
3797 return '<%s%s %d:%d>' % (type(self).__name__, d,
3758 return '<%s%s %d:%d>' % (type(self).__name__, d,
3798 self._start, self._end - 1)
3759 self._start, self._end - 1)
3799
3760
3800 class fullreposet(spanset):
3761 class fullreposet(spanset):
3801 """a set containing all revisions in the repo
3762 """a set containing all revisions in the repo
3802
3763
3803 This class exists to host special optimization and magic to handle virtual
3764 This class exists to host special optimization and magic to handle virtual
3804 revisions such as "null".
3765 revisions such as "null".
3805 """
3766 """
3806
3767
3807 def __init__(self, repo):
3768 def __init__(self, repo):
3808 super(fullreposet, self).__init__(repo)
3769 super(fullreposet, self).__init__(repo)
3809
3770
3810 def __and__(self, other):
3771 def __and__(self, other):
3811 """As self contains the whole repo, all of the other set should also be
3772 """As self contains the whole repo, all of the other set should also be
3812 in self. Therefore `self & other = other`.
3773 in self. Therefore `self & other = other`.
3813
3774
3814 This boldly assumes the other contains valid revs only.
3775 This boldly assumes the other contains valid revs only.
3815 """
3776 """
3816 # other not a smartset, make is so
3777 # other not a smartset, make is so
3817 if not util.safehasattr(other, 'isascending'):
3778 if not util.safehasattr(other, 'isascending'):
3818 # filter out hidden revision
3779 # filter out hidden revision
3819 # (this boldly assumes all smartset are pure)
3780 # (this boldly assumes all smartset are pure)
3820 #
3781 #
3821 # `other` was used with "&", let's assume this is a set like
3782 # `other` was used with "&", let's assume this is a set like
3822 # object.
3783 # object.
3823 other = baseset(other - self._hiddenrevs)
3784 other = baseset(other - self._hiddenrevs)
3824
3785
3825 # XXX As fullreposet is also used as bootstrap, this is wrong.
3786 # XXX As fullreposet is also used as bootstrap, this is wrong.
3826 #
3787 #
3827 # With a giveme312() revset returning [3,1,2], this makes
3788 # With a giveme312() revset returning [3,1,2], this makes
3828 # 'hg log -r "giveme312()"' -> 1, 2, 3 (wrong)
3789 # 'hg log -r "giveme312()"' -> 1, 2, 3 (wrong)
3829 # We cannot just drop it because other usage still need to sort it:
3790 # We cannot just drop it because other usage still need to sort it:
3830 # 'hg log -r "all() and giveme312()"' -> 1, 2, 3 (right)
3791 # 'hg log -r "all() and giveme312()"' -> 1, 2, 3 (right)
3831 #
3792 #
3832 # There is also some faulty revset implementations that rely on it
3793 # There is also some faulty revset implementations that rely on it
3833 # (eg: children as of its state in e8075329c5fb)
3794 # (eg: children as of its state in e8075329c5fb)
3834 #
3795 #
3835 # When we fix the two points above we can move this into the if clause
3796 # When we fix the two points above we can move this into the if clause
3836 other.sort(reverse=self.isdescending())
3797 other.sort(reverse=self.isdescending())
3837 return other
3798 return other
3838
3799
3839 def prettyformatset(revs):
3800 def prettyformatset(revs):
3840 lines = []
3801 lines = []
3841 rs = repr(revs)
3802 rs = repr(revs)
3842 p = 0
3803 p = 0
3843 while p < len(rs):
3804 while p < len(rs):
3844 q = rs.find('<', p + 1)
3805 q = rs.find('<', p + 1)
3845 if q < 0:
3806 if q < 0:
3846 q = len(rs)
3807 q = len(rs)
3847 l = rs.count('<', 0, p) - rs.count('>', 0, p)
3808 l = rs.count('<', 0, p) - rs.count('>', 0, p)
3848 assert l >= 0
3809 assert l >= 0
3849 lines.append((l, rs[p:q].rstrip()))
3810 lines.append((l, rs[p:q].rstrip()))
3850 p = q
3811 p = q
3851 return '\n'.join(' ' * l + s for l, s in lines)
3812 return '\n'.join(' ' * l + s for l, s in lines)
3852
3813
3853 # tell hggettext to extract docstrings from these functions:
3814 # tell hggettext to extract docstrings from these functions:
3854 i18nfunctions = symbols.values()
3815 i18nfunctions = symbols.values()
@@ -1,2423 +1,2462
1 # util.py - Mercurial utility functions and platform specific implementations
1 # util.py - Mercurial utility functions and platform specific implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specific implementations.
10 """Mercurial utility functions and platform specific implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 import i18n
16 import i18n
17 _ = i18n._
17 _ = i18n._
18 import error, osutil, encoding, parsers
18 import error, osutil, encoding, parsers
19 import errno, shutil, sys, tempfile, traceback
19 import errno, shutil, sys, tempfile, traceback
20 import re as remod
20 import re as remod
21 import os, time, datetime, calendar, textwrap, signal, collections
21 import os, time, datetime, calendar, textwrap, signal, collections
22 import imp, socket, urllib
22 import imp, socket, urllib
23 import gc
23 import gc
24 import bz2
24 import bz2
25 import zlib
25 import zlib
26
26
27 if os.name == 'nt':
27 if os.name == 'nt':
28 import windows as platform
28 import windows as platform
29 else:
29 else:
30 import posix as platform
30 import posix as platform
31
31
32 cachestat = platform.cachestat
32 cachestat = platform.cachestat
33 checkexec = platform.checkexec
33 checkexec = platform.checkexec
34 checklink = platform.checklink
34 checklink = platform.checklink
35 copymode = platform.copymode
35 copymode = platform.copymode
36 executablepath = platform.executablepath
36 executablepath = platform.executablepath
37 expandglobs = platform.expandglobs
37 expandglobs = platform.expandglobs
38 explainexit = platform.explainexit
38 explainexit = platform.explainexit
39 findexe = platform.findexe
39 findexe = platform.findexe
40 gethgcmd = platform.gethgcmd
40 gethgcmd = platform.gethgcmd
41 getuser = platform.getuser
41 getuser = platform.getuser
42 groupmembers = platform.groupmembers
42 groupmembers = platform.groupmembers
43 groupname = platform.groupname
43 groupname = platform.groupname
44 hidewindow = platform.hidewindow
44 hidewindow = platform.hidewindow
45 isexec = platform.isexec
45 isexec = platform.isexec
46 isowner = platform.isowner
46 isowner = platform.isowner
47 localpath = platform.localpath
47 localpath = platform.localpath
48 lookupreg = platform.lookupreg
48 lookupreg = platform.lookupreg
49 makedir = platform.makedir
49 makedir = platform.makedir
50 nlinks = platform.nlinks
50 nlinks = platform.nlinks
51 normpath = platform.normpath
51 normpath = platform.normpath
52 normcase = platform.normcase
52 normcase = platform.normcase
53 normcasespec = platform.normcasespec
53 normcasespec = platform.normcasespec
54 normcasefallback = platform.normcasefallback
54 normcasefallback = platform.normcasefallback
55 openhardlinks = platform.openhardlinks
55 openhardlinks = platform.openhardlinks
56 oslink = platform.oslink
56 oslink = platform.oslink
57 parsepatchoutput = platform.parsepatchoutput
57 parsepatchoutput = platform.parsepatchoutput
58 pconvert = platform.pconvert
58 pconvert = platform.pconvert
59 poll = platform.poll
59 poll = platform.poll
60 popen = platform.popen
60 popen = platform.popen
61 posixfile = platform.posixfile
61 posixfile = platform.posixfile
62 quotecommand = platform.quotecommand
62 quotecommand = platform.quotecommand
63 readpipe = platform.readpipe
63 readpipe = platform.readpipe
64 rename = platform.rename
64 rename = platform.rename
65 removedirs = platform.removedirs
65 removedirs = platform.removedirs
66 samedevice = platform.samedevice
66 samedevice = platform.samedevice
67 samefile = platform.samefile
67 samefile = platform.samefile
68 samestat = platform.samestat
68 samestat = platform.samestat
69 setbinary = platform.setbinary
69 setbinary = platform.setbinary
70 setflags = platform.setflags
70 setflags = platform.setflags
71 setsignalhandler = platform.setsignalhandler
71 setsignalhandler = platform.setsignalhandler
72 shellquote = platform.shellquote
72 shellquote = platform.shellquote
73 spawndetached = platform.spawndetached
73 spawndetached = platform.spawndetached
74 split = platform.split
74 split = platform.split
75 sshargs = platform.sshargs
75 sshargs = platform.sshargs
76 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
76 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
77 statisexec = platform.statisexec
77 statisexec = platform.statisexec
78 statislink = platform.statislink
78 statislink = platform.statislink
79 termwidth = platform.termwidth
79 termwidth = platform.termwidth
80 testpid = platform.testpid
80 testpid = platform.testpid
81 umask = platform.umask
81 umask = platform.umask
82 unlink = platform.unlink
82 unlink = platform.unlink
83 unlinkpath = platform.unlinkpath
83 unlinkpath = platform.unlinkpath
84 username = platform.username
84 username = platform.username
85
85
86 # Python compatibility
86 # Python compatibility
87
87
88 _notset = object()
88 _notset = object()
89
89
90 def safehasattr(thing, attr):
90 def safehasattr(thing, attr):
91 return getattr(thing, attr, _notset) is not _notset
91 return getattr(thing, attr, _notset) is not _notset
92
92
93 def sha1(s=''):
93 def sha1(s=''):
94 '''
94 '''
95 Low-overhead wrapper around Python's SHA support
95 Low-overhead wrapper around Python's SHA support
96
96
97 >>> f = _fastsha1
97 >>> f = _fastsha1
98 >>> a = sha1()
98 >>> a = sha1()
99 >>> a = f()
99 >>> a = f()
100 >>> a.hexdigest()
100 >>> a.hexdigest()
101 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
101 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
102 '''
102 '''
103
103
104 return _fastsha1(s)
104 return _fastsha1(s)
105
105
106 def _fastsha1(s=''):
106 def _fastsha1(s=''):
107 # This function will import sha1 from hashlib or sha (whichever is
107 # This function will import sha1 from hashlib or sha (whichever is
108 # available) and overwrite itself with it on the first call.
108 # available) and overwrite itself with it on the first call.
109 # Subsequent calls will go directly to the imported function.
109 # Subsequent calls will go directly to the imported function.
110 if sys.version_info >= (2, 5):
110 if sys.version_info >= (2, 5):
111 from hashlib import sha1 as _sha1
111 from hashlib import sha1 as _sha1
112 else:
112 else:
113 from sha import sha as _sha1
113 from sha import sha as _sha1
114 global _fastsha1, sha1
114 global _fastsha1, sha1
115 _fastsha1 = sha1 = _sha1
115 _fastsha1 = sha1 = _sha1
116 return _sha1(s)
116 return _sha1(s)
117
117
118 def md5(s=''):
118 def md5(s=''):
119 try:
119 try:
120 from hashlib import md5 as _md5
120 from hashlib import md5 as _md5
121 except ImportError:
121 except ImportError:
122 from md5 import md5 as _md5
122 from md5 import md5 as _md5
123 global md5
123 global md5
124 md5 = _md5
124 md5 = _md5
125 return _md5(s)
125 return _md5(s)
126
126
127 DIGESTS = {
127 DIGESTS = {
128 'md5': md5,
128 'md5': md5,
129 'sha1': sha1,
129 'sha1': sha1,
130 }
130 }
131 # List of digest types from strongest to weakest
131 # List of digest types from strongest to weakest
132 DIGESTS_BY_STRENGTH = ['sha1', 'md5']
132 DIGESTS_BY_STRENGTH = ['sha1', 'md5']
133
133
134 try:
134 try:
135 import hashlib
135 import hashlib
136 DIGESTS.update({
136 DIGESTS.update({
137 'sha512': hashlib.sha512,
137 'sha512': hashlib.sha512,
138 })
138 })
139 DIGESTS_BY_STRENGTH.insert(0, 'sha512')
139 DIGESTS_BY_STRENGTH.insert(0, 'sha512')
140 except ImportError:
140 except ImportError:
141 pass
141 pass
142
142
143 for k in DIGESTS_BY_STRENGTH:
143 for k in DIGESTS_BY_STRENGTH:
144 assert k in DIGESTS
144 assert k in DIGESTS
145
145
146 class digester(object):
146 class digester(object):
147 """helper to compute digests.
147 """helper to compute digests.
148
148
149 This helper can be used to compute one or more digests given their name.
149 This helper can be used to compute one or more digests given their name.
150
150
151 >>> d = digester(['md5', 'sha1'])
151 >>> d = digester(['md5', 'sha1'])
152 >>> d.update('foo')
152 >>> d.update('foo')
153 >>> [k for k in sorted(d)]
153 >>> [k for k in sorted(d)]
154 ['md5', 'sha1']
154 ['md5', 'sha1']
155 >>> d['md5']
155 >>> d['md5']
156 'acbd18db4cc2f85cedef654fccc4a4d8'
156 'acbd18db4cc2f85cedef654fccc4a4d8'
157 >>> d['sha1']
157 >>> d['sha1']
158 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
158 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
159 >>> digester.preferred(['md5', 'sha1'])
159 >>> digester.preferred(['md5', 'sha1'])
160 'sha1'
160 'sha1'
161 """
161 """
162
162
163 def __init__(self, digests, s=''):
163 def __init__(self, digests, s=''):
164 self._hashes = {}
164 self._hashes = {}
165 for k in digests:
165 for k in digests:
166 if k not in DIGESTS:
166 if k not in DIGESTS:
167 raise Abort(_('unknown digest type: %s') % k)
167 raise Abort(_('unknown digest type: %s') % k)
168 self._hashes[k] = DIGESTS[k]()
168 self._hashes[k] = DIGESTS[k]()
169 if s:
169 if s:
170 self.update(s)
170 self.update(s)
171
171
172 def update(self, data):
172 def update(self, data):
173 for h in self._hashes.values():
173 for h in self._hashes.values():
174 h.update(data)
174 h.update(data)
175
175
176 def __getitem__(self, key):
176 def __getitem__(self, key):
177 if key not in DIGESTS:
177 if key not in DIGESTS:
178 raise Abort(_('unknown digest type: %s') % k)
178 raise Abort(_('unknown digest type: %s') % k)
179 return self._hashes[key].hexdigest()
179 return self._hashes[key].hexdigest()
180
180
181 def __iter__(self):
181 def __iter__(self):
182 return iter(self._hashes)
182 return iter(self._hashes)
183
183
184 @staticmethod
184 @staticmethod
185 def preferred(supported):
185 def preferred(supported):
186 """returns the strongest digest type in both supported and DIGESTS."""
186 """returns the strongest digest type in both supported and DIGESTS."""
187
187
188 for k in DIGESTS_BY_STRENGTH:
188 for k in DIGESTS_BY_STRENGTH:
189 if k in supported:
189 if k in supported:
190 return k
190 return k
191 return None
191 return None
192
192
193 class digestchecker(object):
193 class digestchecker(object):
194 """file handle wrapper that additionally checks content against a given
194 """file handle wrapper that additionally checks content against a given
195 size and digests.
195 size and digests.
196
196
197 d = digestchecker(fh, size, {'md5': '...'})
197 d = digestchecker(fh, size, {'md5': '...'})
198
198
199 When multiple digests are given, all of them are validated.
199 When multiple digests are given, all of them are validated.
200 """
200 """
201
201
202 def __init__(self, fh, size, digests):
202 def __init__(self, fh, size, digests):
203 self._fh = fh
203 self._fh = fh
204 self._size = size
204 self._size = size
205 self._got = 0
205 self._got = 0
206 self._digests = dict(digests)
206 self._digests = dict(digests)
207 self._digester = digester(self._digests.keys())
207 self._digester = digester(self._digests.keys())
208
208
209 def read(self, length=-1):
209 def read(self, length=-1):
210 content = self._fh.read(length)
210 content = self._fh.read(length)
211 self._digester.update(content)
211 self._digester.update(content)
212 self._got += len(content)
212 self._got += len(content)
213 return content
213 return content
214
214
215 def validate(self):
215 def validate(self):
216 if self._size != self._got:
216 if self._size != self._got:
217 raise Abort(_('size mismatch: expected %d, got %d') %
217 raise Abort(_('size mismatch: expected %d, got %d') %
218 (self._size, self._got))
218 (self._size, self._got))
219 for k, v in self._digests.items():
219 for k, v in self._digests.items():
220 if v != self._digester[k]:
220 if v != self._digester[k]:
221 # i18n: first parameter is a digest name
221 # i18n: first parameter is a digest name
222 raise Abort(_('%s mismatch: expected %s, got %s') %
222 raise Abort(_('%s mismatch: expected %s, got %s') %
223 (k, v, self._digester[k]))
223 (k, v, self._digester[k]))
224
224
225 try:
225 try:
226 buffer = buffer
226 buffer = buffer
227 except NameError:
227 except NameError:
228 if sys.version_info[0] < 3:
228 if sys.version_info[0] < 3:
229 def buffer(sliceable, offset=0):
229 def buffer(sliceable, offset=0):
230 return sliceable[offset:]
230 return sliceable[offset:]
231 else:
231 else:
232 def buffer(sliceable, offset=0):
232 def buffer(sliceable, offset=0):
233 return memoryview(sliceable)[offset:]
233 return memoryview(sliceable)[offset:]
234
234
235 import subprocess
235 import subprocess
236 closefds = os.name == 'posix'
236 closefds = os.name == 'posix'
237
237
238 _chunksize = 4096
238 _chunksize = 4096
239
239
240 class bufferedinputpipe(object):
240 class bufferedinputpipe(object):
241 """a manually buffered input pipe
241 """a manually buffered input pipe
242
242
243 Python will not let us use buffered IO and lazy reading with 'polling' at
243 Python will not let us use buffered IO and lazy reading with 'polling' at
244 the same time. We cannot probe the buffer state and select will not detect
244 the same time. We cannot probe the buffer state and select will not detect
245 that data are ready to read if they are already buffered.
245 that data are ready to read if they are already buffered.
246
246
247 This class let us work around that by implementing its own buffering
247 This class let us work around that by implementing its own buffering
248 (allowing efficient readline) while offering a way to know if the buffer is
248 (allowing efficient readline) while offering a way to know if the buffer is
249 empty from the output (allowing collaboration of the buffer with polling).
249 empty from the output (allowing collaboration of the buffer with polling).
250
250
251 This class lives in the 'util' module because it makes use of the 'os'
251 This class lives in the 'util' module because it makes use of the 'os'
252 module from the python stdlib.
252 module from the python stdlib.
253 """
253 """
254
254
255 def __init__(self, input):
255 def __init__(self, input):
256 self._input = input
256 self._input = input
257 self._buffer = []
257 self._buffer = []
258 self._eof = False
258 self._eof = False
259 self._lenbuf = 0
259 self._lenbuf = 0
260
260
261 @property
261 @property
262 def hasbuffer(self):
262 def hasbuffer(self):
263 """True is any data is currently buffered
263 """True is any data is currently buffered
264
264
265 This will be used externally a pre-step for polling IO. If there is
265 This will be used externally a pre-step for polling IO. If there is
266 already data then no polling should be set in place."""
266 already data then no polling should be set in place."""
267 return bool(self._buffer)
267 return bool(self._buffer)
268
268
269 @property
269 @property
270 def closed(self):
270 def closed(self):
271 return self._input.closed
271 return self._input.closed
272
272
273 def fileno(self):
273 def fileno(self):
274 return self._input.fileno()
274 return self._input.fileno()
275
275
276 def close(self):
276 def close(self):
277 return self._input.close()
277 return self._input.close()
278
278
279 def read(self, size):
279 def read(self, size):
280 while (not self._eof) and (self._lenbuf < size):
280 while (not self._eof) and (self._lenbuf < size):
281 self._fillbuffer()
281 self._fillbuffer()
282 return self._frombuffer(size)
282 return self._frombuffer(size)
283
283
284 def readline(self, *args, **kwargs):
284 def readline(self, *args, **kwargs):
285 if 1 < len(self._buffer):
285 if 1 < len(self._buffer):
286 # this should not happen because both read and readline end with a
286 # this should not happen because both read and readline end with a
287 # _frombuffer call that collapse it.
287 # _frombuffer call that collapse it.
288 self._buffer = [''.join(self._buffer)]
288 self._buffer = [''.join(self._buffer)]
289 self._lenbuf = len(self._buffer[0])
289 self._lenbuf = len(self._buffer[0])
290 lfi = -1
290 lfi = -1
291 if self._buffer:
291 if self._buffer:
292 lfi = self._buffer[-1].find('\n')
292 lfi = self._buffer[-1].find('\n')
293 while (not self._eof) and lfi < 0:
293 while (not self._eof) and lfi < 0:
294 self._fillbuffer()
294 self._fillbuffer()
295 if self._buffer:
295 if self._buffer:
296 lfi = self._buffer[-1].find('\n')
296 lfi = self._buffer[-1].find('\n')
297 size = lfi + 1
297 size = lfi + 1
298 if lfi < 0: # end of file
298 if lfi < 0: # end of file
299 size = self._lenbuf
299 size = self._lenbuf
300 elif 1 < len(self._buffer):
300 elif 1 < len(self._buffer):
301 # we need to take previous chunks into account
301 # we need to take previous chunks into account
302 size += self._lenbuf - len(self._buffer[-1])
302 size += self._lenbuf - len(self._buffer[-1])
303 return self._frombuffer(size)
303 return self._frombuffer(size)
304
304
305 def _frombuffer(self, size):
305 def _frombuffer(self, size):
306 """return at most 'size' data from the buffer
306 """return at most 'size' data from the buffer
307
307
308 The data are removed from the buffer."""
308 The data are removed from the buffer."""
309 if size == 0 or not self._buffer:
309 if size == 0 or not self._buffer:
310 return ''
310 return ''
311 buf = self._buffer[0]
311 buf = self._buffer[0]
312 if 1 < len(self._buffer):
312 if 1 < len(self._buffer):
313 buf = ''.join(self._buffer)
313 buf = ''.join(self._buffer)
314
314
315 data = buf[:size]
315 data = buf[:size]
316 buf = buf[len(data):]
316 buf = buf[len(data):]
317 if buf:
317 if buf:
318 self._buffer = [buf]
318 self._buffer = [buf]
319 self._lenbuf = len(buf)
319 self._lenbuf = len(buf)
320 else:
320 else:
321 self._buffer = []
321 self._buffer = []
322 self._lenbuf = 0
322 self._lenbuf = 0
323 return data
323 return data
324
324
325 def _fillbuffer(self):
325 def _fillbuffer(self):
326 """read data to the buffer"""
326 """read data to the buffer"""
327 data = os.read(self._input.fileno(), _chunksize)
327 data = os.read(self._input.fileno(), _chunksize)
328 if not data:
328 if not data:
329 self._eof = True
329 self._eof = True
330 else:
330 else:
331 self._lenbuf += len(data)
331 self._lenbuf += len(data)
332 self._buffer.append(data)
332 self._buffer.append(data)
333
333
334 def popen2(cmd, env=None, newlines=False):
334 def popen2(cmd, env=None, newlines=False):
335 # Setting bufsize to -1 lets the system decide the buffer size.
335 # Setting bufsize to -1 lets the system decide the buffer size.
336 # The default for bufsize is 0, meaning unbuffered. This leads to
336 # The default for bufsize is 0, meaning unbuffered. This leads to
337 # poor performance on Mac OS X: http://bugs.python.org/issue4194
337 # poor performance on Mac OS X: http://bugs.python.org/issue4194
338 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
338 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
339 close_fds=closefds,
339 close_fds=closefds,
340 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
340 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
341 universal_newlines=newlines,
341 universal_newlines=newlines,
342 env=env)
342 env=env)
343 return p.stdin, p.stdout
343 return p.stdin, p.stdout
344
344
345 def popen3(cmd, env=None, newlines=False):
345 def popen3(cmd, env=None, newlines=False):
346 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
346 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
347 return stdin, stdout, stderr
347 return stdin, stdout, stderr
348
348
349 def popen4(cmd, env=None, newlines=False, bufsize=-1):
349 def popen4(cmd, env=None, newlines=False, bufsize=-1):
350 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
350 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
351 close_fds=closefds,
351 close_fds=closefds,
352 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
352 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
353 stderr=subprocess.PIPE,
353 stderr=subprocess.PIPE,
354 universal_newlines=newlines,
354 universal_newlines=newlines,
355 env=env)
355 env=env)
356 return p.stdin, p.stdout, p.stderr, p
356 return p.stdin, p.stdout, p.stderr, p
357
357
358 def version():
358 def version():
359 """Return version information if available."""
359 """Return version information if available."""
360 try:
360 try:
361 import __version__
361 import __version__
362 return __version__.version
362 return __version__.version
363 except ImportError:
363 except ImportError:
364 return 'unknown'
364 return 'unknown'
365
365
366 # used by parsedate
366 # used by parsedate
367 defaultdateformats = (
367 defaultdateformats = (
368 '%Y-%m-%d %H:%M:%S',
368 '%Y-%m-%d %H:%M:%S',
369 '%Y-%m-%d %I:%M:%S%p',
369 '%Y-%m-%d %I:%M:%S%p',
370 '%Y-%m-%d %H:%M',
370 '%Y-%m-%d %H:%M',
371 '%Y-%m-%d %I:%M%p',
371 '%Y-%m-%d %I:%M%p',
372 '%Y-%m-%d',
372 '%Y-%m-%d',
373 '%m-%d',
373 '%m-%d',
374 '%m/%d',
374 '%m/%d',
375 '%m/%d/%y',
375 '%m/%d/%y',
376 '%m/%d/%Y',
376 '%m/%d/%Y',
377 '%a %b %d %H:%M:%S %Y',
377 '%a %b %d %H:%M:%S %Y',
378 '%a %b %d %I:%M:%S%p %Y',
378 '%a %b %d %I:%M:%S%p %Y',
379 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
379 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
380 '%b %d %H:%M:%S %Y',
380 '%b %d %H:%M:%S %Y',
381 '%b %d %I:%M:%S%p %Y',
381 '%b %d %I:%M:%S%p %Y',
382 '%b %d %H:%M:%S',
382 '%b %d %H:%M:%S',
383 '%b %d %I:%M:%S%p',
383 '%b %d %I:%M:%S%p',
384 '%b %d %H:%M',
384 '%b %d %H:%M',
385 '%b %d %I:%M%p',
385 '%b %d %I:%M%p',
386 '%b %d %Y',
386 '%b %d %Y',
387 '%b %d',
387 '%b %d',
388 '%H:%M:%S',
388 '%H:%M:%S',
389 '%I:%M:%S%p',
389 '%I:%M:%S%p',
390 '%H:%M',
390 '%H:%M',
391 '%I:%M%p',
391 '%I:%M%p',
392 )
392 )
393
393
394 extendeddateformats = defaultdateformats + (
394 extendeddateformats = defaultdateformats + (
395 "%Y",
395 "%Y",
396 "%Y-%m",
396 "%Y-%m",
397 "%b",
397 "%b",
398 "%b %Y",
398 "%b %Y",
399 )
399 )
400
400
401 def cachefunc(func):
401 def cachefunc(func):
402 '''cache the result of function calls'''
402 '''cache the result of function calls'''
403 # XXX doesn't handle keywords args
403 # XXX doesn't handle keywords args
404 if func.func_code.co_argcount == 0:
404 if func.func_code.co_argcount == 0:
405 cache = []
405 cache = []
406 def f():
406 def f():
407 if len(cache) == 0:
407 if len(cache) == 0:
408 cache.append(func())
408 cache.append(func())
409 return cache[0]
409 return cache[0]
410 return f
410 return f
411 cache = {}
411 cache = {}
412 if func.func_code.co_argcount == 1:
412 if func.func_code.co_argcount == 1:
413 # we gain a small amount of time because
413 # we gain a small amount of time because
414 # we don't need to pack/unpack the list
414 # we don't need to pack/unpack the list
415 def f(arg):
415 def f(arg):
416 if arg not in cache:
416 if arg not in cache:
417 cache[arg] = func(arg)
417 cache[arg] = func(arg)
418 return cache[arg]
418 return cache[arg]
419 else:
419 else:
420 def f(*args):
420 def f(*args):
421 if args not in cache:
421 if args not in cache:
422 cache[args] = func(*args)
422 cache[args] = func(*args)
423 return cache[args]
423 return cache[args]
424
424
425 return f
425 return f
426
426
427 class sortdict(dict):
427 class sortdict(dict):
428 '''a simple sorted dictionary'''
428 '''a simple sorted dictionary'''
429 def __init__(self, data=None):
429 def __init__(self, data=None):
430 self._list = []
430 self._list = []
431 if data:
431 if data:
432 self.update(data)
432 self.update(data)
433 def copy(self):
433 def copy(self):
434 return sortdict(self)
434 return sortdict(self)
435 def __setitem__(self, key, val):
435 def __setitem__(self, key, val):
436 if key in self:
436 if key in self:
437 self._list.remove(key)
437 self._list.remove(key)
438 self._list.append(key)
438 self._list.append(key)
439 dict.__setitem__(self, key, val)
439 dict.__setitem__(self, key, val)
440 def __iter__(self):
440 def __iter__(self):
441 return self._list.__iter__()
441 return self._list.__iter__()
442 def update(self, src):
442 def update(self, src):
443 if isinstance(src, dict):
443 if isinstance(src, dict):
444 src = src.iteritems()
444 src = src.iteritems()
445 for k, v in src:
445 for k, v in src:
446 self[k] = v
446 self[k] = v
447 def clear(self):
447 def clear(self):
448 dict.clear(self)
448 dict.clear(self)
449 self._list = []
449 self._list = []
450 def items(self):
450 def items(self):
451 return [(k, self[k]) for k in self._list]
451 return [(k, self[k]) for k in self._list]
452 def __delitem__(self, key):
452 def __delitem__(self, key):
453 dict.__delitem__(self, key)
453 dict.__delitem__(self, key)
454 self._list.remove(key)
454 self._list.remove(key)
455 def pop(self, key, *args, **kwargs):
455 def pop(self, key, *args, **kwargs):
456 dict.pop(self, key, *args, **kwargs)
456 dict.pop(self, key, *args, **kwargs)
457 try:
457 try:
458 self._list.remove(key)
458 self._list.remove(key)
459 except ValueError:
459 except ValueError:
460 pass
460 pass
461 def keys(self):
461 def keys(self):
462 return self._list
462 return self._list
463 def iterkeys(self):
463 def iterkeys(self):
464 return self._list.__iter__()
464 return self._list.__iter__()
465 def iteritems(self):
465 def iteritems(self):
466 for k in self._list:
466 for k in self._list:
467 yield k, self[k]
467 yield k, self[k]
468 def insert(self, index, key, val):
468 def insert(self, index, key, val):
469 self._list.insert(index, key)
469 self._list.insert(index, key)
470 dict.__setitem__(self, key, val)
470 dict.__setitem__(self, key, val)
471
471
472 class lrucachedict(object):
472 class lrucachedict(object):
473 '''cache most recent gets from or sets to this dictionary'''
473 '''cache most recent gets from or sets to this dictionary'''
474 def __init__(self, maxsize):
474 def __init__(self, maxsize):
475 self._cache = {}
475 self._cache = {}
476 self._maxsize = maxsize
476 self._maxsize = maxsize
477 self._order = collections.deque()
477 self._order = collections.deque()
478
478
479 def __getitem__(self, key):
479 def __getitem__(self, key):
480 value = self._cache[key]
480 value = self._cache[key]
481 self._order.remove(key)
481 self._order.remove(key)
482 self._order.append(key)
482 self._order.append(key)
483 return value
483 return value
484
484
485 def __setitem__(self, key, value):
485 def __setitem__(self, key, value):
486 if key not in self._cache:
486 if key not in self._cache:
487 if len(self._cache) >= self._maxsize:
487 if len(self._cache) >= self._maxsize:
488 del self._cache[self._order.popleft()]
488 del self._cache[self._order.popleft()]
489 else:
489 else:
490 self._order.remove(key)
490 self._order.remove(key)
491 self._cache[key] = value
491 self._cache[key] = value
492 self._order.append(key)
492 self._order.append(key)
493
493
494 def __contains__(self, key):
494 def __contains__(self, key):
495 return key in self._cache
495 return key in self._cache
496
496
497 def clear(self):
497 def clear(self):
498 self._cache.clear()
498 self._cache.clear()
499 self._order = collections.deque()
499 self._order = collections.deque()
500
500
501 def lrucachefunc(func):
501 def lrucachefunc(func):
502 '''cache most recent results of function calls'''
502 '''cache most recent results of function calls'''
503 cache = {}
503 cache = {}
504 order = collections.deque()
504 order = collections.deque()
505 if func.func_code.co_argcount == 1:
505 if func.func_code.co_argcount == 1:
506 def f(arg):
506 def f(arg):
507 if arg not in cache:
507 if arg not in cache:
508 if len(cache) > 20:
508 if len(cache) > 20:
509 del cache[order.popleft()]
509 del cache[order.popleft()]
510 cache[arg] = func(arg)
510 cache[arg] = func(arg)
511 else:
511 else:
512 order.remove(arg)
512 order.remove(arg)
513 order.append(arg)
513 order.append(arg)
514 return cache[arg]
514 return cache[arg]
515 else:
515 else:
516 def f(*args):
516 def f(*args):
517 if args not in cache:
517 if args not in cache:
518 if len(cache) > 20:
518 if len(cache) > 20:
519 del cache[order.popleft()]
519 del cache[order.popleft()]
520 cache[args] = func(*args)
520 cache[args] = func(*args)
521 else:
521 else:
522 order.remove(args)
522 order.remove(args)
523 order.append(args)
523 order.append(args)
524 return cache[args]
524 return cache[args]
525
525
526 return f
526 return f
527
527
528 class propertycache(object):
528 class propertycache(object):
529 def __init__(self, func):
529 def __init__(self, func):
530 self.func = func
530 self.func = func
531 self.name = func.__name__
531 self.name = func.__name__
532 def __get__(self, obj, type=None):
532 def __get__(self, obj, type=None):
533 result = self.func(obj)
533 result = self.func(obj)
534 self.cachevalue(obj, result)
534 self.cachevalue(obj, result)
535 return result
535 return result
536
536
537 def cachevalue(self, obj, value):
537 def cachevalue(self, obj, value):
538 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
538 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
539 obj.__dict__[self.name] = value
539 obj.__dict__[self.name] = value
540
540
541 def pipefilter(s, cmd):
541 def pipefilter(s, cmd):
542 '''filter string S through command CMD, returning its output'''
542 '''filter string S through command CMD, returning its output'''
543 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
543 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
544 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
544 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
545 pout, perr = p.communicate(s)
545 pout, perr = p.communicate(s)
546 return pout
546 return pout
547
547
548 def tempfilter(s, cmd):
548 def tempfilter(s, cmd):
549 '''filter string S through a pair of temporary files with CMD.
549 '''filter string S through a pair of temporary files with CMD.
550 CMD is used as a template to create the real command to be run,
550 CMD is used as a template to create the real command to be run,
551 with the strings INFILE and OUTFILE replaced by the real names of
551 with the strings INFILE and OUTFILE replaced by the real names of
552 the temporary files generated.'''
552 the temporary files generated.'''
553 inname, outname = None, None
553 inname, outname = None, None
554 try:
554 try:
555 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
555 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
556 fp = os.fdopen(infd, 'wb')
556 fp = os.fdopen(infd, 'wb')
557 fp.write(s)
557 fp.write(s)
558 fp.close()
558 fp.close()
559 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
559 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
560 os.close(outfd)
560 os.close(outfd)
561 cmd = cmd.replace('INFILE', inname)
561 cmd = cmd.replace('INFILE', inname)
562 cmd = cmd.replace('OUTFILE', outname)
562 cmd = cmd.replace('OUTFILE', outname)
563 code = os.system(cmd)
563 code = os.system(cmd)
564 if sys.platform == 'OpenVMS' and code & 1:
564 if sys.platform == 'OpenVMS' and code & 1:
565 code = 0
565 code = 0
566 if code:
566 if code:
567 raise Abort(_("command '%s' failed: %s") %
567 raise Abort(_("command '%s' failed: %s") %
568 (cmd, explainexit(code)))
568 (cmd, explainexit(code)))
569 fp = open(outname, 'rb')
569 fp = open(outname, 'rb')
570 r = fp.read()
570 r = fp.read()
571 fp.close()
571 fp.close()
572 return r
572 return r
573 finally:
573 finally:
574 try:
574 try:
575 if inname:
575 if inname:
576 os.unlink(inname)
576 os.unlink(inname)
577 except OSError:
577 except OSError:
578 pass
578 pass
579 try:
579 try:
580 if outname:
580 if outname:
581 os.unlink(outname)
581 os.unlink(outname)
582 except OSError:
582 except OSError:
583 pass
583 pass
584
584
585 filtertable = {
585 filtertable = {
586 'tempfile:': tempfilter,
586 'tempfile:': tempfilter,
587 'pipe:': pipefilter,
587 'pipe:': pipefilter,
588 }
588 }
589
589
590 def filter(s, cmd):
590 def filter(s, cmd):
591 "filter a string through a command that transforms its input to its output"
591 "filter a string through a command that transforms its input to its output"
592 for name, fn in filtertable.iteritems():
592 for name, fn in filtertable.iteritems():
593 if cmd.startswith(name):
593 if cmd.startswith(name):
594 return fn(s, cmd[len(name):].lstrip())
594 return fn(s, cmd[len(name):].lstrip())
595 return pipefilter(s, cmd)
595 return pipefilter(s, cmd)
596
596
597 def binary(s):
597 def binary(s):
598 """return true if a string is binary data"""
598 """return true if a string is binary data"""
599 return bool(s and '\0' in s)
599 return bool(s and '\0' in s)
600
600
601 def increasingchunks(source, min=1024, max=65536):
601 def increasingchunks(source, min=1024, max=65536):
602 '''return no less than min bytes per chunk while data remains,
602 '''return no less than min bytes per chunk while data remains,
603 doubling min after each chunk until it reaches max'''
603 doubling min after each chunk until it reaches max'''
604 def log2(x):
604 def log2(x):
605 if not x:
605 if not x:
606 return 0
606 return 0
607 i = 0
607 i = 0
608 while x:
608 while x:
609 x >>= 1
609 x >>= 1
610 i += 1
610 i += 1
611 return i - 1
611 return i - 1
612
612
613 buf = []
613 buf = []
614 blen = 0
614 blen = 0
615 for chunk in source:
615 for chunk in source:
616 buf.append(chunk)
616 buf.append(chunk)
617 blen += len(chunk)
617 blen += len(chunk)
618 if blen >= min:
618 if blen >= min:
619 if min < max:
619 if min < max:
620 min = min << 1
620 min = min << 1
621 nmin = 1 << log2(blen)
621 nmin = 1 << log2(blen)
622 if nmin > min:
622 if nmin > min:
623 min = nmin
623 min = nmin
624 if min > max:
624 if min > max:
625 min = max
625 min = max
626 yield ''.join(buf)
626 yield ''.join(buf)
627 blen = 0
627 blen = 0
628 buf = []
628 buf = []
629 if buf:
629 if buf:
630 yield ''.join(buf)
630 yield ''.join(buf)
631
631
632 Abort = error.Abort
632 Abort = error.Abort
633
633
634 def always(fn):
634 def always(fn):
635 return True
635 return True
636
636
637 def never(fn):
637 def never(fn):
638 return False
638 return False
639
639
640 def nogc(func):
640 def nogc(func):
641 """disable garbage collector
641 """disable garbage collector
642
642
643 Python's garbage collector triggers a GC each time a certain number of
643 Python's garbage collector triggers a GC each time a certain number of
644 container objects (the number being defined by gc.get_threshold()) are
644 container objects (the number being defined by gc.get_threshold()) are
645 allocated even when marked not to be tracked by the collector. Tracking has
645 allocated even when marked not to be tracked by the collector. Tracking has
646 no effect on when GCs are triggered, only on what objects the GC looks
646 no effect on when GCs are triggered, only on what objects the GC looks
647 into. As a workaround, disable GC while building complex (huge)
647 into. As a workaround, disable GC while building complex (huge)
648 containers.
648 containers.
649
649
650 This garbage collector issue have been fixed in 2.7.
650 This garbage collector issue have been fixed in 2.7.
651 """
651 """
652 def wrapper(*args, **kwargs):
652 def wrapper(*args, **kwargs):
653 gcenabled = gc.isenabled()
653 gcenabled = gc.isenabled()
654 gc.disable()
654 gc.disable()
655 try:
655 try:
656 return func(*args, **kwargs)
656 return func(*args, **kwargs)
657 finally:
657 finally:
658 if gcenabled:
658 if gcenabled:
659 gc.enable()
659 gc.enable()
660 return wrapper
660 return wrapper
661
661
662 def pathto(root, n1, n2):
662 def pathto(root, n1, n2):
663 '''return the relative path from one place to another.
663 '''return the relative path from one place to another.
664 root should use os.sep to separate directories
664 root should use os.sep to separate directories
665 n1 should use os.sep to separate directories
665 n1 should use os.sep to separate directories
666 n2 should use "/" to separate directories
666 n2 should use "/" to separate directories
667 returns an os.sep-separated path.
667 returns an os.sep-separated path.
668
668
669 If n1 is a relative path, it's assumed it's
669 If n1 is a relative path, it's assumed it's
670 relative to root.
670 relative to root.
671 n2 should always be relative to root.
671 n2 should always be relative to root.
672 '''
672 '''
673 if not n1:
673 if not n1:
674 return localpath(n2)
674 return localpath(n2)
675 if os.path.isabs(n1):
675 if os.path.isabs(n1):
676 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
676 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
677 return os.path.join(root, localpath(n2))
677 return os.path.join(root, localpath(n2))
678 n2 = '/'.join((pconvert(root), n2))
678 n2 = '/'.join((pconvert(root), n2))
679 a, b = splitpath(n1), n2.split('/')
679 a, b = splitpath(n1), n2.split('/')
680 a.reverse()
680 a.reverse()
681 b.reverse()
681 b.reverse()
682 while a and b and a[-1] == b[-1]:
682 while a and b and a[-1] == b[-1]:
683 a.pop()
683 a.pop()
684 b.pop()
684 b.pop()
685 b.reverse()
685 b.reverse()
686 return os.sep.join((['..'] * len(a)) + b) or '.'
686 return os.sep.join((['..'] * len(a)) + b) or '.'
687
687
688 def mainfrozen():
688 def mainfrozen():
689 """return True if we are a frozen executable.
689 """return True if we are a frozen executable.
690
690
691 The code supports py2exe (most common, Windows only) and tools/freeze
691 The code supports py2exe (most common, Windows only) and tools/freeze
692 (portable, not much used).
692 (portable, not much used).
693 """
693 """
694 return (safehasattr(sys, "frozen") or # new py2exe
694 return (safehasattr(sys, "frozen") or # new py2exe
695 safehasattr(sys, "importers") or # old py2exe
695 safehasattr(sys, "importers") or # old py2exe
696 imp.is_frozen("__main__")) # tools/freeze
696 imp.is_frozen("__main__")) # tools/freeze
697
697
698 # the location of data files matching the source code
698 # the location of data files matching the source code
699 if mainfrozen():
699 if mainfrozen():
700 # executable version (py2exe) doesn't support __file__
700 # executable version (py2exe) doesn't support __file__
701 datapath = os.path.dirname(sys.executable)
701 datapath = os.path.dirname(sys.executable)
702 else:
702 else:
703 datapath = os.path.dirname(__file__)
703 datapath = os.path.dirname(__file__)
704
704
705 i18n.setdatapath(datapath)
705 i18n.setdatapath(datapath)
706
706
707 _hgexecutable = None
707 _hgexecutable = None
708
708
709 def hgexecutable():
709 def hgexecutable():
710 """return location of the 'hg' executable.
710 """return location of the 'hg' executable.
711
711
712 Defaults to $HG or 'hg' in the search path.
712 Defaults to $HG or 'hg' in the search path.
713 """
713 """
714 if _hgexecutable is None:
714 if _hgexecutable is None:
715 hg = os.environ.get('HG')
715 hg = os.environ.get('HG')
716 mainmod = sys.modules['__main__']
716 mainmod = sys.modules['__main__']
717 if hg:
717 if hg:
718 _sethgexecutable(hg)
718 _sethgexecutable(hg)
719 elif mainfrozen():
719 elif mainfrozen():
720 _sethgexecutable(sys.executable)
720 _sethgexecutable(sys.executable)
721 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
721 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
722 _sethgexecutable(mainmod.__file__)
722 _sethgexecutable(mainmod.__file__)
723 else:
723 else:
724 exe = findexe('hg') or os.path.basename(sys.argv[0])
724 exe = findexe('hg') or os.path.basename(sys.argv[0])
725 _sethgexecutable(exe)
725 _sethgexecutable(exe)
726 return _hgexecutable
726 return _hgexecutable
727
727
728 def _sethgexecutable(path):
728 def _sethgexecutable(path):
729 """set location of the 'hg' executable"""
729 """set location of the 'hg' executable"""
730 global _hgexecutable
730 global _hgexecutable
731 _hgexecutable = path
731 _hgexecutable = path
732
732
733 def _isstdout(f):
733 def _isstdout(f):
734 fileno = getattr(f, 'fileno', None)
734 fileno = getattr(f, 'fileno', None)
735 return fileno and fileno() == sys.__stdout__.fileno()
735 return fileno and fileno() == sys.__stdout__.fileno()
736
736
737 def system(cmd, environ=None, cwd=None, onerr=None, errprefix=None, out=None):
737 def system(cmd, environ=None, cwd=None, onerr=None, errprefix=None, out=None):
738 '''enhanced shell command execution.
738 '''enhanced shell command execution.
739 run with environment maybe modified, maybe in different dir.
739 run with environment maybe modified, maybe in different dir.
740
740
741 if command fails and onerr is None, return status, else raise onerr
741 if command fails and onerr is None, return status, else raise onerr
742 object as exception.
742 object as exception.
743
743
744 if out is specified, it is assumed to be a file-like object that has a
744 if out is specified, it is assumed to be a file-like object that has a
745 write() method. stdout and stderr will be redirected to out.'''
745 write() method. stdout and stderr will be redirected to out.'''
746 if environ is None:
746 if environ is None:
747 environ = {}
747 environ = {}
748 try:
748 try:
749 sys.stdout.flush()
749 sys.stdout.flush()
750 except Exception:
750 except Exception:
751 pass
751 pass
752 def py2shell(val):
752 def py2shell(val):
753 'convert python object into string that is useful to shell'
753 'convert python object into string that is useful to shell'
754 if val is None or val is False:
754 if val is None or val is False:
755 return '0'
755 return '0'
756 if val is True:
756 if val is True:
757 return '1'
757 return '1'
758 return str(val)
758 return str(val)
759 origcmd = cmd
759 origcmd = cmd
760 cmd = quotecommand(cmd)
760 cmd = quotecommand(cmd)
761 if sys.platform == 'plan9' and (sys.version_info[0] == 2
761 if sys.platform == 'plan9' and (sys.version_info[0] == 2
762 and sys.version_info[1] < 7):
762 and sys.version_info[1] < 7):
763 # subprocess kludge to work around issues in half-baked Python
763 # subprocess kludge to work around issues in half-baked Python
764 # ports, notably bichued/python:
764 # ports, notably bichued/python:
765 if not cwd is None:
765 if not cwd is None:
766 os.chdir(cwd)
766 os.chdir(cwd)
767 rc = os.system(cmd)
767 rc = os.system(cmd)
768 else:
768 else:
769 env = dict(os.environ)
769 env = dict(os.environ)
770 env.update((k, py2shell(v)) for k, v in environ.iteritems())
770 env.update((k, py2shell(v)) for k, v in environ.iteritems())
771 env['HG'] = hgexecutable()
771 env['HG'] = hgexecutable()
772 if out is None or _isstdout(out):
772 if out is None or _isstdout(out):
773 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
773 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
774 env=env, cwd=cwd)
774 env=env, cwd=cwd)
775 else:
775 else:
776 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
776 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
777 env=env, cwd=cwd, stdout=subprocess.PIPE,
777 env=env, cwd=cwd, stdout=subprocess.PIPE,
778 stderr=subprocess.STDOUT)
778 stderr=subprocess.STDOUT)
779 while True:
779 while True:
780 line = proc.stdout.readline()
780 line = proc.stdout.readline()
781 if not line:
781 if not line:
782 break
782 break
783 out.write(line)
783 out.write(line)
784 proc.wait()
784 proc.wait()
785 rc = proc.returncode
785 rc = proc.returncode
786 if sys.platform == 'OpenVMS' and rc & 1:
786 if sys.platform == 'OpenVMS' and rc & 1:
787 rc = 0
787 rc = 0
788 if rc and onerr:
788 if rc and onerr:
789 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
789 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
790 explainexit(rc)[0])
790 explainexit(rc)[0])
791 if errprefix:
791 if errprefix:
792 errmsg = '%s: %s' % (errprefix, errmsg)
792 errmsg = '%s: %s' % (errprefix, errmsg)
793 raise onerr(errmsg)
793 raise onerr(errmsg)
794 return rc
794 return rc
795
795
796 def checksignature(func):
796 def checksignature(func):
797 '''wrap a function with code to check for calling errors'''
797 '''wrap a function with code to check for calling errors'''
798 def check(*args, **kwargs):
798 def check(*args, **kwargs):
799 try:
799 try:
800 return func(*args, **kwargs)
800 return func(*args, **kwargs)
801 except TypeError:
801 except TypeError:
802 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
802 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
803 raise error.SignatureError
803 raise error.SignatureError
804 raise
804 raise
805
805
806 return check
806 return check
807
807
808 def copyfile(src, dest, hardlink=False):
808 def copyfile(src, dest, hardlink=False):
809 "copy a file, preserving mode and atime/mtime"
809 "copy a file, preserving mode and atime/mtime"
810 if os.path.lexists(dest):
810 if os.path.lexists(dest):
811 unlink(dest)
811 unlink(dest)
812 # hardlinks are problematic on CIFS, quietly ignore this flag
812 # hardlinks are problematic on CIFS, quietly ignore this flag
813 # until we find a way to work around it cleanly (issue4546)
813 # until we find a way to work around it cleanly (issue4546)
814 if False and hardlink:
814 if False and hardlink:
815 try:
815 try:
816 oslink(src, dest)
816 oslink(src, dest)
817 return
817 return
818 except (IOError, OSError):
818 except (IOError, OSError):
819 pass # fall back to normal copy
819 pass # fall back to normal copy
820 if os.path.islink(src):
820 if os.path.islink(src):
821 os.symlink(os.readlink(src), dest)
821 os.symlink(os.readlink(src), dest)
822 else:
822 else:
823 try:
823 try:
824 shutil.copyfile(src, dest)
824 shutil.copyfile(src, dest)
825 shutil.copymode(src, dest)
825 shutil.copymode(src, dest)
826 except shutil.Error as inst:
826 except shutil.Error as inst:
827 raise Abort(str(inst))
827 raise Abort(str(inst))
828
828
829 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
829 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
830 """Copy a directory tree using hardlinks if possible."""
830 """Copy a directory tree using hardlinks if possible."""
831 num = 0
831 num = 0
832
832
833 if hardlink is None:
833 if hardlink is None:
834 hardlink = (os.stat(src).st_dev ==
834 hardlink = (os.stat(src).st_dev ==
835 os.stat(os.path.dirname(dst)).st_dev)
835 os.stat(os.path.dirname(dst)).st_dev)
836 if hardlink:
836 if hardlink:
837 topic = _('linking')
837 topic = _('linking')
838 else:
838 else:
839 topic = _('copying')
839 topic = _('copying')
840
840
841 if os.path.isdir(src):
841 if os.path.isdir(src):
842 os.mkdir(dst)
842 os.mkdir(dst)
843 for name, kind in osutil.listdir(src):
843 for name, kind in osutil.listdir(src):
844 srcname = os.path.join(src, name)
844 srcname = os.path.join(src, name)
845 dstname = os.path.join(dst, name)
845 dstname = os.path.join(dst, name)
846 def nprog(t, pos):
846 def nprog(t, pos):
847 if pos is not None:
847 if pos is not None:
848 return progress(t, pos + num)
848 return progress(t, pos + num)
849 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
849 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
850 num += n
850 num += n
851 else:
851 else:
852 if hardlink:
852 if hardlink:
853 try:
853 try:
854 oslink(src, dst)
854 oslink(src, dst)
855 except (IOError, OSError):
855 except (IOError, OSError):
856 hardlink = False
856 hardlink = False
857 shutil.copy(src, dst)
857 shutil.copy(src, dst)
858 else:
858 else:
859 shutil.copy(src, dst)
859 shutil.copy(src, dst)
860 num += 1
860 num += 1
861 progress(topic, num)
861 progress(topic, num)
862 progress(topic, None)
862 progress(topic, None)
863
863
864 return hardlink, num
864 return hardlink, num
865
865
866 _winreservednames = '''con prn aux nul
866 _winreservednames = '''con prn aux nul
867 com1 com2 com3 com4 com5 com6 com7 com8 com9
867 com1 com2 com3 com4 com5 com6 com7 com8 com9
868 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
868 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
869 _winreservedchars = ':*?"<>|'
869 _winreservedchars = ':*?"<>|'
870 def checkwinfilename(path):
870 def checkwinfilename(path):
871 r'''Check that the base-relative path is a valid filename on Windows.
871 r'''Check that the base-relative path is a valid filename on Windows.
872 Returns None if the path is ok, or a UI string describing the problem.
872 Returns None if the path is ok, or a UI string describing the problem.
873
873
874 >>> checkwinfilename("just/a/normal/path")
874 >>> checkwinfilename("just/a/normal/path")
875 >>> checkwinfilename("foo/bar/con.xml")
875 >>> checkwinfilename("foo/bar/con.xml")
876 "filename contains 'con', which is reserved on Windows"
876 "filename contains 'con', which is reserved on Windows"
877 >>> checkwinfilename("foo/con.xml/bar")
877 >>> checkwinfilename("foo/con.xml/bar")
878 "filename contains 'con', which is reserved on Windows"
878 "filename contains 'con', which is reserved on Windows"
879 >>> checkwinfilename("foo/bar/xml.con")
879 >>> checkwinfilename("foo/bar/xml.con")
880 >>> checkwinfilename("foo/bar/AUX/bla.txt")
880 >>> checkwinfilename("foo/bar/AUX/bla.txt")
881 "filename contains 'AUX', which is reserved on Windows"
881 "filename contains 'AUX', which is reserved on Windows"
882 >>> checkwinfilename("foo/bar/bla:.txt")
882 >>> checkwinfilename("foo/bar/bla:.txt")
883 "filename contains ':', which is reserved on Windows"
883 "filename contains ':', which is reserved on Windows"
884 >>> checkwinfilename("foo/bar/b\07la.txt")
884 >>> checkwinfilename("foo/bar/b\07la.txt")
885 "filename contains '\\x07', which is invalid on Windows"
885 "filename contains '\\x07', which is invalid on Windows"
886 >>> checkwinfilename("foo/bar/bla ")
886 >>> checkwinfilename("foo/bar/bla ")
887 "filename ends with ' ', which is not allowed on Windows"
887 "filename ends with ' ', which is not allowed on Windows"
888 >>> checkwinfilename("../bar")
888 >>> checkwinfilename("../bar")
889 >>> checkwinfilename("foo\\")
889 >>> checkwinfilename("foo\\")
890 "filename ends with '\\', which is invalid on Windows"
890 "filename ends with '\\', which is invalid on Windows"
891 >>> checkwinfilename("foo\\/bar")
891 >>> checkwinfilename("foo\\/bar")
892 "directory name ends with '\\', which is invalid on Windows"
892 "directory name ends with '\\', which is invalid on Windows"
893 '''
893 '''
894 if path.endswith('\\'):
894 if path.endswith('\\'):
895 return _("filename ends with '\\', which is invalid on Windows")
895 return _("filename ends with '\\', which is invalid on Windows")
896 if '\\/' in path:
896 if '\\/' in path:
897 return _("directory name ends with '\\', which is invalid on Windows")
897 return _("directory name ends with '\\', which is invalid on Windows")
898 for n in path.replace('\\', '/').split('/'):
898 for n in path.replace('\\', '/').split('/'):
899 if not n:
899 if not n:
900 continue
900 continue
901 for c in n:
901 for c in n:
902 if c in _winreservedchars:
902 if c in _winreservedchars:
903 return _("filename contains '%s', which is reserved "
903 return _("filename contains '%s', which is reserved "
904 "on Windows") % c
904 "on Windows") % c
905 if ord(c) <= 31:
905 if ord(c) <= 31:
906 return _("filename contains %r, which is invalid "
906 return _("filename contains %r, which is invalid "
907 "on Windows") % c
907 "on Windows") % c
908 base = n.split('.')[0]
908 base = n.split('.')[0]
909 if base and base.lower() in _winreservednames:
909 if base and base.lower() in _winreservednames:
910 return _("filename contains '%s', which is reserved "
910 return _("filename contains '%s', which is reserved "
911 "on Windows") % base
911 "on Windows") % base
912 t = n[-1]
912 t = n[-1]
913 if t in '. ' and n not in '..':
913 if t in '. ' and n not in '..':
914 return _("filename ends with '%s', which is not allowed "
914 return _("filename ends with '%s', which is not allowed "
915 "on Windows") % t
915 "on Windows") % t
916
916
917 if os.name == 'nt':
917 if os.name == 'nt':
918 checkosfilename = checkwinfilename
918 checkosfilename = checkwinfilename
919 else:
919 else:
920 checkosfilename = platform.checkosfilename
920 checkosfilename = platform.checkosfilename
921
921
922 def makelock(info, pathname):
922 def makelock(info, pathname):
923 try:
923 try:
924 return os.symlink(info, pathname)
924 return os.symlink(info, pathname)
925 except OSError as why:
925 except OSError as why:
926 if why.errno == errno.EEXIST:
926 if why.errno == errno.EEXIST:
927 raise
927 raise
928 except AttributeError: # no symlink in os
928 except AttributeError: # no symlink in os
929 pass
929 pass
930
930
931 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
931 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
932 os.write(ld, info)
932 os.write(ld, info)
933 os.close(ld)
933 os.close(ld)
934
934
935 def readlock(pathname):
935 def readlock(pathname):
936 try:
936 try:
937 return os.readlink(pathname)
937 return os.readlink(pathname)
938 except OSError as why:
938 except OSError as why:
939 if why.errno not in (errno.EINVAL, errno.ENOSYS):
939 if why.errno not in (errno.EINVAL, errno.ENOSYS):
940 raise
940 raise
941 except AttributeError: # no symlink in os
941 except AttributeError: # no symlink in os
942 pass
942 pass
943 fp = posixfile(pathname)
943 fp = posixfile(pathname)
944 r = fp.read()
944 r = fp.read()
945 fp.close()
945 fp.close()
946 return r
946 return r
947
947
948 def fstat(fp):
948 def fstat(fp):
949 '''stat file object that may not have fileno method.'''
949 '''stat file object that may not have fileno method.'''
950 try:
950 try:
951 return os.fstat(fp.fileno())
951 return os.fstat(fp.fileno())
952 except AttributeError:
952 except AttributeError:
953 return os.stat(fp.name)
953 return os.stat(fp.name)
954
954
955 # File system features
955 # File system features
956
956
957 def checkcase(path):
957 def checkcase(path):
958 """
958 """
959 Return true if the given path is on a case-sensitive filesystem
959 Return true if the given path is on a case-sensitive filesystem
960
960
961 Requires a path (like /foo/.hg) ending with a foldable final
961 Requires a path (like /foo/.hg) ending with a foldable final
962 directory component.
962 directory component.
963 """
963 """
964 s1 = os.lstat(path)
964 s1 = os.lstat(path)
965 d, b = os.path.split(path)
965 d, b = os.path.split(path)
966 b2 = b.upper()
966 b2 = b.upper()
967 if b == b2:
967 if b == b2:
968 b2 = b.lower()
968 b2 = b.lower()
969 if b == b2:
969 if b == b2:
970 return True # no evidence against case sensitivity
970 return True # no evidence against case sensitivity
971 p2 = os.path.join(d, b2)
971 p2 = os.path.join(d, b2)
972 try:
972 try:
973 s2 = os.lstat(p2)
973 s2 = os.lstat(p2)
974 if s2 == s1:
974 if s2 == s1:
975 return False
975 return False
976 return True
976 return True
977 except OSError:
977 except OSError:
978 return True
978 return True
979
979
980 try:
980 try:
981 import re2
981 import re2
982 _re2 = None
982 _re2 = None
983 except ImportError:
983 except ImportError:
984 _re2 = False
984 _re2 = False
985
985
986 class _re(object):
986 class _re(object):
987 def _checkre2(self):
987 def _checkre2(self):
988 global _re2
988 global _re2
989 try:
989 try:
990 # check if match works, see issue3964
990 # check if match works, see issue3964
991 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
991 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
992 except ImportError:
992 except ImportError:
993 _re2 = False
993 _re2 = False
994
994
995 def compile(self, pat, flags=0):
995 def compile(self, pat, flags=0):
996 '''Compile a regular expression, using re2 if possible
996 '''Compile a regular expression, using re2 if possible
997
997
998 For best performance, use only re2-compatible regexp features. The
998 For best performance, use only re2-compatible regexp features. The
999 only flags from the re module that are re2-compatible are
999 only flags from the re module that are re2-compatible are
1000 IGNORECASE and MULTILINE.'''
1000 IGNORECASE and MULTILINE.'''
1001 if _re2 is None:
1001 if _re2 is None:
1002 self._checkre2()
1002 self._checkre2()
1003 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1003 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1004 if flags & remod.IGNORECASE:
1004 if flags & remod.IGNORECASE:
1005 pat = '(?i)' + pat
1005 pat = '(?i)' + pat
1006 if flags & remod.MULTILINE:
1006 if flags & remod.MULTILINE:
1007 pat = '(?m)' + pat
1007 pat = '(?m)' + pat
1008 try:
1008 try:
1009 return re2.compile(pat)
1009 return re2.compile(pat)
1010 except re2.error:
1010 except re2.error:
1011 pass
1011 pass
1012 return remod.compile(pat, flags)
1012 return remod.compile(pat, flags)
1013
1013
1014 @propertycache
1014 @propertycache
1015 def escape(self):
1015 def escape(self):
1016 '''Return the version of escape corresponding to self.compile.
1016 '''Return the version of escape corresponding to self.compile.
1017
1017
1018 This is imperfect because whether re2 or re is used for a particular
1018 This is imperfect because whether re2 or re is used for a particular
1019 function depends on the flags, etc, but it's the best we can do.
1019 function depends on the flags, etc, but it's the best we can do.
1020 '''
1020 '''
1021 global _re2
1021 global _re2
1022 if _re2 is None:
1022 if _re2 is None:
1023 self._checkre2()
1023 self._checkre2()
1024 if _re2:
1024 if _re2:
1025 return re2.escape
1025 return re2.escape
1026 else:
1026 else:
1027 return remod.escape
1027 return remod.escape
1028
1028
1029 re = _re()
1029 re = _re()
1030
1030
1031 _fspathcache = {}
1031 _fspathcache = {}
1032 def fspath(name, root):
1032 def fspath(name, root):
1033 '''Get name in the case stored in the filesystem
1033 '''Get name in the case stored in the filesystem
1034
1034
1035 The name should be relative to root, and be normcase-ed for efficiency.
1035 The name should be relative to root, and be normcase-ed for efficiency.
1036
1036
1037 Note that this function is unnecessary, and should not be
1037 Note that this function is unnecessary, and should not be
1038 called, for case-sensitive filesystems (simply because it's expensive).
1038 called, for case-sensitive filesystems (simply because it's expensive).
1039
1039
1040 The root should be normcase-ed, too.
1040 The root should be normcase-ed, too.
1041 '''
1041 '''
1042 def _makefspathcacheentry(dir):
1042 def _makefspathcacheentry(dir):
1043 return dict((normcase(n), n) for n in os.listdir(dir))
1043 return dict((normcase(n), n) for n in os.listdir(dir))
1044
1044
1045 seps = os.sep
1045 seps = os.sep
1046 if os.altsep:
1046 if os.altsep:
1047 seps = seps + os.altsep
1047 seps = seps + os.altsep
1048 # Protect backslashes. This gets silly very quickly.
1048 # Protect backslashes. This gets silly very quickly.
1049 seps.replace('\\','\\\\')
1049 seps.replace('\\','\\\\')
1050 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1050 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1051 dir = os.path.normpath(root)
1051 dir = os.path.normpath(root)
1052 result = []
1052 result = []
1053 for part, sep in pattern.findall(name):
1053 for part, sep in pattern.findall(name):
1054 if sep:
1054 if sep:
1055 result.append(sep)
1055 result.append(sep)
1056 continue
1056 continue
1057
1057
1058 if dir not in _fspathcache:
1058 if dir not in _fspathcache:
1059 _fspathcache[dir] = _makefspathcacheentry(dir)
1059 _fspathcache[dir] = _makefspathcacheentry(dir)
1060 contents = _fspathcache[dir]
1060 contents = _fspathcache[dir]
1061
1061
1062 found = contents.get(part)
1062 found = contents.get(part)
1063 if not found:
1063 if not found:
1064 # retry "once per directory" per "dirstate.walk" which
1064 # retry "once per directory" per "dirstate.walk" which
1065 # may take place for each patches of "hg qpush", for example
1065 # may take place for each patches of "hg qpush", for example
1066 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1066 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1067 found = contents.get(part)
1067 found = contents.get(part)
1068
1068
1069 result.append(found or part)
1069 result.append(found or part)
1070 dir = os.path.join(dir, part)
1070 dir = os.path.join(dir, part)
1071
1071
1072 return ''.join(result)
1072 return ''.join(result)
1073
1073
1074 def checknlink(testfile):
1074 def checknlink(testfile):
1075 '''check whether hardlink count reporting works properly'''
1075 '''check whether hardlink count reporting works properly'''
1076
1076
1077 # testfile may be open, so we need a separate file for checking to
1077 # testfile may be open, so we need a separate file for checking to
1078 # work around issue2543 (or testfile may get lost on Samba shares)
1078 # work around issue2543 (or testfile may get lost on Samba shares)
1079 f1 = testfile + ".hgtmp1"
1079 f1 = testfile + ".hgtmp1"
1080 if os.path.lexists(f1):
1080 if os.path.lexists(f1):
1081 return False
1081 return False
1082 try:
1082 try:
1083 posixfile(f1, 'w').close()
1083 posixfile(f1, 'w').close()
1084 except IOError:
1084 except IOError:
1085 return False
1085 return False
1086
1086
1087 f2 = testfile + ".hgtmp2"
1087 f2 = testfile + ".hgtmp2"
1088 fd = None
1088 fd = None
1089 try:
1089 try:
1090 oslink(f1, f2)
1090 oslink(f1, f2)
1091 # nlinks() may behave differently for files on Windows shares if
1091 # nlinks() may behave differently for files on Windows shares if
1092 # the file is open.
1092 # the file is open.
1093 fd = posixfile(f2)
1093 fd = posixfile(f2)
1094 return nlinks(f2) > 1
1094 return nlinks(f2) > 1
1095 except OSError:
1095 except OSError:
1096 return False
1096 return False
1097 finally:
1097 finally:
1098 if fd is not None:
1098 if fd is not None:
1099 fd.close()
1099 fd.close()
1100 for f in (f1, f2):
1100 for f in (f1, f2):
1101 try:
1101 try:
1102 os.unlink(f)
1102 os.unlink(f)
1103 except OSError:
1103 except OSError:
1104 pass
1104 pass
1105
1105
1106 def endswithsep(path):
1106 def endswithsep(path):
1107 '''Check path ends with os.sep or os.altsep.'''
1107 '''Check path ends with os.sep or os.altsep.'''
1108 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1108 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1109
1109
1110 def splitpath(path):
1110 def splitpath(path):
1111 '''Split path by os.sep.
1111 '''Split path by os.sep.
1112 Note that this function does not use os.altsep because this is
1112 Note that this function does not use os.altsep because this is
1113 an alternative of simple "xxx.split(os.sep)".
1113 an alternative of simple "xxx.split(os.sep)".
1114 It is recommended to use os.path.normpath() before using this
1114 It is recommended to use os.path.normpath() before using this
1115 function if need.'''
1115 function if need.'''
1116 return path.split(os.sep)
1116 return path.split(os.sep)
1117
1117
1118 def gui():
1118 def gui():
1119 '''Are we running in a GUI?'''
1119 '''Are we running in a GUI?'''
1120 if sys.platform == 'darwin':
1120 if sys.platform == 'darwin':
1121 if 'SSH_CONNECTION' in os.environ:
1121 if 'SSH_CONNECTION' in os.environ:
1122 # handle SSH access to a box where the user is logged in
1122 # handle SSH access to a box where the user is logged in
1123 return False
1123 return False
1124 elif getattr(osutil, 'isgui', None):
1124 elif getattr(osutil, 'isgui', None):
1125 # check if a CoreGraphics session is available
1125 # check if a CoreGraphics session is available
1126 return osutil.isgui()
1126 return osutil.isgui()
1127 else:
1127 else:
1128 # pure build; use a safe default
1128 # pure build; use a safe default
1129 return True
1129 return True
1130 else:
1130 else:
1131 return os.name == "nt" or os.environ.get("DISPLAY")
1131 return os.name == "nt" or os.environ.get("DISPLAY")
1132
1132
1133 def mktempcopy(name, emptyok=False, createmode=None):
1133 def mktempcopy(name, emptyok=False, createmode=None):
1134 """Create a temporary file with the same contents from name
1134 """Create a temporary file with the same contents from name
1135
1135
1136 The permission bits are copied from the original file.
1136 The permission bits are copied from the original file.
1137
1137
1138 If the temporary file is going to be truncated immediately, you
1138 If the temporary file is going to be truncated immediately, you
1139 can use emptyok=True as an optimization.
1139 can use emptyok=True as an optimization.
1140
1140
1141 Returns the name of the temporary file.
1141 Returns the name of the temporary file.
1142 """
1142 """
1143 d, fn = os.path.split(name)
1143 d, fn = os.path.split(name)
1144 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1144 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1145 os.close(fd)
1145 os.close(fd)
1146 # Temporary files are created with mode 0600, which is usually not
1146 # Temporary files are created with mode 0600, which is usually not
1147 # what we want. If the original file already exists, just copy
1147 # what we want. If the original file already exists, just copy
1148 # its mode. Otherwise, manually obey umask.
1148 # its mode. Otherwise, manually obey umask.
1149 copymode(name, temp, createmode)
1149 copymode(name, temp, createmode)
1150 if emptyok:
1150 if emptyok:
1151 return temp
1151 return temp
1152 try:
1152 try:
1153 try:
1153 try:
1154 ifp = posixfile(name, "rb")
1154 ifp = posixfile(name, "rb")
1155 except IOError as inst:
1155 except IOError as inst:
1156 if inst.errno == errno.ENOENT:
1156 if inst.errno == errno.ENOENT:
1157 return temp
1157 return temp
1158 if not getattr(inst, 'filename', None):
1158 if not getattr(inst, 'filename', None):
1159 inst.filename = name
1159 inst.filename = name
1160 raise
1160 raise
1161 ofp = posixfile(temp, "wb")
1161 ofp = posixfile(temp, "wb")
1162 for chunk in filechunkiter(ifp):
1162 for chunk in filechunkiter(ifp):
1163 ofp.write(chunk)
1163 ofp.write(chunk)
1164 ifp.close()
1164 ifp.close()
1165 ofp.close()
1165 ofp.close()
1166 except: # re-raises
1166 except: # re-raises
1167 try: os.unlink(temp)
1167 try: os.unlink(temp)
1168 except OSError: pass
1168 except OSError: pass
1169 raise
1169 raise
1170 return temp
1170 return temp
1171
1171
1172 class atomictempfile(object):
1172 class atomictempfile(object):
1173 '''writable file object that atomically updates a file
1173 '''writable file object that atomically updates a file
1174
1174
1175 All writes will go to a temporary copy of the original file. Call
1175 All writes will go to a temporary copy of the original file. Call
1176 close() when you are done writing, and atomictempfile will rename
1176 close() when you are done writing, and atomictempfile will rename
1177 the temporary copy to the original name, making the changes
1177 the temporary copy to the original name, making the changes
1178 visible. If the object is destroyed without being closed, all your
1178 visible. If the object is destroyed without being closed, all your
1179 writes are discarded.
1179 writes are discarded.
1180 '''
1180 '''
1181 def __init__(self, name, mode='w+b', createmode=None):
1181 def __init__(self, name, mode='w+b', createmode=None):
1182 self.__name = name # permanent name
1182 self.__name = name # permanent name
1183 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1183 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1184 createmode=createmode)
1184 createmode=createmode)
1185 self._fp = posixfile(self._tempname, mode)
1185 self._fp = posixfile(self._tempname, mode)
1186
1186
1187 # delegated methods
1187 # delegated methods
1188 self.write = self._fp.write
1188 self.write = self._fp.write
1189 self.seek = self._fp.seek
1189 self.seek = self._fp.seek
1190 self.tell = self._fp.tell
1190 self.tell = self._fp.tell
1191 self.fileno = self._fp.fileno
1191 self.fileno = self._fp.fileno
1192
1192
1193 def close(self):
1193 def close(self):
1194 if not self._fp.closed:
1194 if not self._fp.closed:
1195 self._fp.close()
1195 self._fp.close()
1196 rename(self._tempname, localpath(self.__name))
1196 rename(self._tempname, localpath(self.__name))
1197
1197
1198 def discard(self):
1198 def discard(self):
1199 if not self._fp.closed:
1199 if not self._fp.closed:
1200 try:
1200 try:
1201 os.unlink(self._tempname)
1201 os.unlink(self._tempname)
1202 except OSError:
1202 except OSError:
1203 pass
1203 pass
1204 self._fp.close()
1204 self._fp.close()
1205
1205
1206 def __del__(self):
1206 def __del__(self):
1207 if safehasattr(self, '_fp'): # constructor actually did something
1207 if safehasattr(self, '_fp'): # constructor actually did something
1208 self.discard()
1208 self.discard()
1209
1209
1210 def makedirs(name, mode=None, notindexed=False):
1210 def makedirs(name, mode=None, notindexed=False):
1211 """recursive directory creation with parent mode inheritance"""
1211 """recursive directory creation with parent mode inheritance"""
1212 try:
1212 try:
1213 makedir(name, notindexed)
1213 makedir(name, notindexed)
1214 except OSError as err:
1214 except OSError as err:
1215 if err.errno == errno.EEXIST:
1215 if err.errno == errno.EEXIST:
1216 return
1216 return
1217 if err.errno != errno.ENOENT or not name:
1217 if err.errno != errno.ENOENT or not name:
1218 raise
1218 raise
1219 parent = os.path.dirname(os.path.abspath(name))
1219 parent = os.path.dirname(os.path.abspath(name))
1220 if parent == name:
1220 if parent == name:
1221 raise
1221 raise
1222 makedirs(parent, mode, notindexed)
1222 makedirs(parent, mode, notindexed)
1223 makedir(name, notindexed)
1223 makedir(name, notindexed)
1224 if mode is not None:
1224 if mode is not None:
1225 os.chmod(name, mode)
1225 os.chmod(name, mode)
1226
1226
1227 def ensuredirs(name, mode=None, notindexed=False):
1227 def ensuredirs(name, mode=None, notindexed=False):
1228 """race-safe recursive directory creation
1228 """race-safe recursive directory creation
1229
1229
1230 Newly created directories are marked as "not to be indexed by
1230 Newly created directories are marked as "not to be indexed by
1231 the content indexing service", if ``notindexed`` is specified
1231 the content indexing service", if ``notindexed`` is specified
1232 for "write" mode access.
1232 for "write" mode access.
1233 """
1233 """
1234 if os.path.isdir(name):
1234 if os.path.isdir(name):
1235 return
1235 return
1236 parent = os.path.dirname(os.path.abspath(name))
1236 parent = os.path.dirname(os.path.abspath(name))
1237 if parent != name:
1237 if parent != name:
1238 ensuredirs(parent, mode, notindexed)
1238 ensuredirs(parent, mode, notindexed)
1239 try:
1239 try:
1240 makedir(name, notindexed)
1240 makedir(name, notindexed)
1241 except OSError as err:
1241 except OSError as err:
1242 if err.errno == errno.EEXIST and os.path.isdir(name):
1242 if err.errno == errno.EEXIST and os.path.isdir(name):
1243 # someone else seems to have won a directory creation race
1243 # someone else seems to have won a directory creation race
1244 return
1244 return
1245 raise
1245 raise
1246 if mode is not None:
1246 if mode is not None:
1247 os.chmod(name, mode)
1247 os.chmod(name, mode)
1248
1248
1249 def readfile(path):
1249 def readfile(path):
1250 fp = open(path, 'rb')
1250 fp = open(path, 'rb')
1251 try:
1251 try:
1252 return fp.read()
1252 return fp.read()
1253 finally:
1253 finally:
1254 fp.close()
1254 fp.close()
1255
1255
1256 def writefile(path, text):
1256 def writefile(path, text):
1257 fp = open(path, 'wb')
1257 fp = open(path, 'wb')
1258 try:
1258 try:
1259 fp.write(text)
1259 fp.write(text)
1260 finally:
1260 finally:
1261 fp.close()
1261 fp.close()
1262
1262
1263 def appendfile(path, text):
1263 def appendfile(path, text):
1264 fp = open(path, 'ab')
1264 fp = open(path, 'ab')
1265 try:
1265 try:
1266 fp.write(text)
1266 fp.write(text)
1267 finally:
1267 finally:
1268 fp.close()
1268 fp.close()
1269
1269
1270 class chunkbuffer(object):
1270 class chunkbuffer(object):
1271 """Allow arbitrary sized chunks of data to be efficiently read from an
1271 """Allow arbitrary sized chunks of data to be efficiently read from an
1272 iterator over chunks of arbitrary size."""
1272 iterator over chunks of arbitrary size."""
1273
1273
1274 def __init__(self, in_iter):
1274 def __init__(self, in_iter):
1275 """in_iter is the iterator that's iterating over the input chunks.
1275 """in_iter is the iterator that's iterating over the input chunks.
1276 targetsize is how big a buffer to try to maintain."""
1276 targetsize is how big a buffer to try to maintain."""
1277 def splitbig(chunks):
1277 def splitbig(chunks):
1278 for chunk in chunks:
1278 for chunk in chunks:
1279 if len(chunk) > 2**20:
1279 if len(chunk) > 2**20:
1280 pos = 0
1280 pos = 0
1281 while pos < len(chunk):
1281 while pos < len(chunk):
1282 end = pos + 2 ** 18
1282 end = pos + 2 ** 18
1283 yield chunk[pos:end]
1283 yield chunk[pos:end]
1284 pos = end
1284 pos = end
1285 else:
1285 else:
1286 yield chunk
1286 yield chunk
1287 self.iter = splitbig(in_iter)
1287 self.iter = splitbig(in_iter)
1288 self._queue = collections.deque()
1288 self._queue = collections.deque()
1289 self._chunkoffset = 0
1289 self._chunkoffset = 0
1290
1290
1291 def read(self, l=None):
1291 def read(self, l=None):
1292 """Read L bytes of data from the iterator of chunks of data.
1292 """Read L bytes of data from the iterator of chunks of data.
1293 Returns less than L bytes if the iterator runs dry.
1293 Returns less than L bytes if the iterator runs dry.
1294
1294
1295 If size parameter is omitted, read everything"""
1295 If size parameter is omitted, read everything"""
1296 if l is None:
1296 if l is None:
1297 return ''.join(self.iter)
1297 return ''.join(self.iter)
1298
1298
1299 left = l
1299 left = l
1300 buf = []
1300 buf = []
1301 queue = self._queue
1301 queue = self._queue
1302 while left > 0:
1302 while left > 0:
1303 # refill the queue
1303 # refill the queue
1304 if not queue:
1304 if not queue:
1305 target = 2**18
1305 target = 2**18
1306 for chunk in self.iter:
1306 for chunk in self.iter:
1307 queue.append(chunk)
1307 queue.append(chunk)
1308 target -= len(chunk)
1308 target -= len(chunk)
1309 if target <= 0:
1309 if target <= 0:
1310 break
1310 break
1311 if not queue:
1311 if not queue:
1312 break
1312 break
1313
1313
1314 # The easy way to do this would be to queue.popleft(), modify the
1314 # The easy way to do this would be to queue.popleft(), modify the
1315 # chunk (if necessary), then queue.appendleft(). However, for cases
1315 # chunk (if necessary), then queue.appendleft(). However, for cases
1316 # where we read partial chunk content, this incurs 2 dequeue
1316 # where we read partial chunk content, this incurs 2 dequeue
1317 # mutations and creates a new str for the remaining chunk in the
1317 # mutations and creates a new str for the remaining chunk in the
1318 # queue. Our code below avoids this overhead.
1318 # queue. Our code below avoids this overhead.
1319
1319
1320 chunk = queue[0]
1320 chunk = queue[0]
1321 chunkl = len(chunk)
1321 chunkl = len(chunk)
1322 offset = self._chunkoffset
1322 offset = self._chunkoffset
1323
1323
1324 # Use full chunk.
1324 # Use full chunk.
1325 if offset == 0 and left >= chunkl:
1325 if offset == 0 and left >= chunkl:
1326 left -= chunkl
1326 left -= chunkl
1327 queue.popleft()
1327 queue.popleft()
1328 buf.append(chunk)
1328 buf.append(chunk)
1329 # self._chunkoffset remains at 0.
1329 # self._chunkoffset remains at 0.
1330 continue
1330 continue
1331
1331
1332 chunkremaining = chunkl - offset
1332 chunkremaining = chunkl - offset
1333
1333
1334 # Use all of unconsumed part of chunk.
1334 # Use all of unconsumed part of chunk.
1335 if left >= chunkremaining:
1335 if left >= chunkremaining:
1336 left -= chunkremaining
1336 left -= chunkremaining
1337 queue.popleft()
1337 queue.popleft()
1338 # offset == 0 is enabled by block above, so this won't merely
1338 # offset == 0 is enabled by block above, so this won't merely
1339 # copy via ``chunk[0:]``.
1339 # copy via ``chunk[0:]``.
1340 buf.append(chunk[offset:])
1340 buf.append(chunk[offset:])
1341 self._chunkoffset = 0
1341 self._chunkoffset = 0
1342
1342
1343 # Partial chunk needed.
1343 # Partial chunk needed.
1344 else:
1344 else:
1345 buf.append(chunk[offset:offset + left])
1345 buf.append(chunk[offset:offset + left])
1346 self._chunkoffset += left
1346 self._chunkoffset += left
1347 left -= chunkremaining
1347 left -= chunkremaining
1348
1348
1349 return ''.join(buf)
1349 return ''.join(buf)
1350
1350
1351 def filechunkiter(f, size=65536, limit=None):
1351 def filechunkiter(f, size=65536, limit=None):
1352 """Create a generator that produces the data in the file size
1352 """Create a generator that produces the data in the file size
1353 (default 65536) bytes at a time, up to optional limit (default is
1353 (default 65536) bytes at a time, up to optional limit (default is
1354 to read all data). Chunks may be less than size bytes if the
1354 to read all data). Chunks may be less than size bytes if the
1355 chunk is the last chunk in the file, or the file is a socket or
1355 chunk is the last chunk in the file, or the file is a socket or
1356 some other type of file that sometimes reads less data than is
1356 some other type of file that sometimes reads less data than is
1357 requested."""
1357 requested."""
1358 assert size >= 0
1358 assert size >= 0
1359 assert limit is None or limit >= 0
1359 assert limit is None or limit >= 0
1360 while True:
1360 while True:
1361 if limit is None:
1361 if limit is None:
1362 nbytes = size
1362 nbytes = size
1363 else:
1363 else:
1364 nbytes = min(limit, size)
1364 nbytes = min(limit, size)
1365 s = nbytes and f.read(nbytes)
1365 s = nbytes and f.read(nbytes)
1366 if not s:
1366 if not s:
1367 break
1367 break
1368 if limit:
1368 if limit:
1369 limit -= len(s)
1369 limit -= len(s)
1370 yield s
1370 yield s
1371
1371
1372 def makedate(timestamp=None):
1372 def makedate(timestamp=None):
1373 '''Return a unix timestamp (or the current time) as a (unixtime,
1373 '''Return a unix timestamp (or the current time) as a (unixtime,
1374 offset) tuple based off the local timezone.'''
1374 offset) tuple based off the local timezone.'''
1375 if timestamp is None:
1375 if timestamp is None:
1376 timestamp = time.time()
1376 timestamp = time.time()
1377 if timestamp < 0:
1377 if timestamp < 0:
1378 hint = _("check your clock")
1378 hint = _("check your clock")
1379 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1379 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1380 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1380 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1381 datetime.datetime.fromtimestamp(timestamp))
1381 datetime.datetime.fromtimestamp(timestamp))
1382 tz = delta.days * 86400 + delta.seconds
1382 tz = delta.days * 86400 + delta.seconds
1383 return timestamp, tz
1383 return timestamp, tz
1384
1384
1385 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1385 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1386 """represent a (unixtime, offset) tuple as a localized time.
1386 """represent a (unixtime, offset) tuple as a localized time.
1387 unixtime is seconds since the epoch, and offset is the time zone's
1387 unixtime is seconds since the epoch, and offset is the time zone's
1388 number of seconds away from UTC. if timezone is false, do not
1388 number of seconds away from UTC. if timezone is false, do not
1389 append time zone to string."""
1389 append time zone to string."""
1390 t, tz = date or makedate()
1390 t, tz = date or makedate()
1391 if t < 0:
1391 if t < 0:
1392 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1392 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1393 tz = 0
1393 tz = 0
1394 if "%1" in format or "%2" in format or "%z" in format:
1394 if "%1" in format or "%2" in format or "%z" in format:
1395 sign = (tz > 0) and "-" or "+"
1395 sign = (tz > 0) and "-" or "+"
1396 minutes = abs(tz) // 60
1396 minutes = abs(tz) // 60
1397 format = format.replace("%z", "%1%2")
1397 format = format.replace("%z", "%1%2")
1398 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1398 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1399 format = format.replace("%2", "%02d" % (minutes % 60))
1399 format = format.replace("%2", "%02d" % (minutes % 60))
1400 try:
1400 try:
1401 t = time.gmtime(float(t) - tz)
1401 t = time.gmtime(float(t) - tz)
1402 except ValueError:
1402 except ValueError:
1403 # time was out of range
1403 # time was out of range
1404 t = time.gmtime(sys.maxint)
1404 t = time.gmtime(sys.maxint)
1405 s = time.strftime(format, t)
1405 s = time.strftime(format, t)
1406 return s
1406 return s
1407
1407
1408 def shortdate(date=None):
1408 def shortdate(date=None):
1409 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1409 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1410 return datestr(date, format='%Y-%m-%d')
1410 return datestr(date, format='%Y-%m-%d')
1411
1411
1412 def parsetimezone(tz):
1412 def parsetimezone(tz):
1413 """parse a timezone string and return an offset integer"""
1413 """parse a timezone string and return an offset integer"""
1414 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1414 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1415 sign = (tz[0] == "+") and 1 or -1
1415 sign = (tz[0] == "+") and 1 or -1
1416 hours = int(tz[1:3])
1416 hours = int(tz[1:3])
1417 minutes = int(tz[3:5])
1417 minutes = int(tz[3:5])
1418 return -sign * (hours * 60 + minutes) * 60
1418 return -sign * (hours * 60 + minutes) * 60
1419 if tz == "GMT" or tz == "UTC":
1419 if tz == "GMT" or tz == "UTC":
1420 return 0
1420 return 0
1421 return None
1421 return None
1422
1422
1423 def strdate(string, format, defaults=[]):
1423 def strdate(string, format, defaults=[]):
1424 """parse a localized time string and return a (unixtime, offset) tuple.
1424 """parse a localized time string and return a (unixtime, offset) tuple.
1425 if the string cannot be parsed, ValueError is raised."""
1425 if the string cannot be parsed, ValueError is raised."""
1426 # NOTE: unixtime = localunixtime + offset
1426 # NOTE: unixtime = localunixtime + offset
1427 offset, date = parsetimezone(string.split()[-1]), string
1427 offset, date = parsetimezone(string.split()[-1]), string
1428 if offset is not None:
1428 if offset is not None:
1429 date = " ".join(string.split()[:-1])
1429 date = " ".join(string.split()[:-1])
1430
1430
1431 # add missing elements from defaults
1431 # add missing elements from defaults
1432 usenow = False # default to using biased defaults
1432 usenow = False # default to using biased defaults
1433 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1433 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1434 found = [True for p in part if ("%"+p) in format]
1434 found = [True for p in part if ("%"+p) in format]
1435 if not found:
1435 if not found:
1436 date += "@" + defaults[part][usenow]
1436 date += "@" + defaults[part][usenow]
1437 format += "@%" + part[0]
1437 format += "@%" + part[0]
1438 else:
1438 else:
1439 # We've found a specific time element, less specific time
1439 # We've found a specific time element, less specific time
1440 # elements are relative to today
1440 # elements are relative to today
1441 usenow = True
1441 usenow = True
1442
1442
1443 timetuple = time.strptime(date, format)
1443 timetuple = time.strptime(date, format)
1444 localunixtime = int(calendar.timegm(timetuple))
1444 localunixtime = int(calendar.timegm(timetuple))
1445 if offset is None:
1445 if offset is None:
1446 # local timezone
1446 # local timezone
1447 unixtime = int(time.mktime(timetuple))
1447 unixtime = int(time.mktime(timetuple))
1448 offset = unixtime - localunixtime
1448 offset = unixtime - localunixtime
1449 else:
1449 else:
1450 unixtime = localunixtime + offset
1450 unixtime = localunixtime + offset
1451 return unixtime, offset
1451 return unixtime, offset
1452
1452
1453 def parsedate(date, formats=None, bias=None):
1453 def parsedate(date, formats=None, bias=None):
1454 """parse a localized date/time and return a (unixtime, offset) tuple.
1454 """parse a localized date/time and return a (unixtime, offset) tuple.
1455
1455
1456 The date may be a "unixtime offset" string or in one of the specified
1456 The date may be a "unixtime offset" string or in one of the specified
1457 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1457 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1458
1458
1459 >>> parsedate(' today ') == parsedate(\
1459 >>> parsedate(' today ') == parsedate(\
1460 datetime.date.today().strftime('%b %d'))
1460 datetime.date.today().strftime('%b %d'))
1461 True
1461 True
1462 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1462 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1463 datetime.timedelta(days=1)\
1463 datetime.timedelta(days=1)\
1464 ).strftime('%b %d'))
1464 ).strftime('%b %d'))
1465 True
1465 True
1466 >>> now, tz = makedate()
1466 >>> now, tz = makedate()
1467 >>> strnow, strtz = parsedate('now')
1467 >>> strnow, strtz = parsedate('now')
1468 >>> (strnow - now) < 1
1468 >>> (strnow - now) < 1
1469 True
1469 True
1470 >>> tz == strtz
1470 >>> tz == strtz
1471 True
1471 True
1472 """
1472 """
1473 if bias is None:
1473 if bias is None:
1474 bias = {}
1474 bias = {}
1475 if not date:
1475 if not date:
1476 return 0, 0
1476 return 0, 0
1477 if isinstance(date, tuple) and len(date) == 2:
1477 if isinstance(date, tuple) and len(date) == 2:
1478 return date
1478 return date
1479 if not formats:
1479 if not formats:
1480 formats = defaultdateformats
1480 formats = defaultdateformats
1481 date = date.strip()
1481 date = date.strip()
1482
1482
1483 if date == 'now' or date == _('now'):
1483 if date == 'now' or date == _('now'):
1484 return makedate()
1484 return makedate()
1485 if date == 'today' or date == _('today'):
1485 if date == 'today' or date == _('today'):
1486 date = datetime.date.today().strftime('%b %d')
1486 date = datetime.date.today().strftime('%b %d')
1487 elif date == 'yesterday' or date == _('yesterday'):
1487 elif date == 'yesterday' or date == _('yesterday'):
1488 date = (datetime.date.today() -
1488 date = (datetime.date.today() -
1489 datetime.timedelta(days=1)).strftime('%b %d')
1489 datetime.timedelta(days=1)).strftime('%b %d')
1490
1490
1491 try:
1491 try:
1492 when, offset = map(int, date.split(' '))
1492 when, offset = map(int, date.split(' '))
1493 except ValueError:
1493 except ValueError:
1494 # fill out defaults
1494 # fill out defaults
1495 now = makedate()
1495 now = makedate()
1496 defaults = {}
1496 defaults = {}
1497 for part in ("d", "mb", "yY", "HI", "M", "S"):
1497 for part in ("d", "mb", "yY", "HI", "M", "S"):
1498 # this piece is for rounding the specific end of unknowns
1498 # this piece is for rounding the specific end of unknowns
1499 b = bias.get(part)
1499 b = bias.get(part)
1500 if b is None:
1500 if b is None:
1501 if part[0] in "HMS":
1501 if part[0] in "HMS":
1502 b = "00"
1502 b = "00"
1503 else:
1503 else:
1504 b = "0"
1504 b = "0"
1505
1505
1506 # this piece is for matching the generic end to today's date
1506 # this piece is for matching the generic end to today's date
1507 n = datestr(now, "%" + part[0])
1507 n = datestr(now, "%" + part[0])
1508
1508
1509 defaults[part] = (b, n)
1509 defaults[part] = (b, n)
1510
1510
1511 for format in formats:
1511 for format in formats:
1512 try:
1512 try:
1513 when, offset = strdate(date, format, defaults)
1513 when, offset = strdate(date, format, defaults)
1514 except (ValueError, OverflowError):
1514 except (ValueError, OverflowError):
1515 pass
1515 pass
1516 else:
1516 else:
1517 break
1517 break
1518 else:
1518 else:
1519 raise Abort(_('invalid date: %r') % date)
1519 raise Abort(_('invalid date: %r') % date)
1520 # validate explicit (probably user-specified) date and
1520 # validate explicit (probably user-specified) date and
1521 # time zone offset. values must fit in signed 32 bits for
1521 # time zone offset. values must fit in signed 32 bits for
1522 # current 32-bit linux runtimes. timezones go from UTC-12
1522 # current 32-bit linux runtimes. timezones go from UTC-12
1523 # to UTC+14
1523 # to UTC+14
1524 if abs(when) > 0x7fffffff:
1524 if abs(when) > 0x7fffffff:
1525 raise Abort(_('date exceeds 32 bits: %d') % when)
1525 raise Abort(_('date exceeds 32 bits: %d') % when)
1526 if when < 0:
1526 if when < 0:
1527 raise Abort(_('negative date value: %d') % when)
1527 raise Abort(_('negative date value: %d') % when)
1528 if offset < -50400 or offset > 43200:
1528 if offset < -50400 or offset > 43200:
1529 raise Abort(_('impossible time zone offset: %d') % offset)
1529 raise Abort(_('impossible time zone offset: %d') % offset)
1530 return when, offset
1530 return when, offset
1531
1531
1532 def matchdate(date):
1532 def matchdate(date):
1533 """Return a function that matches a given date match specifier
1533 """Return a function that matches a given date match specifier
1534
1534
1535 Formats include:
1535 Formats include:
1536
1536
1537 '{date}' match a given date to the accuracy provided
1537 '{date}' match a given date to the accuracy provided
1538
1538
1539 '<{date}' on or before a given date
1539 '<{date}' on or before a given date
1540
1540
1541 '>{date}' on or after a given date
1541 '>{date}' on or after a given date
1542
1542
1543 >>> p1 = parsedate("10:29:59")
1543 >>> p1 = parsedate("10:29:59")
1544 >>> p2 = parsedate("10:30:00")
1544 >>> p2 = parsedate("10:30:00")
1545 >>> p3 = parsedate("10:30:59")
1545 >>> p3 = parsedate("10:30:59")
1546 >>> p4 = parsedate("10:31:00")
1546 >>> p4 = parsedate("10:31:00")
1547 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1547 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1548 >>> f = matchdate("10:30")
1548 >>> f = matchdate("10:30")
1549 >>> f(p1[0])
1549 >>> f(p1[0])
1550 False
1550 False
1551 >>> f(p2[0])
1551 >>> f(p2[0])
1552 True
1552 True
1553 >>> f(p3[0])
1553 >>> f(p3[0])
1554 True
1554 True
1555 >>> f(p4[0])
1555 >>> f(p4[0])
1556 False
1556 False
1557 >>> f(p5[0])
1557 >>> f(p5[0])
1558 False
1558 False
1559 """
1559 """
1560
1560
1561 def lower(date):
1561 def lower(date):
1562 d = {'mb': "1", 'd': "1"}
1562 d = {'mb': "1", 'd': "1"}
1563 return parsedate(date, extendeddateformats, d)[0]
1563 return parsedate(date, extendeddateformats, d)[0]
1564
1564
1565 def upper(date):
1565 def upper(date):
1566 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1566 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1567 for days in ("31", "30", "29"):
1567 for days in ("31", "30", "29"):
1568 try:
1568 try:
1569 d["d"] = days
1569 d["d"] = days
1570 return parsedate(date, extendeddateformats, d)[0]
1570 return parsedate(date, extendeddateformats, d)[0]
1571 except Abort:
1571 except Abort:
1572 pass
1572 pass
1573 d["d"] = "28"
1573 d["d"] = "28"
1574 return parsedate(date, extendeddateformats, d)[0]
1574 return parsedate(date, extendeddateformats, d)[0]
1575
1575
1576 date = date.strip()
1576 date = date.strip()
1577
1577
1578 if not date:
1578 if not date:
1579 raise Abort(_("dates cannot consist entirely of whitespace"))
1579 raise Abort(_("dates cannot consist entirely of whitespace"))
1580 elif date[0] == "<":
1580 elif date[0] == "<":
1581 if not date[1:]:
1581 if not date[1:]:
1582 raise Abort(_("invalid day spec, use '<DATE'"))
1582 raise Abort(_("invalid day spec, use '<DATE'"))
1583 when = upper(date[1:])
1583 when = upper(date[1:])
1584 return lambda x: x <= when
1584 return lambda x: x <= when
1585 elif date[0] == ">":
1585 elif date[0] == ">":
1586 if not date[1:]:
1586 if not date[1:]:
1587 raise Abort(_("invalid day spec, use '>DATE'"))
1587 raise Abort(_("invalid day spec, use '>DATE'"))
1588 when = lower(date[1:])
1588 when = lower(date[1:])
1589 return lambda x: x >= when
1589 return lambda x: x >= when
1590 elif date[0] == "-":
1590 elif date[0] == "-":
1591 try:
1591 try:
1592 days = int(date[1:])
1592 days = int(date[1:])
1593 except ValueError:
1593 except ValueError:
1594 raise Abort(_("invalid day spec: %s") % date[1:])
1594 raise Abort(_("invalid day spec: %s") % date[1:])
1595 if days < 0:
1595 if days < 0:
1596 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1596 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1597 % date[1:])
1597 % date[1:])
1598 when = makedate()[0] - days * 3600 * 24
1598 when = makedate()[0] - days * 3600 * 24
1599 return lambda x: x >= when
1599 return lambda x: x >= when
1600 elif " to " in date:
1600 elif " to " in date:
1601 a, b = date.split(" to ")
1601 a, b = date.split(" to ")
1602 start, stop = lower(a), upper(b)
1602 start, stop = lower(a), upper(b)
1603 return lambda x: x >= start and x <= stop
1603 return lambda x: x >= start and x <= stop
1604 else:
1604 else:
1605 start, stop = lower(date), upper(date)
1605 start, stop = lower(date), upper(date)
1606 return lambda x: x >= start and x <= stop
1606 return lambda x: x >= start and x <= stop
1607
1607
1608 def stringmatcher(pattern):
1609 """
1610 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1611 returns the matcher name, pattern, and matcher function.
1612 missing or unknown prefixes are treated as literal matches.
1613
1614 helper for tests:
1615 >>> def test(pattern, *tests):
1616 ... kind, pattern, matcher = stringmatcher(pattern)
1617 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1618
1619 exact matching (no prefix):
1620 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1621 ('literal', 'abcdefg', [False, False, True])
1622
1623 regex matching ('re:' prefix)
1624 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1625 ('re', 'a.+b', [False, False, True])
1626
1627 force exact matches ('literal:' prefix)
1628 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1629 ('literal', 're:foobar', [False, True])
1630
1631 unknown prefixes are ignored and treated as literals
1632 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1633 ('literal', 'foo:bar', [False, False, True])
1634 """
1635 if pattern.startswith('re:'):
1636 pattern = pattern[3:]
1637 try:
1638 regex = remod.compile(pattern)
1639 except remod.error as e:
1640 raise error.ParseError(_('invalid regular expression: %s')
1641 % e)
1642 return 're', pattern, regex.search
1643 elif pattern.startswith('literal:'):
1644 pattern = pattern[8:]
1645 return 'literal', pattern, pattern.__eq__
1646
1608 def shortuser(user):
1647 def shortuser(user):
1609 """Return a short representation of a user name or email address."""
1648 """Return a short representation of a user name or email address."""
1610 f = user.find('@')
1649 f = user.find('@')
1611 if f >= 0:
1650 if f >= 0:
1612 user = user[:f]
1651 user = user[:f]
1613 f = user.find('<')
1652 f = user.find('<')
1614 if f >= 0:
1653 if f >= 0:
1615 user = user[f + 1:]
1654 user = user[f + 1:]
1616 f = user.find(' ')
1655 f = user.find(' ')
1617 if f >= 0:
1656 if f >= 0:
1618 user = user[:f]
1657 user = user[:f]
1619 f = user.find('.')
1658 f = user.find('.')
1620 if f >= 0:
1659 if f >= 0:
1621 user = user[:f]
1660 user = user[:f]
1622 return user
1661 return user
1623
1662
1624 def emailuser(user):
1663 def emailuser(user):
1625 """Return the user portion of an email address."""
1664 """Return the user portion of an email address."""
1626 f = user.find('@')
1665 f = user.find('@')
1627 if f >= 0:
1666 if f >= 0:
1628 user = user[:f]
1667 user = user[:f]
1629 f = user.find('<')
1668 f = user.find('<')
1630 if f >= 0:
1669 if f >= 0:
1631 user = user[f + 1:]
1670 user = user[f + 1:]
1632 return user
1671 return user
1633
1672
1634 def email(author):
1673 def email(author):
1635 '''get email of author.'''
1674 '''get email of author.'''
1636 r = author.find('>')
1675 r = author.find('>')
1637 if r == -1:
1676 if r == -1:
1638 r = None
1677 r = None
1639 return author[author.find('<') + 1:r]
1678 return author[author.find('<') + 1:r]
1640
1679
1641 def ellipsis(text, maxlength=400):
1680 def ellipsis(text, maxlength=400):
1642 """Trim string to at most maxlength (default: 400) columns in display."""
1681 """Trim string to at most maxlength (default: 400) columns in display."""
1643 return encoding.trim(text, maxlength, ellipsis='...')
1682 return encoding.trim(text, maxlength, ellipsis='...')
1644
1683
1645 def unitcountfn(*unittable):
1684 def unitcountfn(*unittable):
1646 '''return a function that renders a readable count of some quantity'''
1685 '''return a function that renders a readable count of some quantity'''
1647
1686
1648 def go(count):
1687 def go(count):
1649 for multiplier, divisor, format in unittable:
1688 for multiplier, divisor, format in unittable:
1650 if count >= divisor * multiplier:
1689 if count >= divisor * multiplier:
1651 return format % (count / float(divisor))
1690 return format % (count / float(divisor))
1652 return unittable[-1][2] % count
1691 return unittable[-1][2] % count
1653
1692
1654 return go
1693 return go
1655
1694
1656 bytecount = unitcountfn(
1695 bytecount = unitcountfn(
1657 (100, 1 << 30, _('%.0f GB')),
1696 (100, 1 << 30, _('%.0f GB')),
1658 (10, 1 << 30, _('%.1f GB')),
1697 (10, 1 << 30, _('%.1f GB')),
1659 (1, 1 << 30, _('%.2f GB')),
1698 (1, 1 << 30, _('%.2f GB')),
1660 (100, 1 << 20, _('%.0f MB')),
1699 (100, 1 << 20, _('%.0f MB')),
1661 (10, 1 << 20, _('%.1f MB')),
1700 (10, 1 << 20, _('%.1f MB')),
1662 (1, 1 << 20, _('%.2f MB')),
1701 (1, 1 << 20, _('%.2f MB')),
1663 (100, 1 << 10, _('%.0f KB')),
1702 (100, 1 << 10, _('%.0f KB')),
1664 (10, 1 << 10, _('%.1f KB')),
1703 (10, 1 << 10, _('%.1f KB')),
1665 (1, 1 << 10, _('%.2f KB')),
1704 (1, 1 << 10, _('%.2f KB')),
1666 (1, 1, _('%.0f bytes')),
1705 (1, 1, _('%.0f bytes')),
1667 )
1706 )
1668
1707
1669 def uirepr(s):
1708 def uirepr(s):
1670 # Avoid double backslash in Windows path repr()
1709 # Avoid double backslash in Windows path repr()
1671 return repr(s).replace('\\\\', '\\')
1710 return repr(s).replace('\\\\', '\\')
1672
1711
1673 # delay import of textwrap
1712 # delay import of textwrap
1674 def MBTextWrapper(**kwargs):
1713 def MBTextWrapper(**kwargs):
1675 class tw(textwrap.TextWrapper):
1714 class tw(textwrap.TextWrapper):
1676 """
1715 """
1677 Extend TextWrapper for width-awareness.
1716 Extend TextWrapper for width-awareness.
1678
1717
1679 Neither number of 'bytes' in any encoding nor 'characters' is
1718 Neither number of 'bytes' in any encoding nor 'characters' is
1680 appropriate to calculate terminal columns for specified string.
1719 appropriate to calculate terminal columns for specified string.
1681
1720
1682 Original TextWrapper implementation uses built-in 'len()' directly,
1721 Original TextWrapper implementation uses built-in 'len()' directly,
1683 so overriding is needed to use width information of each characters.
1722 so overriding is needed to use width information of each characters.
1684
1723
1685 In addition, characters classified into 'ambiguous' width are
1724 In addition, characters classified into 'ambiguous' width are
1686 treated as wide in East Asian area, but as narrow in other.
1725 treated as wide in East Asian area, but as narrow in other.
1687
1726
1688 This requires use decision to determine width of such characters.
1727 This requires use decision to determine width of such characters.
1689 """
1728 """
1690 def _cutdown(self, ucstr, space_left):
1729 def _cutdown(self, ucstr, space_left):
1691 l = 0
1730 l = 0
1692 colwidth = encoding.ucolwidth
1731 colwidth = encoding.ucolwidth
1693 for i in xrange(len(ucstr)):
1732 for i in xrange(len(ucstr)):
1694 l += colwidth(ucstr[i])
1733 l += colwidth(ucstr[i])
1695 if space_left < l:
1734 if space_left < l:
1696 return (ucstr[:i], ucstr[i:])
1735 return (ucstr[:i], ucstr[i:])
1697 return ucstr, ''
1736 return ucstr, ''
1698
1737
1699 # overriding of base class
1738 # overriding of base class
1700 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1739 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1701 space_left = max(width - cur_len, 1)
1740 space_left = max(width - cur_len, 1)
1702
1741
1703 if self.break_long_words:
1742 if self.break_long_words:
1704 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1743 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1705 cur_line.append(cut)
1744 cur_line.append(cut)
1706 reversed_chunks[-1] = res
1745 reversed_chunks[-1] = res
1707 elif not cur_line:
1746 elif not cur_line:
1708 cur_line.append(reversed_chunks.pop())
1747 cur_line.append(reversed_chunks.pop())
1709
1748
1710 # this overriding code is imported from TextWrapper of Python 2.6
1749 # this overriding code is imported from TextWrapper of Python 2.6
1711 # to calculate columns of string by 'encoding.ucolwidth()'
1750 # to calculate columns of string by 'encoding.ucolwidth()'
1712 def _wrap_chunks(self, chunks):
1751 def _wrap_chunks(self, chunks):
1713 colwidth = encoding.ucolwidth
1752 colwidth = encoding.ucolwidth
1714
1753
1715 lines = []
1754 lines = []
1716 if self.width <= 0:
1755 if self.width <= 0:
1717 raise ValueError("invalid width %r (must be > 0)" % self.width)
1756 raise ValueError("invalid width %r (must be > 0)" % self.width)
1718
1757
1719 # Arrange in reverse order so items can be efficiently popped
1758 # Arrange in reverse order so items can be efficiently popped
1720 # from a stack of chucks.
1759 # from a stack of chucks.
1721 chunks.reverse()
1760 chunks.reverse()
1722
1761
1723 while chunks:
1762 while chunks:
1724
1763
1725 # Start the list of chunks that will make up the current line.
1764 # Start the list of chunks that will make up the current line.
1726 # cur_len is just the length of all the chunks in cur_line.
1765 # cur_len is just the length of all the chunks in cur_line.
1727 cur_line = []
1766 cur_line = []
1728 cur_len = 0
1767 cur_len = 0
1729
1768
1730 # Figure out which static string will prefix this line.
1769 # Figure out which static string will prefix this line.
1731 if lines:
1770 if lines:
1732 indent = self.subsequent_indent
1771 indent = self.subsequent_indent
1733 else:
1772 else:
1734 indent = self.initial_indent
1773 indent = self.initial_indent
1735
1774
1736 # Maximum width for this line.
1775 # Maximum width for this line.
1737 width = self.width - len(indent)
1776 width = self.width - len(indent)
1738
1777
1739 # First chunk on line is whitespace -- drop it, unless this
1778 # First chunk on line is whitespace -- drop it, unless this
1740 # is the very beginning of the text (i.e. no lines started yet).
1779 # is the very beginning of the text (i.e. no lines started yet).
1741 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1780 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1742 del chunks[-1]
1781 del chunks[-1]
1743
1782
1744 while chunks:
1783 while chunks:
1745 l = colwidth(chunks[-1])
1784 l = colwidth(chunks[-1])
1746
1785
1747 # Can at least squeeze this chunk onto the current line.
1786 # Can at least squeeze this chunk onto the current line.
1748 if cur_len + l <= width:
1787 if cur_len + l <= width:
1749 cur_line.append(chunks.pop())
1788 cur_line.append(chunks.pop())
1750 cur_len += l
1789 cur_len += l
1751
1790
1752 # Nope, this line is full.
1791 # Nope, this line is full.
1753 else:
1792 else:
1754 break
1793 break
1755
1794
1756 # The current line is full, and the next chunk is too big to
1795 # The current line is full, and the next chunk is too big to
1757 # fit on *any* line (not just this one).
1796 # fit on *any* line (not just this one).
1758 if chunks and colwidth(chunks[-1]) > width:
1797 if chunks and colwidth(chunks[-1]) > width:
1759 self._handle_long_word(chunks, cur_line, cur_len, width)
1798 self._handle_long_word(chunks, cur_line, cur_len, width)
1760
1799
1761 # If the last chunk on this line is all whitespace, drop it.
1800 # If the last chunk on this line is all whitespace, drop it.
1762 if (self.drop_whitespace and
1801 if (self.drop_whitespace and
1763 cur_line and cur_line[-1].strip() == ''):
1802 cur_line and cur_line[-1].strip() == ''):
1764 del cur_line[-1]
1803 del cur_line[-1]
1765
1804
1766 # Convert current line back to a string and store it in list
1805 # Convert current line back to a string and store it in list
1767 # of all lines (return value).
1806 # of all lines (return value).
1768 if cur_line:
1807 if cur_line:
1769 lines.append(indent + ''.join(cur_line))
1808 lines.append(indent + ''.join(cur_line))
1770
1809
1771 return lines
1810 return lines
1772
1811
1773 global MBTextWrapper
1812 global MBTextWrapper
1774 MBTextWrapper = tw
1813 MBTextWrapper = tw
1775 return tw(**kwargs)
1814 return tw(**kwargs)
1776
1815
1777 def wrap(line, width, initindent='', hangindent=''):
1816 def wrap(line, width, initindent='', hangindent=''):
1778 maxindent = max(len(hangindent), len(initindent))
1817 maxindent = max(len(hangindent), len(initindent))
1779 if width <= maxindent:
1818 if width <= maxindent:
1780 # adjust for weird terminal size
1819 # adjust for weird terminal size
1781 width = max(78, maxindent + 1)
1820 width = max(78, maxindent + 1)
1782 line = line.decode(encoding.encoding, encoding.encodingmode)
1821 line = line.decode(encoding.encoding, encoding.encodingmode)
1783 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1822 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1784 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1823 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1785 wrapper = MBTextWrapper(width=width,
1824 wrapper = MBTextWrapper(width=width,
1786 initial_indent=initindent,
1825 initial_indent=initindent,
1787 subsequent_indent=hangindent)
1826 subsequent_indent=hangindent)
1788 return wrapper.fill(line).encode(encoding.encoding)
1827 return wrapper.fill(line).encode(encoding.encoding)
1789
1828
1790 def iterlines(iterator):
1829 def iterlines(iterator):
1791 for chunk in iterator:
1830 for chunk in iterator:
1792 for line in chunk.splitlines():
1831 for line in chunk.splitlines():
1793 yield line
1832 yield line
1794
1833
1795 def expandpath(path):
1834 def expandpath(path):
1796 return os.path.expanduser(os.path.expandvars(path))
1835 return os.path.expanduser(os.path.expandvars(path))
1797
1836
1798 def hgcmd():
1837 def hgcmd():
1799 """Return the command used to execute current hg
1838 """Return the command used to execute current hg
1800
1839
1801 This is different from hgexecutable() because on Windows we want
1840 This is different from hgexecutable() because on Windows we want
1802 to avoid things opening new shell windows like batch files, so we
1841 to avoid things opening new shell windows like batch files, so we
1803 get either the python call or current executable.
1842 get either the python call or current executable.
1804 """
1843 """
1805 if mainfrozen():
1844 if mainfrozen():
1806 return [sys.executable]
1845 return [sys.executable]
1807 return gethgcmd()
1846 return gethgcmd()
1808
1847
1809 def rundetached(args, condfn):
1848 def rundetached(args, condfn):
1810 """Execute the argument list in a detached process.
1849 """Execute the argument list in a detached process.
1811
1850
1812 condfn is a callable which is called repeatedly and should return
1851 condfn is a callable which is called repeatedly and should return
1813 True once the child process is known to have started successfully.
1852 True once the child process is known to have started successfully.
1814 At this point, the child process PID is returned. If the child
1853 At this point, the child process PID is returned. If the child
1815 process fails to start or finishes before condfn() evaluates to
1854 process fails to start or finishes before condfn() evaluates to
1816 True, return -1.
1855 True, return -1.
1817 """
1856 """
1818 # Windows case is easier because the child process is either
1857 # Windows case is easier because the child process is either
1819 # successfully starting and validating the condition or exiting
1858 # successfully starting and validating the condition or exiting
1820 # on failure. We just poll on its PID. On Unix, if the child
1859 # on failure. We just poll on its PID. On Unix, if the child
1821 # process fails to start, it will be left in a zombie state until
1860 # process fails to start, it will be left in a zombie state until
1822 # the parent wait on it, which we cannot do since we expect a long
1861 # the parent wait on it, which we cannot do since we expect a long
1823 # running process on success. Instead we listen for SIGCHLD telling
1862 # running process on success. Instead we listen for SIGCHLD telling
1824 # us our child process terminated.
1863 # us our child process terminated.
1825 terminated = set()
1864 terminated = set()
1826 def handler(signum, frame):
1865 def handler(signum, frame):
1827 terminated.add(os.wait())
1866 terminated.add(os.wait())
1828 prevhandler = None
1867 prevhandler = None
1829 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1868 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1830 if SIGCHLD is not None:
1869 if SIGCHLD is not None:
1831 prevhandler = signal.signal(SIGCHLD, handler)
1870 prevhandler = signal.signal(SIGCHLD, handler)
1832 try:
1871 try:
1833 pid = spawndetached(args)
1872 pid = spawndetached(args)
1834 while not condfn():
1873 while not condfn():
1835 if ((pid in terminated or not testpid(pid))
1874 if ((pid in terminated or not testpid(pid))
1836 and not condfn()):
1875 and not condfn()):
1837 return -1
1876 return -1
1838 time.sleep(0.1)
1877 time.sleep(0.1)
1839 return pid
1878 return pid
1840 finally:
1879 finally:
1841 if prevhandler is not None:
1880 if prevhandler is not None:
1842 signal.signal(signal.SIGCHLD, prevhandler)
1881 signal.signal(signal.SIGCHLD, prevhandler)
1843
1882
1844 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1883 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1845 """Return the result of interpolating items in the mapping into string s.
1884 """Return the result of interpolating items in the mapping into string s.
1846
1885
1847 prefix is a single character string, or a two character string with
1886 prefix is a single character string, or a two character string with
1848 a backslash as the first character if the prefix needs to be escaped in
1887 a backslash as the first character if the prefix needs to be escaped in
1849 a regular expression.
1888 a regular expression.
1850
1889
1851 fn is an optional function that will be applied to the replacement text
1890 fn is an optional function that will be applied to the replacement text
1852 just before replacement.
1891 just before replacement.
1853
1892
1854 escape_prefix is an optional flag that allows using doubled prefix for
1893 escape_prefix is an optional flag that allows using doubled prefix for
1855 its escaping.
1894 its escaping.
1856 """
1895 """
1857 fn = fn or (lambda s: s)
1896 fn = fn or (lambda s: s)
1858 patterns = '|'.join(mapping.keys())
1897 patterns = '|'.join(mapping.keys())
1859 if escape_prefix:
1898 if escape_prefix:
1860 patterns += '|' + prefix
1899 patterns += '|' + prefix
1861 if len(prefix) > 1:
1900 if len(prefix) > 1:
1862 prefix_char = prefix[1:]
1901 prefix_char = prefix[1:]
1863 else:
1902 else:
1864 prefix_char = prefix
1903 prefix_char = prefix
1865 mapping[prefix_char] = prefix_char
1904 mapping[prefix_char] = prefix_char
1866 r = remod.compile(r'%s(%s)' % (prefix, patterns))
1905 r = remod.compile(r'%s(%s)' % (prefix, patterns))
1867 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1906 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1868
1907
1869 def getport(port):
1908 def getport(port):
1870 """Return the port for a given network service.
1909 """Return the port for a given network service.
1871
1910
1872 If port is an integer, it's returned as is. If it's a string, it's
1911 If port is an integer, it's returned as is. If it's a string, it's
1873 looked up using socket.getservbyname(). If there's no matching
1912 looked up using socket.getservbyname(). If there's no matching
1874 service, util.Abort is raised.
1913 service, util.Abort is raised.
1875 """
1914 """
1876 try:
1915 try:
1877 return int(port)
1916 return int(port)
1878 except ValueError:
1917 except ValueError:
1879 pass
1918 pass
1880
1919
1881 try:
1920 try:
1882 return socket.getservbyname(port)
1921 return socket.getservbyname(port)
1883 except socket.error:
1922 except socket.error:
1884 raise Abort(_("no port number associated with service '%s'") % port)
1923 raise Abort(_("no port number associated with service '%s'") % port)
1885
1924
1886 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1925 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1887 '0': False, 'no': False, 'false': False, 'off': False,
1926 '0': False, 'no': False, 'false': False, 'off': False,
1888 'never': False}
1927 'never': False}
1889
1928
1890 def parsebool(s):
1929 def parsebool(s):
1891 """Parse s into a boolean.
1930 """Parse s into a boolean.
1892
1931
1893 If s is not a valid boolean, returns None.
1932 If s is not a valid boolean, returns None.
1894 """
1933 """
1895 return _booleans.get(s.lower(), None)
1934 return _booleans.get(s.lower(), None)
1896
1935
1897 _hexdig = '0123456789ABCDEFabcdef'
1936 _hexdig = '0123456789ABCDEFabcdef'
1898 _hextochr = dict((a + b, chr(int(a + b, 16)))
1937 _hextochr = dict((a + b, chr(int(a + b, 16)))
1899 for a in _hexdig for b in _hexdig)
1938 for a in _hexdig for b in _hexdig)
1900
1939
1901 def _urlunquote(s):
1940 def _urlunquote(s):
1902 """Decode HTTP/HTML % encoding.
1941 """Decode HTTP/HTML % encoding.
1903
1942
1904 >>> _urlunquote('abc%20def')
1943 >>> _urlunquote('abc%20def')
1905 'abc def'
1944 'abc def'
1906 """
1945 """
1907 res = s.split('%')
1946 res = s.split('%')
1908 # fastpath
1947 # fastpath
1909 if len(res) == 1:
1948 if len(res) == 1:
1910 return s
1949 return s
1911 s = res[0]
1950 s = res[0]
1912 for item in res[1:]:
1951 for item in res[1:]:
1913 try:
1952 try:
1914 s += _hextochr[item[:2]] + item[2:]
1953 s += _hextochr[item[:2]] + item[2:]
1915 except KeyError:
1954 except KeyError:
1916 s += '%' + item
1955 s += '%' + item
1917 except UnicodeDecodeError:
1956 except UnicodeDecodeError:
1918 s += unichr(int(item[:2], 16)) + item[2:]
1957 s += unichr(int(item[:2], 16)) + item[2:]
1919 return s
1958 return s
1920
1959
1921 class url(object):
1960 class url(object):
1922 r"""Reliable URL parser.
1961 r"""Reliable URL parser.
1923
1962
1924 This parses URLs and provides attributes for the following
1963 This parses URLs and provides attributes for the following
1925 components:
1964 components:
1926
1965
1927 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1966 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1928
1967
1929 Missing components are set to None. The only exception is
1968 Missing components are set to None. The only exception is
1930 fragment, which is set to '' if present but empty.
1969 fragment, which is set to '' if present but empty.
1931
1970
1932 If parsefragment is False, fragment is included in query. If
1971 If parsefragment is False, fragment is included in query. If
1933 parsequery is False, query is included in path. If both are
1972 parsequery is False, query is included in path. If both are
1934 False, both fragment and query are included in path.
1973 False, both fragment and query are included in path.
1935
1974
1936 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1975 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1937
1976
1938 Note that for backward compatibility reasons, bundle URLs do not
1977 Note that for backward compatibility reasons, bundle URLs do not
1939 take host names. That means 'bundle://../' has a path of '../'.
1978 take host names. That means 'bundle://../' has a path of '../'.
1940
1979
1941 Examples:
1980 Examples:
1942
1981
1943 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1982 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1944 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1983 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1945 >>> url('ssh://[::1]:2200//home/joe/repo')
1984 >>> url('ssh://[::1]:2200//home/joe/repo')
1946 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1985 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1947 >>> url('file:///home/joe/repo')
1986 >>> url('file:///home/joe/repo')
1948 <url scheme: 'file', path: '/home/joe/repo'>
1987 <url scheme: 'file', path: '/home/joe/repo'>
1949 >>> url('file:///c:/temp/foo/')
1988 >>> url('file:///c:/temp/foo/')
1950 <url scheme: 'file', path: 'c:/temp/foo/'>
1989 <url scheme: 'file', path: 'c:/temp/foo/'>
1951 >>> url('bundle:foo')
1990 >>> url('bundle:foo')
1952 <url scheme: 'bundle', path: 'foo'>
1991 <url scheme: 'bundle', path: 'foo'>
1953 >>> url('bundle://../foo')
1992 >>> url('bundle://../foo')
1954 <url scheme: 'bundle', path: '../foo'>
1993 <url scheme: 'bundle', path: '../foo'>
1955 >>> url(r'c:\foo\bar')
1994 >>> url(r'c:\foo\bar')
1956 <url path: 'c:\\foo\\bar'>
1995 <url path: 'c:\\foo\\bar'>
1957 >>> url(r'\\blah\blah\blah')
1996 >>> url(r'\\blah\blah\blah')
1958 <url path: '\\\\blah\\blah\\blah'>
1997 <url path: '\\\\blah\\blah\\blah'>
1959 >>> url(r'\\blah\blah\blah#baz')
1998 >>> url(r'\\blah\blah\blah#baz')
1960 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1999 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1961 >>> url(r'file:///C:\users\me')
2000 >>> url(r'file:///C:\users\me')
1962 <url scheme: 'file', path: 'C:\\users\\me'>
2001 <url scheme: 'file', path: 'C:\\users\\me'>
1963
2002
1964 Authentication credentials:
2003 Authentication credentials:
1965
2004
1966 >>> url('ssh://joe:xyz@x/repo')
2005 >>> url('ssh://joe:xyz@x/repo')
1967 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2006 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1968 >>> url('ssh://joe@x/repo')
2007 >>> url('ssh://joe@x/repo')
1969 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2008 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1970
2009
1971 Query strings and fragments:
2010 Query strings and fragments:
1972
2011
1973 >>> url('http://host/a?b#c')
2012 >>> url('http://host/a?b#c')
1974 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2013 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1975 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2014 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1976 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2015 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1977 """
2016 """
1978
2017
1979 _safechars = "!~*'()+"
2018 _safechars = "!~*'()+"
1980 _safepchars = "/!~*'()+:\\"
2019 _safepchars = "/!~*'()+:\\"
1981 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
2020 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
1982
2021
1983 def __init__(self, path, parsequery=True, parsefragment=True):
2022 def __init__(self, path, parsequery=True, parsefragment=True):
1984 # We slowly chomp away at path until we have only the path left
2023 # We slowly chomp away at path until we have only the path left
1985 self.scheme = self.user = self.passwd = self.host = None
2024 self.scheme = self.user = self.passwd = self.host = None
1986 self.port = self.path = self.query = self.fragment = None
2025 self.port = self.path = self.query = self.fragment = None
1987 self._localpath = True
2026 self._localpath = True
1988 self._hostport = ''
2027 self._hostport = ''
1989 self._origpath = path
2028 self._origpath = path
1990
2029
1991 if parsefragment and '#' in path:
2030 if parsefragment and '#' in path:
1992 path, self.fragment = path.split('#', 1)
2031 path, self.fragment = path.split('#', 1)
1993 if not path:
2032 if not path:
1994 path = None
2033 path = None
1995
2034
1996 # special case for Windows drive letters and UNC paths
2035 # special case for Windows drive letters and UNC paths
1997 if hasdriveletter(path) or path.startswith(r'\\'):
2036 if hasdriveletter(path) or path.startswith(r'\\'):
1998 self.path = path
2037 self.path = path
1999 return
2038 return
2000
2039
2001 # For compatibility reasons, we can't handle bundle paths as
2040 # For compatibility reasons, we can't handle bundle paths as
2002 # normal URLS
2041 # normal URLS
2003 if path.startswith('bundle:'):
2042 if path.startswith('bundle:'):
2004 self.scheme = 'bundle'
2043 self.scheme = 'bundle'
2005 path = path[7:]
2044 path = path[7:]
2006 if path.startswith('//'):
2045 if path.startswith('//'):
2007 path = path[2:]
2046 path = path[2:]
2008 self.path = path
2047 self.path = path
2009 return
2048 return
2010
2049
2011 if self._matchscheme(path):
2050 if self._matchscheme(path):
2012 parts = path.split(':', 1)
2051 parts = path.split(':', 1)
2013 if parts[0]:
2052 if parts[0]:
2014 self.scheme, path = parts
2053 self.scheme, path = parts
2015 self._localpath = False
2054 self._localpath = False
2016
2055
2017 if not path:
2056 if not path:
2018 path = None
2057 path = None
2019 if self._localpath:
2058 if self._localpath:
2020 self.path = ''
2059 self.path = ''
2021 return
2060 return
2022 else:
2061 else:
2023 if self._localpath:
2062 if self._localpath:
2024 self.path = path
2063 self.path = path
2025 return
2064 return
2026
2065
2027 if parsequery and '?' in path:
2066 if parsequery and '?' in path:
2028 path, self.query = path.split('?', 1)
2067 path, self.query = path.split('?', 1)
2029 if not path:
2068 if not path:
2030 path = None
2069 path = None
2031 if not self.query:
2070 if not self.query:
2032 self.query = None
2071 self.query = None
2033
2072
2034 # // is required to specify a host/authority
2073 # // is required to specify a host/authority
2035 if path and path.startswith('//'):
2074 if path and path.startswith('//'):
2036 parts = path[2:].split('/', 1)
2075 parts = path[2:].split('/', 1)
2037 if len(parts) > 1:
2076 if len(parts) > 1:
2038 self.host, path = parts
2077 self.host, path = parts
2039 else:
2078 else:
2040 self.host = parts[0]
2079 self.host = parts[0]
2041 path = None
2080 path = None
2042 if not self.host:
2081 if not self.host:
2043 self.host = None
2082 self.host = None
2044 # path of file:///d is /d
2083 # path of file:///d is /d
2045 # path of file:///d:/ is d:/, not /d:/
2084 # path of file:///d:/ is d:/, not /d:/
2046 if path and not hasdriveletter(path):
2085 if path and not hasdriveletter(path):
2047 path = '/' + path
2086 path = '/' + path
2048
2087
2049 if self.host and '@' in self.host:
2088 if self.host and '@' in self.host:
2050 self.user, self.host = self.host.rsplit('@', 1)
2089 self.user, self.host = self.host.rsplit('@', 1)
2051 if ':' in self.user:
2090 if ':' in self.user:
2052 self.user, self.passwd = self.user.split(':', 1)
2091 self.user, self.passwd = self.user.split(':', 1)
2053 if not self.host:
2092 if not self.host:
2054 self.host = None
2093 self.host = None
2055
2094
2056 # Don't split on colons in IPv6 addresses without ports
2095 # Don't split on colons in IPv6 addresses without ports
2057 if (self.host and ':' in self.host and
2096 if (self.host and ':' in self.host and
2058 not (self.host.startswith('[') and self.host.endswith(']'))):
2097 not (self.host.startswith('[') and self.host.endswith(']'))):
2059 self._hostport = self.host
2098 self._hostport = self.host
2060 self.host, self.port = self.host.rsplit(':', 1)
2099 self.host, self.port = self.host.rsplit(':', 1)
2061 if not self.host:
2100 if not self.host:
2062 self.host = None
2101 self.host = None
2063
2102
2064 if (self.host and self.scheme == 'file' and
2103 if (self.host and self.scheme == 'file' and
2065 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2104 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2066 raise Abort(_('file:// URLs can only refer to localhost'))
2105 raise Abort(_('file:// URLs can only refer to localhost'))
2067
2106
2068 self.path = path
2107 self.path = path
2069
2108
2070 # leave the query string escaped
2109 # leave the query string escaped
2071 for a in ('user', 'passwd', 'host', 'port',
2110 for a in ('user', 'passwd', 'host', 'port',
2072 'path', 'fragment'):
2111 'path', 'fragment'):
2073 v = getattr(self, a)
2112 v = getattr(self, a)
2074 if v is not None:
2113 if v is not None:
2075 setattr(self, a, _urlunquote(v))
2114 setattr(self, a, _urlunquote(v))
2076
2115
2077 def __repr__(self):
2116 def __repr__(self):
2078 attrs = []
2117 attrs = []
2079 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2118 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2080 'query', 'fragment'):
2119 'query', 'fragment'):
2081 v = getattr(self, a)
2120 v = getattr(self, a)
2082 if v is not None:
2121 if v is not None:
2083 attrs.append('%s: %r' % (a, v))
2122 attrs.append('%s: %r' % (a, v))
2084 return '<url %s>' % ', '.join(attrs)
2123 return '<url %s>' % ', '.join(attrs)
2085
2124
2086 def __str__(self):
2125 def __str__(self):
2087 r"""Join the URL's components back into a URL string.
2126 r"""Join the URL's components back into a URL string.
2088
2127
2089 Examples:
2128 Examples:
2090
2129
2091 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2130 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2092 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2131 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2093 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2132 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2094 'http://user:pw@host:80/?foo=bar&baz=42'
2133 'http://user:pw@host:80/?foo=bar&baz=42'
2095 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2134 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2096 'http://user:pw@host:80/?foo=bar%3dbaz'
2135 'http://user:pw@host:80/?foo=bar%3dbaz'
2097 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2136 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2098 'ssh://user:pw@[::1]:2200//home/joe#'
2137 'ssh://user:pw@[::1]:2200//home/joe#'
2099 >>> str(url('http://localhost:80//'))
2138 >>> str(url('http://localhost:80//'))
2100 'http://localhost:80//'
2139 'http://localhost:80//'
2101 >>> str(url('http://localhost:80/'))
2140 >>> str(url('http://localhost:80/'))
2102 'http://localhost:80/'
2141 'http://localhost:80/'
2103 >>> str(url('http://localhost:80'))
2142 >>> str(url('http://localhost:80'))
2104 'http://localhost:80/'
2143 'http://localhost:80/'
2105 >>> str(url('bundle:foo'))
2144 >>> str(url('bundle:foo'))
2106 'bundle:foo'
2145 'bundle:foo'
2107 >>> str(url('bundle://../foo'))
2146 >>> str(url('bundle://../foo'))
2108 'bundle:../foo'
2147 'bundle:../foo'
2109 >>> str(url('path'))
2148 >>> str(url('path'))
2110 'path'
2149 'path'
2111 >>> str(url('file:///tmp/foo/bar'))
2150 >>> str(url('file:///tmp/foo/bar'))
2112 'file:///tmp/foo/bar'
2151 'file:///tmp/foo/bar'
2113 >>> str(url('file:///c:/tmp/foo/bar'))
2152 >>> str(url('file:///c:/tmp/foo/bar'))
2114 'file:///c:/tmp/foo/bar'
2153 'file:///c:/tmp/foo/bar'
2115 >>> print url(r'bundle:foo\bar')
2154 >>> print url(r'bundle:foo\bar')
2116 bundle:foo\bar
2155 bundle:foo\bar
2117 >>> print url(r'file:///D:\data\hg')
2156 >>> print url(r'file:///D:\data\hg')
2118 file:///D:\data\hg
2157 file:///D:\data\hg
2119 """
2158 """
2120 if self._localpath:
2159 if self._localpath:
2121 s = self.path
2160 s = self.path
2122 if self.scheme == 'bundle':
2161 if self.scheme == 'bundle':
2123 s = 'bundle:' + s
2162 s = 'bundle:' + s
2124 if self.fragment:
2163 if self.fragment:
2125 s += '#' + self.fragment
2164 s += '#' + self.fragment
2126 return s
2165 return s
2127
2166
2128 s = self.scheme + ':'
2167 s = self.scheme + ':'
2129 if self.user or self.passwd or self.host:
2168 if self.user or self.passwd or self.host:
2130 s += '//'
2169 s += '//'
2131 elif self.scheme and (not self.path or self.path.startswith('/')
2170 elif self.scheme and (not self.path or self.path.startswith('/')
2132 or hasdriveletter(self.path)):
2171 or hasdriveletter(self.path)):
2133 s += '//'
2172 s += '//'
2134 if hasdriveletter(self.path):
2173 if hasdriveletter(self.path):
2135 s += '/'
2174 s += '/'
2136 if self.user:
2175 if self.user:
2137 s += urllib.quote(self.user, safe=self._safechars)
2176 s += urllib.quote(self.user, safe=self._safechars)
2138 if self.passwd:
2177 if self.passwd:
2139 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
2178 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
2140 if self.user or self.passwd:
2179 if self.user or self.passwd:
2141 s += '@'
2180 s += '@'
2142 if self.host:
2181 if self.host:
2143 if not (self.host.startswith('[') and self.host.endswith(']')):
2182 if not (self.host.startswith('[') and self.host.endswith(']')):
2144 s += urllib.quote(self.host)
2183 s += urllib.quote(self.host)
2145 else:
2184 else:
2146 s += self.host
2185 s += self.host
2147 if self.port:
2186 if self.port:
2148 s += ':' + urllib.quote(self.port)
2187 s += ':' + urllib.quote(self.port)
2149 if self.host:
2188 if self.host:
2150 s += '/'
2189 s += '/'
2151 if self.path:
2190 if self.path:
2152 # TODO: similar to the query string, we should not unescape the
2191 # TODO: similar to the query string, we should not unescape the
2153 # path when we store it, the path might contain '%2f' = '/',
2192 # path when we store it, the path might contain '%2f' = '/',
2154 # which we should *not* escape.
2193 # which we should *not* escape.
2155 s += urllib.quote(self.path, safe=self._safepchars)
2194 s += urllib.quote(self.path, safe=self._safepchars)
2156 if self.query:
2195 if self.query:
2157 # we store the query in escaped form.
2196 # we store the query in escaped form.
2158 s += '?' + self.query
2197 s += '?' + self.query
2159 if self.fragment is not None:
2198 if self.fragment is not None:
2160 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
2199 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
2161 return s
2200 return s
2162
2201
2163 def authinfo(self):
2202 def authinfo(self):
2164 user, passwd = self.user, self.passwd
2203 user, passwd = self.user, self.passwd
2165 try:
2204 try:
2166 self.user, self.passwd = None, None
2205 self.user, self.passwd = None, None
2167 s = str(self)
2206 s = str(self)
2168 finally:
2207 finally:
2169 self.user, self.passwd = user, passwd
2208 self.user, self.passwd = user, passwd
2170 if not self.user:
2209 if not self.user:
2171 return (s, None)
2210 return (s, None)
2172 # authinfo[1] is passed to urllib2 password manager, and its
2211 # authinfo[1] is passed to urllib2 password manager, and its
2173 # URIs must not contain credentials. The host is passed in the
2212 # URIs must not contain credentials. The host is passed in the
2174 # URIs list because Python < 2.4.3 uses only that to search for
2213 # URIs list because Python < 2.4.3 uses only that to search for
2175 # a password.
2214 # a password.
2176 return (s, (None, (s, self.host),
2215 return (s, (None, (s, self.host),
2177 self.user, self.passwd or ''))
2216 self.user, self.passwd or ''))
2178
2217
2179 def isabs(self):
2218 def isabs(self):
2180 if self.scheme and self.scheme != 'file':
2219 if self.scheme and self.scheme != 'file':
2181 return True # remote URL
2220 return True # remote URL
2182 if hasdriveletter(self.path):
2221 if hasdriveletter(self.path):
2183 return True # absolute for our purposes - can't be joined()
2222 return True # absolute for our purposes - can't be joined()
2184 if self.path.startswith(r'\\'):
2223 if self.path.startswith(r'\\'):
2185 return True # Windows UNC path
2224 return True # Windows UNC path
2186 if self.path.startswith('/'):
2225 if self.path.startswith('/'):
2187 return True # POSIX-style
2226 return True # POSIX-style
2188 return False
2227 return False
2189
2228
2190 def localpath(self):
2229 def localpath(self):
2191 if self.scheme == 'file' or self.scheme == 'bundle':
2230 if self.scheme == 'file' or self.scheme == 'bundle':
2192 path = self.path or '/'
2231 path = self.path or '/'
2193 # For Windows, we need to promote hosts containing drive
2232 # For Windows, we need to promote hosts containing drive
2194 # letters to paths with drive letters.
2233 # letters to paths with drive letters.
2195 if hasdriveletter(self._hostport):
2234 if hasdriveletter(self._hostport):
2196 path = self._hostport + '/' + self.path
2235 path = self._hostport + '/' + self.path
2197 elif (self.host is not None and self.path
2236 elif (self.host is not None and self.path
2198 and not hasdriveletter(path)):
2237 and not hasdriveletter(path)):
2199 path = '/' + path
2238 path = '/' + path
2200 return path
2239 return path
2201 return self._origpath
2240 return self._origpath
2202
2241
2203 def islocal(self):
2242 def islocal(self):
2204 '''whether localpath will return something that posixfile can open'''
2243 '''whether localpath will return something that posixfile can open'''
2205 return (not self.scheme or self.scheme == 'file'
2244 return (not self.scheme or self.scheme == 'file'
2206 or self.scheme == 'bundle')
2245 or self.scheme == 'bundle')
2207
2246
2208 def hasscheme(path):
2247 def hasscheme(path):
2209 return bool(url(path).scheme)
2248 return bool(url(path).scheme)
2210
2249
2211 def hasdriveletter(path):
2250 def hasdriveletter(path):
2212 return path and path[1:2] == ':' and path[0:1].isalpha()
2251 return path and path[1:2] == ':' and path[0:1].isalpha()
2213
2252
2214 def urllocalpath(path):
2253 def urllocalpath(path):
2215 return url(path, parsequery=False, parsefragment=False).localpath()
2254 return url(path, parsequery=False, parsefragment=False).localpath()
2216
2255
2217 def hidepassword(u):
2256 def hidepassword(u):
2218 '''hide user credential in a url string'''
2257 '''hide user credential in a url string'''
2219 u = url(u)
2258 u = url(u)
2220 if u.passwd:
2259 if u.passwd:
2221 u.passwd = '***'
2260 u.passwd = '***'
2222 return str(u)
2261 return str(u)
2223
2262
2224 def removeauth(u):
2263 def removeauth(u):
2225 '''remove all authentication information from a url string'''
2264 '''remove all authentication information from a url string'''
2226 u = url(u)
2265 u = url(u)
2227 u.user = u.passwd = None
2266 u.user = u.passwd = None
2228 return str(u)
2267 return str(u)
2229
2268
2230 def isatty(fd):
2269 def isatty(fd):
2231 try:
2270 try:
2232 return fd.isatty()
2271 return fd.isatty()
2233 except AttributeError:
2272 except AttributeError:
2234 return False
2273 return False
2235
2274
2236 timecount = unitcountfn(
2275 timecount = unitcountfn(
2237 (1, 1e3, _('%.0f s')),
2276 (1, 1e3, _('%.0f s')),
2238 (100, 1, _('%.1f s')),
2277 (100, 1, _('%.1f s')),
2239 (10, 1, _('%.2f s')),
2278 (10, 1, _('%.2f s')),
2240 (1, 1, _('%.3f s')),
2279 (1, 1, _('%.3f s')),
2241 (100, 0.001, _('%.1f ms')),
2280 (100, 0.001, _('%.1f ms')),
2242 (10, 0.001, _('%.2f ms')),
2281 (10, 0.001, _('%.2f ms')),
2243 (1, 0.001, _('%.3f ms')),
2282 (1, 0.001, _('%.3f ms')),
2244 (100, 0.000001, _('%.1f us')),
2283 (100, 0.000001, _('%.1f us')),
2245 (10, 0.000001, _('%.2f us')),
2284 (10, 0.000001, _('%.2f us')),
2246 (1, 0.000001, _('%.3f us')),
2285 (1, 0.000001, _('%.3f us')),
2247 (100, 0.000000001, _('%.1f ns')),
2286 (100, 0.000000001, _('%.1f ns')),
2248 (10, 0.000000001, _('%.2f ns')),
2287 (10, 0.000000001, _('%.2f ns')),
2249 (1, 0.000000001, _('%.3f ns')),
2288 (1, 0.000000001, _('%.3f ns')),
2250 )
2289 )
2251
2290
2252 _timenesting = [0]
2291 _timenesting = [0]
2253
2292
2254 def timed(func):
2293 def timed(func):
2255 '''Report the execution time of a function call to stderr.
2294 '''Report the execution time of a function call to stderr.
2256
2295
2257 During development, use as a decorator when you need to measure
2296 During development, use as a decorator when you need to measure
2258 the cost of a function, e.g. as follows:
2297 the cost of a function, e.g. as follows:
2259
2298
2260 @util.timed
2299 @util.timed
2261 def foo(a, b, c):
2300 def foo(a, b, c):
2262 pass
2301 pass
2263 '''
2302 '''
2264
2303
2265 def wrapper(*args, **kwargs):
2304 def wrapper(*args, **kwargs):
2266 start = time.time()
2305 start = time.time()
2267 indent = 2
2306 indent = 2
2268 _timenesting[0] += indent
2307 _timenesting[0] += indent
2269 try:
2308 try:
2270 return func(*args, **kwargs)
2309 return func(*args, **kwargs)
2271 finally:
2310 finally:
2272 elapsed = time.time() - start
2311 elapsed = time.time() - start
2273 _timenesting[0] -= indent
2312 _timenesting[0] -= indent
2274 sys.stderr.write('%s%s: %s\n' %
2313 sys.stderr.write('%s%s: %s\n' %
2275 (' ' * _timenesting[0], func.__name__,
2314 (' ' * _timenesting[0], func.__name__,
2276 timecount(elapsed)))
2315 timecount(elapsed)))
2277 return wrapper
2316 return wrapper
2278
2317
2279 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2318 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2280 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2319 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2281
2320
2282 def sizetoint(s):
2321 def sizetoint(s):
2283 '''Convert a space specifier to a byte count.
2322 '''Convert a space specifier to a byte count.
2284
2323
2285 >>> sizetoint('30')
2324 >>> sizetoint('30')
2286 30
2325 30
2287 >>> sizetoint('2.2kb')
2326 >>> sizetoint('2.2kb')
2288 2252
2327 2252
2289 >>> sizetoint('6M')
2328 >>> sizetoint('6M')
2290 6291456
2329 6291456
2291 '''
2330 '''
2292 t = s.strip().lower()
2331 t = s.strip().lower()
2293 try:
2332 try:
2294 for k, u in _sizeunits:
2333 for k, u in _sizeunits:
2295 if t.endswith(k):
2334 if t.endswith(k):
2296 return int(float(t[:-len(k)]) * u)
2335 return int(float(t[:-len(k)]) * u)
2297 return int(t)
2336 return int(t)
2298 except ValueError:
2337 except ValueError:
2299 raise error.ParseError(_("couldn't parse size: %s") % s)
2338 raise error.ParseError(_("couldn't parse size: %s") % s)
2300
2339
2301 class hooks(object):
2340 class hooks(object):
2302 '''A collection of hook functions that can be used to extend a
2341 '''A collection of hook functions that can be used to extend a
2303 function's behavior. Hooks are called in lexicographic order,
2342 function's behavior. Hooks are called in lexicographic order,
2304 based on the names of their sources.'''
2343 based on the names of their sources.'''
2305
2344
2306 def __init__(self):
2345 def __init__(self):
2307 self._hooks = []
2346 self._hooks = []
2308
2347
2309 def add(self, source, hook):
2348 def add(self, source, hook):
2310 self._hooks.append((source, hook))
2349 self._hooks.append((source, hook))
2311
2350
2312 def __call__(self, *args):
2351 def __call__(self, *args):
2313 self._hooks.sort(key=lambda x: x[0])
2352 self._hooks.sort(key=lambda x: x[0])
2314 results = []
2353 results = []
2315 for source, hook in self._hooks:
2354 for source, hook in self._hooks:
2316 results.append(hook(*args))
2355 results.append(hook(*args))
2317 return results
2356 return results
2318
2357
2319 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2358 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2320 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2359 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2321 Skips the 'skip' last entries. By default it will flush stdout first.
2360 Skips the 'skip' last entries. By default it will flush stdout first.
2322 It can be used everywhere and do intentionally not require an ui object.
2361 It can be used everywhere and do intentionally not require an ui object.
2323 Not be used in production code but very convenient while developing.
2362 Not be used in production code but very convenient while developing.
2324 '''
2363 '''
2325 if otherf:
2364 if otherf:
2326 otherf.flush()
2365 otherf.flush()
2327 f.write('%s at:\n' % msg)
2366 f.write('%s at:\n' % msg)
2328 entries = [('%s:%s' % (fn, ln), func)
2367 entries = [('%s:%s' % (fn, ln), func)
2329 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2368 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2330 if entries:
2369 if entries:
2331 fnmax = max(len(entry[0]) for entry in entries)
2370 fnmax = max(len(entry[0]) for entry in entries)
2332 for fnln, func in entries:
2371 for fnln, func in entries:
2333 f.write(' %-*s in %s\n' % (fnmax, fnln, func))
2372 f.write(' %-*s in %s\n' % (fnmax, fnln, func))
2334 f.flush()
2373 f.flush()
2335
2374
2336 class dirs(object):
2375 class dirs(object):
2337 '''a multiset of directory names from a dirstate or manifest'''
2376 '''a multiset of directory names from a dirstate or manifest'''
2338
2377
2339 def __init__(self, map, skip=None):
2378 def __init__(self, map, skip=None):
2340 self._dirs = {}
2379 self._dirs = {}
2341 addpath = self.addpath
2380 addpath = self.addpath
2342 if safehasattr(map, 'iteritems') and skip is not None:
2381 if safehasattr(map, 'iteritems') and skip is not None:
2343 for f, s in map.iteritems():
2382 for f, s in map.iteritems():
2344 if s[0] != skip:
2383 if s[0] != skip:
2345 addpath(f)
2384 addpath(f)
2346 else:
2385 else:
2347 for f in map:
2386 for f in map:
2348 addpath(f)
2387 addpath(f)
2349
2388
2350 def addpath(self, path):
2389 def addpath(self, path):
2351 dirs = self._dirs
2390 dirs = self._dirs
2352 for base in finddirs(path):
2391 for base in finddirs(path):
2353 if base in dirs:
2392 if base in dirs:
2354 dirs[base] += 1
2393 dirs[base] += 1
2355 return
2394 return
2356 dirs[base] = 1
2395 dirs[base] = 1
2357
2396
2358 def delpath(self, path):
2397 def delpath(self, path):
2359 dirs = self._dirs
2398 dirs = self._dirs
2360 for base in finddirs(path):
2399 for base in finddirs(path):
2361 if dirs[base] > 1:
2400 if dirs[base] > 1:
2362 dirs[base] -= 1
2401 dirs[base] -= 1
2363 return
2402 return
2364 del dirs[base]
2403 del dirs[base]
2365
2404
2366 def __iter__(self):
2405 def __iter__(self):
2367 return self._dirs.iterkeys()
2406 return self._dirs.iterkeys()
2368
2407
2369 def __contains__(self, d):
2408 def __contains__(self, d):
2370 return d in self._dirs
2409 return d in self._dirs
2371
2410
2372 if safehasattr(parsers, 'dirs'):
2411 if safehasattr(parsers, 'dirs'):
2373 dirs = parsers.dirs
2412 dirs = parsers.dirs
2374
2413
2375 def finddirs(path):
2414 def finddirs(path):
2376 pos = path.rfind('/')
2415 pos = path.rfind('/')
2377 while pos != -1:
2416 while pos != -1:
2378 yield path[:pos]
2417 yield path[:pos]
2379 pos = path.rfind('/', 0, pos)
2418 pos = path.rfind('/', 0, pos)
2380
2419
2381 # compression utility
2420 # compression utility
2382
2421
2383 class nocompress(object):
2422 class nocompress(object):
2384 def compress(self, x):
2423 def compress(self, x):
2385 return x
2424 return x
2386 def flush(self):
2425 def flush(self):
2387 return ""
2426 return ""
2388
2427
2389 compressors = {
2428 compressors = {
2390 None: nocompress,
2429 None: nocompress,
2391 # lambda to prevent early import
2430 # lambda to prevent early import
2392 'BZ': lambda: bz2.BZ2Compressor(),
2431 'BZ': lambda: bz2.BZ2Compressor(),
2393 'GZ': lambda: zlib.compressobj(),
2432 'GZ': lambda: zlib.compressobj(),
2394 }
2433 }
2395 # also support the old form by courtesies
2434 # also support the old form by courtesies
2396 compressors['UN'] = compressors[None]
2435 compressors['UN'] = compressors[None]
2397
2436
2398 def _makedecompressor(decompcls):
2437 def _makedecompressor(decompcls):
2399 def generator(f):
2438 def generator(f):
2400 d = decompcls()
2439 d = decompcls()
2401 for chunk in filechunkiter(f):
2440 for chunk in filechunkiter(f):
2402 yield d.decompress(chunk)
2441 yield d.decompress(chunk)
2403 def func(fh):
2442 def func(fh):
2404 return chunkbuffer(generator(fh))
2443 return chunkbuffer(generator(fh))
2405 return func
2444 return func
2406
2445
2407 def _bz2():
2446 def _bz2():
2408 d = bz2.BZ2Decompressor()
2447 d = bz2.BZ2Decompressor()
2409 # Bzip2 stream start with BZ, but we stripped it.
2448 # Bzip2 stream start with BZ, but we stripped it.
2410 # we put it back for good measure.
2449 # we put it back for good measure.
2411 d.decompress('BZ')
2450 d.decompress('BZ')
2412 return d
2451 return d
2413
2452
2414 decompressors = {None: lambda fh: fh,
2453 decompressors = {None: lambda fh: fh,
2415 '_truncatedBZ': _makedecompressor(_bz2),
2454 '_truncatedBZ': _makedecompressor(_bz2),
2416 'BZ': _makedecompressor(lambda: bz2.BZ2Decompressor()),
2455 'BZ': _makedecompressor(lambda: bz2.BZ2Decompressor()),
2417 'GZ': _makedecompressor(lambda: zlib.decompressobj()),
2456 'GZ': _makedecompressor(lambda: zlib.decompressobj()),
2418 }
2457 }
2419 # also support the old form by courtesies
2458 # also support the old form by courtesies
2420 decompressors['UN'] = decompressors[None]
2459 decompressors['UN'] = decompressors[None]
2421
2460
2422 # convenient shortcut
2461 # convenient shortcut
2423 dst = debugstacktrace
2462 dst = debugstacktrace
General Comments 0
You need to be logged in to leave comments. Login now