##// END OF EJS Templates
matcher: use re2 bindings if available...
Bryan O'Sullivan -
r16943:8d08a28a default
parent child Browse files
Show More
@@ -1,344 +1,352 b''
1 # match.py - filename matching
1 # match.py - filename matching
2 #
2 #
3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import re
8 import re
9 import scmutil, util, fileset
9 import scmutil, util, fileset
10 from i18n import _
10 from i18n import _
11
11
12 def _rematcher(pat):
13 m = util.compilere(pat)
14 try:
15 # slightly faster, provided by facebook's re2 bindings
16 return m.test_match
17 except AttributeError:
18 return m.match
19
12 def _expandsets(pats, ctx):
20 def _expandsets(pats, ctx):
13 '''convert set: patterns into a list of files in the given context'''
21 '''convert set: patterns into a list of files in the given context'''
14 fset = set()
22 fset = set()
15 other = []
23 other = []
16
24
17 for kind, expr in pats:
25 for kind, expr in pats:
18 if kind == 'set':
26 if kind == 'set':
19 if not ctx:
27 if not ctx:
20 raise util.Abort("fileset expression with no context")
28 raise util.Abort("fileset expression with no context")
21 s = fileset.getfileset(ctx, expr)
29 s = fileset.getfileset(ctx, expr)
22 fset.update(s)
30 fset.update(s)
23 continue
31 continue
24 other.append((kind, expr))
32 other.append((kind, expr))
25 return fset, other
33 return fset, other
26
34
27 class match(object):
35 class match(object):
28 def __init__(self, root, cwd, patterns, include=[], exclude=[],
36 def __init__(self, root, cwd, patterns, include=[], exclude=[],
29 default='glob', exact=False, auditor=None, ctx=None):
37 default='glob', exact=False, auditor=None, ctx=None):
30 """build an object to match a set of file patterns
38 """build an object to match a set of file patterns
31
39
32 arguments:
40 arguments:
33 root - the canonical root of the tree you're matching against
41 root - the canonical root of the tree you're matching against
34 cwd - the current working directory, if relevant
42 cwd - the current working directory, if relevant
35 patterns - patterns to find
43 patterns - patterns to find
36 include - patterns to include
44 include - patterns to include
37 exclude - patterns to exclude
45 exclude - patterns to exclude
38 default - if a pattern in names has no explicit type, assume this one
46 default - if a pattern in names has no explicit type, assume this one
39 exact - patterns are actually literals
47 exact - patterns are actually literals
40
48
41 a pattern is one of:
49 a pattern is one of:
42 'glob:<glob>' - a glob relative to cwd
50 'glob:<glob>' - a glob relative to cwd
43 're:<regexp>' - a regular expression
51 're:<regexp>' - a regular expression
44 'path:<path>' - a path relative to canonroot
52 'path:<path>' - a path relative to canonroot
45 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
53 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
46 'relpath:<path>' - a path relative to cwd
54 'relpath:<path>' - a path relative to cwd
47 'relre:<regexp>' - a regexp that needn't match the start of a name
55 'relre:<regexp>' - a regexp that needn't match the start of a name
48 'set:<fileset>' - a fileset expression
56 'set:<fileset>' - a fileset expression
49 '<something>' - a pattern of the specified default type
57 '<something>' - a pattern of the specified default type
50 """
58 """
51
59
52 self._root = root
60 self._root = root
53 self._cwd = cwd
61 self._cwd = cwd
54 self._files = []
62 self._files = []
55 self._anypats = bool(include or exclude)
63 self._anypats = bool(include or exclude)
56 self._ctx = ctx
64 self._ctx = ctx
57
65
58 if include:
66 if include:
59 pats = _normalize(include, 'glob', root, cwd, auditor)
67 pats = _normalize(include, 'glob', root, cwd, auditor)
60 self.includepat, im = _buildmatch(ctx, pats, '(?:/|$)')
68 self.includepat, im = _buildmatch(ctx, pats, '(?:/|$)')
61 if exclude:
69 if exclude:
62 pats = _normalize(exclude, 'glob', root, cwd, auditor)
70 pats = _normalize(exclude, 'glob', root, cwd, auditor)
63 self.excludepat, em = _buildmatch(ctx, pats, '(?:/|$)')
71 self.excludepat, em = _buildmatch(ctx, pats, '(?:/|$)')
64 if exact:
72 if exact:
65 if isinstance(patterns, list):
73 if isinstance(patterns, list):
66 self._files = patterns
74 self._files = patterns
67 else:
75 else:
68 self._files = list(patterns)
76 self._files = list(patterns)
69 pm = self.exact
77 pm = self.exact
70 elif patterns:
78 elif patterns:
71 pats = _normalize(patterns, default, root, cwd, auditor)
79 pats = _normalize(patterns, default, root, cwd, auditor)
72 self._files = _roots(pats)
80 self._files = _roots(pats)
73 self._anypats = self._anypats or _anypats(pats)
81 self._anypats = self._anypats or _anypats(pats)
74 self.patternspat, pm = _buildmatch(ctx, pats, '$')
82 self.patternspat, pm = _buildmatch(ctx, pats, '$')
75
83
76 if patterns or exact:
84 if patterns or exact:
77 if include:
85 if include:
78 if exclude:
86 if exclude:
79 m = lambda f: im(f) and not em(f) and pm(f)
87 m = lambda f: im(f) and not em(f) and pm(f)
80 else:
88 else:
81 m = lambda f: im(f) and pm(f)
89 m = lambda f: im(f) and pm(f)
82 else:
90 else:
83 if exclude:
91 if exclude:
84 m = lambda f: not em(f) and pm(f)
92 m = lambda f: not em(f) and pm(f)
85 else:
93 else:
86 m = pm
94 m = pm
87 else:
95 else:
88 if include:
96 if include:
89 if exclude:
97 if exclude:
90 m = lambda f: im(f) and not em(f)
98 m = lambda f: im(f) and not em(f)
91 else:
99 else:
92 m = im
100 m = im
93 else:
101 else:
94 if exclude:
102 if exclude:
95 m = lambda f: not em(f)
103 m = lambda f: not em(f)
96 else:
104 else:
97 m = lambda f: True
105 m = lambda f: True
98
106
99 self.matchfn = m
107 self.matchfn = m
100 self._fmap = set(self._files)
108 self._fmap = set(self._files)
101
109
102 def __call__(self, fn):
110 def __call__(self, fn):
103 return self.matchfn(fn)
111 return self.matchfn(fn)
104 def __iter__(self):
112 def __iter__(self):
105 for f in self._files:
113 for f in self._files:
106 yield f
114 yield f
107 def bad(self, f, msg):
115 def bad(self, f, msg):
108 '''callback for each explicit file that can't be
116 '''callback for each explicit file that can't be
109 found/accessed, with an error message
117 found/accessed, with an error message
110 '''
118 '''
111 pass
119 pass
112 def dir(self, f):
120 def dir(self, f):
113 pass
121 pass
114 def missing(self, f):
122 def missing(self, f):
115 pass
123 pass
116 def exact(self, f):
124 def exact(self, f):
117 return f in self._fmap
125 return f in self._fmap
118 def rel(self, f):
126 def rel(self, f):
119 return util.pathto(self._root, self._cwd, f)
127 return util.pathto(self._root, self._cwd, f)
120 def files(self):
128 def files(self):
121 return self._files
129 return self._files
122 def anypats(self):
130 def anypats(self):
123 return self._anypats
131 return self._anypats
124 def always(self):
132 def always(self):
125 return False
133 return False
126
134
127 class exact(match):
135 class exact(match):
128 def __init__(self, root, cwd, files):
136 def __init__(self, root, cwd, files):
129 match.__init__(self, root, cwd, files, exact = True)
137 match.__init__(self, root, cwd, files, exact = True)
130
138
131 class always(match):
139 class always(match):
132 def __init__(self, root, cwd):
140 def __init__(self, root, cwd):
133 match.__init__(self, root, cwd, [])
141 match.__init__(self, root, cwd, [])
134 def always(self):
142 def always(self):
135 return True
143 return True
136
144
137 class narrowmatcher(match):
145 class narrowmatcher(match):
138 """Adapt a matcher to work on a subdirectory only.
146 """Adapt a matcher to work on a subdirectory only.
139
147
140 The paths are remapped to remove/insert the path as needed:
148 The paths are remapped to remove/insert the path as needed:
141
149
142 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
150 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
143 >>> m2 = narrowmatcher('sub', m1)
151 >>> m2 = narrowmatcher('sub', m1)
144 >>> bool(m2('a.txt'))
152 >>> bool(m2('a.txt'))
145 False
153 False
146 >>> bool(m2('b.txt'))
154 >>> bool(m2('b.txt'))
147 True
155 True
148 >>> bool(m2.matchfn('a.txt'))
156 >>> bool(m2.matchfn('a.txt'))
149 False
157 False
150 >>> bool(m2.matchfn('b.txt'))
158 >>> bool(m2.matchfn('b.txt'))
151 True
159 True
152 >>> m2.files()
160 >>> m2.files()
153 ['b.txt']
161 ['b.txt']
154 >>> m2.exact('b.txt')
162 >>> m2.exact('b.txt')
155 True
163 True
156 >>> m2.rel('b.txt')
164 >>> m2.rel('b.txt')
157 'b.txt'
165 'b.txt'
158 >>> def bad(f, msg):
166 >>> def bad(f, msg):
159 ... print "%s: %s" % (f, msg)
167 ... print "%s: %s" % (f, msg)
160 >>> m1.bad = bad
168 >>> m1.bad = bad
161 >>> m2.bad('x.txt', 'No such file')
169 >>> m2.bad('x.txt', 'No such file')
162 sub/x.txt: No such file
170 sub/x.txt: No such file
163 """
171 """
164
172
165 def __init__(self, path, matcher):
173 def __init__(self, path, matcher):
166 self._root = matcher._root
174 self._root = matcher._root
167 self._cwd = matcher._cwd
175 self._cwd = matcher._cwd
168 self._path = path
176 self._path = path
169 self._matcher = matcher
177 self._matcher = matcher
170
178
171 self._files = [f[len(path) + 1:] for f in matcher._files
179 self._files = [f[len(path) + 1:] for f in matcher._files
172 if f.startswith(path + "/")]
180 if f.startswith(path + "/")]
173 self._anypats = matcher._anypats
181 self._anypats = matcher._anypats
174 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
182 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
175 self._fmap = set(self._files)
183 self._fmap = set(self._files)
176
184
177 def bad(self, f, msg):
185 def bad(self, f, msg):
178 self._matcher.bad(self._path + "/" + f, msg)
186 self._matcher.bad(self._path + "/" + f, msg)
179
187
180 def patkind(pat):
188 def patkind(pat):
181 return _patsplit(pat, None)[0]
189 return _patsplit(pat, None)[0]
182
190
183 def _patsplit(pat, default):
191 def _patsplit(pat, default):
184 """Split a string into an optional pattern kind prefix and the
192 """Split a string into an optional pattern kind prefix and the
185 actual pattern."""
193 actual pattern."""
186 if ':' in pat:
194 if ':' in pat:
187 kind, val = pat.split(':', 1)
195 kind, val = pat.split(':', 1)
188 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
196 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
189 'listfile', 'listfile0', 'set'):
197 'listfile', 'listfile0', 'set'):
190 return kind, val
198 return kind, val
191 return default, pat
199 return default, pat
192
200
193 def _globre(pat):
201 def _globre(pat):
194 "convert a glob pattern into a regexp"
202 "convert a glob pattern into a regexp"
195 i, n = 0, len(pat)
203 i, n = 0, len(pat)
196 res = ''
204 res = ''
197 group = 0
205 group = 0
198 escape = re.escape
206 escape = re.escape
199 def peek():
207 def peek():
200 return i < n and pat[i]
208 return i < n and pat[i]
201 while i < n:
209 while i < n:
202 c = pat[i]
210 c = pat[i]
203 i += 1
211 i += 1
204 if c not in '*?[{},\\':
212 if c not in '*?[{},\\':
205 res += escape(c)
213 res += escape(c)
206 elif c == '*':
214 elif c == '*':
207 if peek() == '*':
215 if peek() == '*':
208 i += 1
216 i += 1
209 res += '.*'
217 res += '.*'
210 else:
218 else:
211 res += '[^/]*'
219 res += '[^/]*'
212 elif c == '?':
220 elif c == '?':
213 res += '.'
221 res += '.'
214 elif c == '[':
222 elif c == '[':
215 j = i
223 j = i
216 if j < n and pat[j] in '!]':
224 if j < n and pat[j] in '!]':
217 j += 1
225 j += 1
218 while j < n and pat[j] != ']':
226 while j < n and pat[j] != ']':
219 j += 1
227 j += 1
220 if j >= n:
228 if j >= n:
221 res += '\\['
229 res += '\\['
222 else:
230 else:
223 stuff = pat[i:j].replace('\\','\\\\')
231 stuff = pat[i:j].replace('\\','\\\\')
224 i = j + 1
232 i = j + 1
225 if stuff[0] == '!':
233 if stuff[0] == '!':
226 stuff = '^' + stuff[1:]
234 stuff = '^' + stuff[1:]
227 elif stuff[0] == '^':
235 elif stuff[0] == '^':
228 stuff = '\\' + stuff
236 stuff = '\\' + stuff
229 res = '%s[%s]' % (res, stuff)
237 res = '%s[%s]' % (res, stuff)
230 elif c == '{':
238 elif c == '{':
231 group += 1
239 group += 1
232 res += '(?:'
240 res += '(?:'
233 elif c == '}' and group:
241 elif c == '}' and group:
234 res += ')'
242 res += ')'
235 group -= 1
243 group -= 1
236 elif c == ',' and group:
244 elif c == ',' and group:
237 res += '|'
245 res += '|'
238 elif c == '\\':
246 elif c == '\\':
239 p = peek()
247 p = peek()
240 if p:
248 if p:
241 i += 1
249 i += 1
242 res += escape(p)
250 res += escape(p)
243 else:
251 else:
244 res += escape(c)
252 res += escape(c)
245 else:
253 else:
246 res += escape(c)
254 res += escape(c)
247 return res
255 return res
248
256
249 def _regex(kind, name, tail):
257 def _regex(kind, name, tail):
250 '''convert a pattern into a regular expression'''
258 '''convert a pattern into a regular expression'''
251 if not name:
259 if not name:
252 return ''
260 return ''
253 if kind == 're':
261 if kind == 're':
254 return name
262 return name
255 elif kind == 'path':
263 elif kind == 'path':
256 return '^' + re.escape(name) + '(?:/|$)'
264 return '^' + re.escape(name) + '(?:/|$)'
257 elif kind == 'relglob':
265 elif kind == 'relglob':
258 return '(?:|.*/)' + _globre(name) + tail
266 return '(?:|.*/)' + _globre(name) + tail
259 elif kind == 'relpath':
267 elif kind == 'relpath':
260 return re.escape(name) + '(?:/|$)'
268 return re.escape(name) + '(?:/|$)'
261 elif kind == 'relre':
269 elif kind == 'relre':
262 if name.startswith('^'):
270 if name.startswith('^'):
263 return name
271 return name
264 return '.*' + name
272 return '.*' + name
265 return _globre(name) + tail
273 return _globre(name) + tail
266
274
267 def _buildmatch(ctx, pats, tail):
275 def _buildmatch(ctx, pats, tail):
268 fset, pats = _expandsets(pats, ctx)
276 fset, pats = _expandsets(pats, ctx)
269 if not pats:
277 if not pats:
270 return "", fset.__contains__
278 return "", fset.__contains__
271
279
272 pat, mf = _buildregexmatch(pats, tail)
280 pat, mf = _buildregexmatch(pats, tail)
273 if fset:
281 if fset:
274 return pat, lambda f: f in fset or mf(f)
282 return pat, lambda f: f in fset or mf(f)
275 return pat, mf
283 return pat, mf
276
284
277 def _buildregexmatch(pats, tail):
285 def _buildregexmatch(pats, tail):
278 """build a matching function from a set of patterns"""
286 """build a matching function from a set of patterns"""
279 try:
287 try:
280 pat = '(?:%s)' % '|'.join([_regex(k, p, tail) for (k, p) in pats])
288 pat = '(?:%s)' % '|'.join([_regex(k, p, tail) for (k, p) in pats])
281 if len(pat) > 20000:
289 if len(pat) > 20000:
282 raise OverflowError
290 raise OverflowError
283 return pat, re.compile(pat).match
291 return pat, _rematcher(pat)
284 except OverflowError:
292 except OverflowError:
285 # We're using a Python with a tiny regex engine and we
293 # We're using a Python with a tiny regex engine and we
286 # made it explode, so we'll divide the pattern list in two
294 # made it explode, so we'll divide the pattern list in two
287 # until it works
295 # until it works
288 l = len(pats)
296 l = len(pats)
289 if l < 2:
297 if l < 2:
290 raise
298 raise
291 pata, a = _buildregexmatch(pats[:l//2], tail)
299 pata, a = _buildregexmatch(pats[:l//2], tail)
292 patb, b = _buildregexmatch(pats[l//2:], tail)
300 patb, b = _buildregexmatch(pats[l//2:], tail)
293 return pat, lambda s: a(s) or b(s)
301 return pat, lambda s: a(s) or b(s)
294 except re.error:
302 except re.error:
295 for k, p in pats:
303 for k, p in pats:
296 try:
304 try:
297 re.compile('(?:%s)' % _regex(k, p, tail))
305 _rematcher('(?:%s)' % _regex(k, p, tail))
298 except re.error:
306 except re.error:
299 raise util.Abort(_("invalid pattern (%s): %s") % (k, p))
307 raise util.Abort(_("invalid pattern (%s): %s") % (k, p))
300 raise util.Abort(_("invalid pattern"))
308 raise util.Abort(_("invalid pattern"))
301
309
302 def _normalize(names, default, root, cwd, auditor):
310 def _normalize(names, default, root, cwd, auditor):
303 pats = []
311 pats = []
304 for kind, name in [_patsplit(p, default) for p in names]:
312 for kind, name in [_patsplit(p, default) for p in names]:
305 if kind in ('glob', 'relpath'):
313 if kind in ('glob', 'relpath'):
306 name = scmutil.canonpath(root, cwd, name, auditor)
314 name = scmutil.canonpath(root, cwd, name, auditor)
307 elif kind in ('relglob', 'path'):
315 elif kind in ('relglob', 'path'):
308 name = util.normpath(name)
316 name = util.normpath(name)
309 elif kind in ('listfile', 'listfile0'):
317 elif kind in ('listfile', 'listfile0'):
310 try:
318 try:
311 files = util.readfile(name)
319 files = util.readfile(name)
312 if kind == 'listfile0':
320 if kind == 'listfile0':
313 files = files.split('\0')
321 files = files.split('\0')
314 else:
322 else:
315 files = files.splitlines()
323 files = files.splitlines()
316 files = [f for f in files if f]
324 files = [f for f in files if f]
317 except EnvironmentError:
325 except EnvironmentError:
318 raise util.Abort(_("unable to read file list (%s)") % name)
326 raise util.Abort(_("unable to read file list (%s)") % name)
319 pats += _normalize(files, default, root, cwd, auditor)
327 pats += _normalize(files, default, root, cwd, auditor)
320 continue
328 continue
321
329
322 pats.append((kind, name))
330 pats.append((kind, name))
323 return pats
331 return pats
324
332
325 def _roots(patterns):
333 def _roots(patterns):
326 r = []
334 r = []
327 for kind, name in patterns:
335 for kind, name in patterns:
328 if kind == 'glob': # find the non-glob prefix
336 if kind == 'glob': # find the non-glob prefix
329 root = []
337 root = []
330 for p in name.split('/'):
338 for p in name.split('/'):
331 if '[' in p or '{' in p or '*' in p or '?' in p:
339 if '[' in p or '{' in p or '*' in p or '?' in p:
332 break
340 break
333 root.append(p)
341 root.append(p)
334 r.append('/'.join(root) or '.')
342 r.append('/'.join(root) or '.')
335 elif kind in ('relpath', 'path'):
343 elif kind in ('relpath', 'path'):
336 r.append(name or '.')
344 r.append(name or '.')
337 elif kind == 'relglob':
345 elif kind == 'relglob':
338 r.append('.')
346 r.append('.')
339 return r
347 return r
340
348
341 def _anypats(patterns):
349 def _anypats(patterns):
342 for kind, name in patterns:
350 for kind, name in patterns:
343 if kind in ('glob', 're', 'relglob', 'relre', 'set'):
351 if kind in ('glob', 're', 'relglob', 'relre', 'set'):
344 return True
352 return True
@@ -1,1778 +1,1802 b''
1 # util.py - Mercurial utility functions and platform specfic implementations
1 # util.py - Mercurial utility functions and platform specfic 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 specfic implementations.
10 """Mercurial utility functions and platform specfic 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 from i18n import _
16 from i18n import _
17 import error, osutil, encoding, collections
17 import error, osutil, encoding, collections
18 import errno, re, shutil, sys, tempfile, traceback
18 import errno, re, shutil, sys, tempfile, traceback
19 import os, time, datetime, calendar, textwrap, signal
19 import os, time, datetime, calendar, textwrap, signal
20 import imp, socket, urllib
20 import imp, socket, urllib
21
21
22 if os.name == 'nt':
22 if os.name == 'nt':
23 import windows as platform
23 import windows as platform
24 else:
24 else:
25 import posix as platform
25 import posix as platform
26
26
27 platform.encodinglower = encoding.lower
27 platform.encodinglower = encoding.lower
28 platform.encodingupper = encoding.upper
28 platform.encodingupper = encoding.upper
29
29
30 cachestat = platform.cachestat
30 cachestat = platform.cachestat
31 checkexec = platform.checkexec
31 checkexec = platform.checkexec
32 checklink = platform.checklink
32 checklink = platform.checklink
33 copymode = platform.copymode
33 copymode = platform.copymode
34 executablepath = platform.executablepath
34 executablepath = platform.executablepath
35 expandglobs = platform.expandglobs
35 expandglobs = platform.expandglobs
36 explainexit = platform.explainexit
36 explainexit = platform.explainexit
37 findexe = platform.findexe
37 findexe = platform.findexe
38 gethgcmd = platform.gethgcmd
38 gethgcmd = platform.gethgcmd
39 getuser = platform.getuser
39 getuser = platform.getuser
40 groupmembers = platform.groupmembers
40 groupmembers = platform.groupmembers
41 groupname = platform.groupname
41 groupname = platform.groupname
42 hidewindow = platform.hidewindow
42 hidewindow = platform.hidewindow
43 isexec = platform.isexec
43 isexec = platform.isexec
44 isowner = platform.isowner
44 isowner = platform.isowner
45 localpath = platform.localpath
45 localpath = platform.localpath
46 lookupreg = platform.lookupreg
46 lookupreg = platform.lookupreg
47 makedir = platform.makedir
47 makedir = platform.makedir
48 nlinks = platform.nlinks
48 nlinks = platform.nlinks
49 normpath = platform.normpath
49 normpath = platform.normpath
50 normcase = platform.normcase
50 normcase = platform.normcase
51 nulldev = platform.nulldev
51 nulldev = platform.nulldev
52 openhardlinks = platform.openhardlinks
52 openhardlinks = platform.openhardlinks
53 oslink = platform.oslink
53 oslink = platform.oslink
54 parsepatchoutput = platform.parsepatchoutput
54 parsepatchoutput = platform.parsepatchoutput
55 pconvert = platform.pconvert
55 pconvert = platform.pconvert
56 popen = platform.popen
56 popen = platform.popen
57 posixfile = platform.posixfile
57 posixfile = platform.posixfile
58 quotecommand = platform.quotecommand
58 quotecommand = platform.quotecommand
59 realpath = platform.realpath
59 realpath = platform.realpath
60 rename = platform.rename
60 rename = platform.rename
61 samedevice = platform.samedevice
61 samedevice = platform.samedevice
62 samefile = platform.samefile
62 samefile = platform.samefile
63 samestat = platform.samestat
63 samestat = platform.samestat
64 setbinary = platform.setbinary
64 setbinary = platform.setbinary
65 setflags = platform.setflags
65 setflags = platform.setflags
66 setsignalhandler = platform.setsignalhandler
66 setsignalhandler = platform.setsignalhandler
67 shellquote = platform.shellquote
67 shellquote = platform.shellquote
68 spawndetached = platform.spawndetached
68 spawndetached = platform.spawndetached
69 sshargs = platform.sshargs
69 sshargs = platform.sshargs
70 statfiles = platform.statfiles
70 statfiles = platform.statfiles
71 termwidth = platform.termwidth
71 termwidth = platform.termwidth
72 testpid = platform.testpid
72 testpid = platform.testpid
73 umask = platform.umask
73 umask = platform.umask
74 unlink = platform.unlink
74 unlink = platform.unlink
75 unlinkpath = platform.unlinkpath
75 unlinkpath = platform.unlinkpath
76 username = platform.username
76 username = platform.username
77
77
78 # Python compatibility
78 # Python compatibility
79
79
80 _notset = object()
80 _notset = object()
81
81
82 def safehasattr(thing, attr):
82 def safehasattr(thing, attr):
83 return getattr(thing, attr, _notset) is not _notset
83 return getattr(thing, attr, _notset) is not _notset
84
84
85 def sha1(s=''):
85 def sha1(s=''):
86 '''
86 '''
87 Low-overhead wrapper around Python's SHA support
87 Low-overhead wrapper around Python's SHA support
88
88
89 >>> f = _fastsha1
89 >>> f = _fastsha1
90 >>> a = sha1()
90 >>> a = sha1()
91 >>> a = f()
91 >>> a = f()
92 >>> a.hexdigest()
92 >>> a.hexdigest()
93 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
93 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
94 '''
94 '''
95
95
96 return _fastsha1(s)
96 return _fastsha1(s)
97
97
98 def _fastsha1(s=''):
98 def _fastsha1(s=''):
99 # This function will import sha1 from hashlib or sha (whichever is
99 # This function will import sha1 from hashlib or sha (whichever is
100 # available) and overwrite itself with it on the first call.
100 # available) and overwrite itself with it on the first call.
101 # Subsequent calls will go directly to the imported function.
101 # Subsequent calls will go directly to the imported function.
102 if sys.version_info >= (2, 5):
102 if sys.version_info >= (2, 5):
103 from hashlib import sha1 as _sha1
103 from hashlib import sha1 as _sha1
104 else:
104 else:
105 from sha import sha as _sha1
105 from sha import sha as _sha1
106 global _fastsha1, sha1
106 global _fastsha1, sha1
107 _fastsha1 = sha1 = _sha1
107 _fastsha1 = sha1 = _sha1
108 return _sha1(s)
108 return _sha1(s)
109
109
110 try:
110 try:
111 buffer = buffer
111 buffer = buffer
112 except NameError:
112 except NameError:
113 if sys.version_info[0] < 3:
113 if sys.version_info[0] < 3:
114 def buffer(sliceable, offset=0):
114 def buffer(sliceable, offset=0):
115 return sliceable[offset:]
115 return sliceable[offset:]
116 else:
116 else:
117 def buffer(sliceable, offset=0):
117 def buffer(sliceable, offset=0):
118 return memoryview(sliceable)[offset:]
118 return memoryview(sliceable)[offset:]
119
119
120 import subprocess
120 import subprocess
121 closefds = os.name == 'posix'
121 closefds = os.name == 'posix'
122
122
123 def popen2(cmd, env=None, newlines=False):
123 def popen2(cmd, env=None, newlines=False):
124 # Setting bufsize to -1 lets the system decide the buffer size.
124 # Setting bufsize to -1 lets the system decide the buffer size.
125 # The default for bufsize is 0, meaning unbuffered. This leads to
125 # The default for bufsize is 0, meaning unbuffered. This leads to
126 # poor performance on Mac OS X: http://bugs.python.org/issue4194
126 # poor performance on Mac OS X: http://bugs.python.org/issue4194
127 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
127 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
128 close_fds=closefds,
128 close_fds=closefds,
129 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
129 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
130 universal_newlines=newlines,
130 universal_newlines=newlines,
131 env=env)
131 env=env)
132 return p.stdin, p.stdout
132 return p.stdin, p.stdout
133
133
134 def popen3(cmd, env=None, newlines=False):
134 def popen3(cmd, env=None, newlines=False):
135 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
135 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
136 close_fds=closefds,
136 close_fds=closefds,
137 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
137 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
138 stderr=subprocess.PIPE,
138 stderr=subprocess.PIPE,
139 universal_newlines=newlines,
139 universal_newlines=newlines,
140 env=env)
140 env=env)
141 return p.stdin, p.stdout, p.stderr
141 return p.stdin, p.stdout, p.stderr
142
142
143 def version():
143 def version():
144 """Return version information if available."""
144 """Return version information if available."""
145 try:
145 try:
146 import __version__
146 import __version__
147 return __version__.version
147 return __version__.version
148 except ImportError:
148 except ImportError:
149 return 'unknown'
149 return 'unknown'
150
150
151 # used by parsedate
151 # used by parsedate
152 defaultdateformats = (
152 defaultdateformats = (
153 '%Y-%m-%d %H:%M:%S',
153 '%Y-%m-%d %H:%M:%S',
154 '%Y-%m-%d %I:%M:%S%p',
154 '%Y-%m-%d %I:%M:%S%p',
155 '%Y-%m-%d %H:%M',
155 '%Y-%m-%d %H:%M',
156 '%Y-%m-%d %I:%M%p',
156 '%Y-%m-%d %I:%M%p',
157 '%Y-%m-%d',
157 '%Y-%m-%d',
158 '%m-%d',
158 '%m-%d',
159 '%m/%d',
159 '%m/%d',
160 '%m/%d/%y',
160 '%m/%d/%y',
161 '%m/%d/%Y',
161 '%m/%d/%Y',
162 '%a %b %d %H:%M:%S %Y',
162 '%a %b %d %H:%M:%S %Y',
163 '%a %b %d %I:%M:%S%p %Y',
163 '%a %b %d %I:%M:%S%p %Y',
164 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
164 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
165 '%b %d %H:%M:%S %Y',
165 '%b %d %H:%M:%S %Y',
166 '%b %d %I:%M:%S%p %Y',
166 '%b %d %I:%M:%S%p %Y',
167 '%b %d %H:%M:%S',
167 '%b %d %H:%M:%S',
168 '%b %d %I:%M:%S%p',
168 '%b %d %I:%M:%S%p',
169 '%b %d %H:%M',
169 '%b %d %H:%M',
170 '%b %d %I:%M%p',
170 '%b %d %I:%M%p',
171 '%b %d %Y',
171 '%b %d %Y',
172 '%b %d',
172 '%b %d',
173 '%H:%M:%S',
173 '%H:%M:%S',
174 '%I:%M:%S%p',
174 '%I:%M:%S%p',
175 '%H:%M',
175 '%H:%M',
176 '%I:%M%p',
176 '%I:%M%p',
177 )
177 )
178
178
179 extendeddateformats = defaultdateformats + (
179 extendeddateformats = defaultdateformats + (
180 "%Y",
180 "%Y",
181 "%Y-%m",
181 "%Y-%m",
182 "%b",
182 "%b",
183 "%b %Y",
183 "%b %Y",
184 )
184 )
185
185
186 def cachefunc(func):
186 def cachefunc(func):
187 '''cache the result of function calls'''
187 '''cache the result of function calls'''
188 # XXX doesn't handle keywords args
188 # XXX doesn't handle keywords args
189 cache = {}
189 cache = {}
190 if func.func_code.co_argcount == 1:
190 if func.func_code.co_argcount == 1:
191 # we gain a small amount of time because
191 # we gain a small amount of time because
192 # we don't need to pack/unpack the list
192 # we don't need to pack/unpack the list
193 def f(arg):
193 def f(arg):
194 if arg not in cache:
194 if arg not in cache:
195 cache[arg] = func(arg)
195 cache[arg] = func(arg)
196 return cache[arg]
196 return cache[arg]
197 else:
197 else:
198 def f(*args):
198 def f(*args):
199 if args not in cache:
199 if args not in cache:
200 cache[args] = func(*args)
200 cache[args] = func(*args)
201 return cache[args]
201 return cache[args]
202
202
203 return f
203 return f
204
204
205 try:
205 try:
206 collections.deque.remove
206 collections.deque.remove
207 deque = collections.deque
207 deque = collections.deque
208 except AttributeError:
208 except AttributeError:
209 # python 2.4 lacks deque.remove
209 # python 2.4 lacks deque.remove
210 class deque(collections.deque):
210 class deque(collections.deque):
211 def remove(self, val):
211 def remove(self, val):
212 for i, v in enumerate(self):
212 for i, v in enumerate(self):
213 if v == val:
213 if v == val:
214 del self[i]
214 del self[i]
215 break
215 break
216
216
217 def lrucachefunc(func):
217 def lrucachefunc(func):
218 '''cache most recent results of function calls'''
218 '''cache most recent results of function calls'''
219 cache = {}
219 cache = {}
220 order = deque()
220 order = deque()
221 if func.func_code.co_argcount == 1:
221 if func.func_code.co_argcount == 1:
222 def f(arg):
222 def f(arg):
223 if arg not in cache:
223 if arg not in cache:
224 if len(cache) > 20:
224 if len(cache) > 20:
225 del cache[order.popleft()]
225 del cache[order.popleft()]
226 cache[arg] = func(arg)
226 cache[arg] = func(arg)
227 else:
227 else:
228 order.remove(arg)
228 order.remove(arg)
229 order.append(arg)
229 order.append(arg)
230 return cache[arg]
230 return cache[arg]
231 else:
231 else:
232 def f(*args):
232 def f(*args):
233 if args not in cache:
233 if args not in cache:
234 if len(cache) > 20:
234 if len(cache) > 20:
235 del cache[order.popleft()]
235 del cache[order.popleft()]
236 cache[args] = func(*args)
236 cache[args] = func(*args)
237 else:
237 else:
238 order.remove(args)
238 order.remove(args)
239 order.append(args)
239 order.append(args)
240 return cache[args]
240 return cache[args]
241
241
242 return f
242 return f
243
243
244 class propertycache(object):
244 class propertycache(object):
245 def __init__(self, func):
245 def __init__(self, func):
246 self.func = func
246 self.func = func
247 self.name = func.__name__
247 self.name = func.__name__
248 def __get__(self, obj, type=None):
248 def __get__(self, obj, type=None):
249 result = self.func(obj)
249 result = self.func(obj)
250 setattr(obj, self.name, result)
250 setattr(obj, self.name, result)
251 return result
251 return result
252
252
253 def pipefilter(s, cmd):
253 def pipefilter(s, cmd):
254 '''filter string S through command CMD, returning its output'''
254 '''filter string S through command CMD, returning its output'''
255 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
255 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
256 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
256 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
257 pout, perr = p.communicate(s)
257 pout, perr = p.communicate(s)
258 return pout
258 return pout
259
259
260 def tempfilter(s, cmd):
260 def tempfilter(s, cmd):
261 '''filter string S through a pair of temporary files with CMD.
261 '''filter string S through a pair of temporary files with CMD.
262 CMD is used as a template to create the real command to be run,
262 CMD is used as a template to create the real command to be run,
263 with the strings INFILE and OUTFILE replaced by the real names of
263 with the strings INFILE and OUTFILE replaced by the real names of
264 the temporary files generated.'''
264 the temporary files generated.'''
265 inname, outname = None, None
265 inname, outname = None, None
266 try:
266 try:
267 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
267 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
268 fp = os.fdopen(infd, 'wb')
268 fp = os.fdopen(infd, 'wb')
269 fp.write(s)
269 fp.write(s)
270 fp.close()
270 fp.close()
271 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
271 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
272 os.close(outfd)
272 os.close(outfd)
273 cmd = cmd.replace('INFILE', inname)
273 cmd = cmd.replace('INFILE', inname)
274 cmd = cmd.replace('OUTFILE', outname)
274 cmd = cmd.replace('OUTFILE', outname)
275 code = os.system(cmd)
275 code = os.system(cmd)
276 if sys.platform == 'OpenVMS' and code & 1:
276 if sys.platform == 'OpenVMS' and code & 1:
277 code = 0
277 code = 0
278 if code:
278 if code:
279 raise Abort(_("command '%s' failed: %s") %
279 raise Abort(_("command '%s' failed: %s") %
280 (cmd, explainexit(code)))
280 (cmd, explainexit(code)))
281 fp = open(outname, 'rb')
281 fp = open(outname, 'rb')
282 r = fp.read()
282 r = fp.read()
283 fp.close()
283 fp.close()
284 return r
284 return r
285 finally:
285 finally:
286 try:
286 try:
287 if inname:
287 if inname:
288 os.unlink(inname)
288 os.unlink(inname)
289 except OSError:
289 except OSError:
290 pass
290 pass
291 try:
291 try:
292 if outname:
292 if outname:
293 os.unlink(outname)
293 os.unlink(outname)
294 except OSError:
294 except OSError:
295 pass
295 pass
296
296
297 filtertable = {
297 filtertable = {
298 'tempfile:': tempfilter,
298 'tempfile:': tempfilter,
299 'pipe:': pipefilter,
299 'pipe:': pipefilter,
300 }
300 }
301
301
302 def filter(s, cmd):
302 def filter(s, cmd):
303 "filter a string through a command that transforms its input to its output"
303 "filter a string through a command that transforms its input to its output"
304 for name, fn in filtertable.iteritems():
304 for name, fn in filtertable.iteritems():
305 if cmd.startswith(name):
305 if cmd.startswith(name):
306 return fn(s, cmd[len(name):].lstrip())
306 return fn(s, cmd[len(name):].lstrip())
307 return pipefilter(s, cmd)
307 return pipefilter(s, cmd)
308
308
309 def binary(s):
309 def binary(s):
310 """return true if a string is binary data"""
310 """return true if a string is binary data"""
311 return bool(s and '\0' in s)
311 return bool(s and '\0' in s)
312
312
313 def increasingchunks(source, min=1024, max=65536):
313 def increasingchunks(source, min=1024, max=65536):
314 '''return no less than min bytes per chunk while data remains,
314 '''return no less than min bytes per chunk while data remains,
315 doubling min after each chunk until it reaches max'''
315 doubling min after each chunk until it reaches max'''
316 def log2(x):
316 def log2(x):
317 if not x:
317 if not x:
318 return 0
318 return 0
319 i = 0
319 i = 0
320 while x:
320 while x:
321 x >>= 1
321 x >>= 1
322 i += 1
322 i += 1
323 return i - 1
323 return i - 1
324
324
325 buf = []
325 buf = []
326 blen = 0
326 blen = 0
327 for chunk in source:
327 for chunk in source:
328 buf.append(chunk)
328 buf.append(chunk)
329 blen += len(chunk)
329 blen += len(chunk)
330 if blen >= min:
330 if blen >= min:
331 if min < max:
331 if min < max:
332 min = min << 1
332 min = min << 1
333 nmin = 1 << log2(blen)
333 nmin = 1 << log2(blen)
334 if nmin > min:
334 if nmin > min:
335 min = nmin
335 min = nmin
336 if min > max:
336 if min > max:
337 min = max
337 min = max
338 yield ''.join(buf)
338 yield ''.join(buf)
339 blen = 0
339 blen = 0
340 buf = []
340 buf = []
341 if buf:
341 if buf:
342 yield ''.join(buf)
342 yield ''.join(buf)
343
343
344 Abort = error.Abort
344 Abort = error.Abort
345
345
346 def always(fn):
346 def always(fn):
347 return True
347 return True
348
348
349 def never(fn):
349 def never(fn):
350 return False
350 return False
351
351
352 def pathto(root, n1, n2):
352 def pathto(root, n1, n2):
353 '''return the relative path from one place to another.
353 '''return the relative path from one place to another.
354 root should use os.sep to separate directories
354 root should use os.sep to separate directories
355 n1 should use os.sep to separate directories
355 n1 should use os.sep to separate directories
356 n2 should use "/" to separate directories
356 n2 should use "/" to separate directories
357 returns an os.sep-separated path.
357 returns an os.sep-separated path.
358
358
359 If n1 is a relative path, it's assumed it's
359 If n1 is a relative path, it's assumed it's
360 relative to root.
360 relative to root.
361 n2 should always be relative to root.
361 n2 should always be relative to root.
362 '''
362 '''
363 if not n1:
363 if not n1:
364 return localpath(n2)
364 return localpath(n2)
365 if os.path.isabs(n1):
365 if os.path.isabs(n1):
366 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
366 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
367 return os.path.join(root, localpath(n2))
367 return os.path.join(root, localpath(n2))
368 n2 = '/'.join((pconvert(root), n2))
368 n2 = '/'.join((pconvert(root), n2))
369 a, b = splitpath(n1), n2.split('/')
369 a, b = splitpath(n1), n2.split('/')
370 a.reverse()
370 a.reverse()
371 b.reverse()
371 b.reverse()
372 while a and b and a[-1] == b[-1]:
372 while a and b and a[-1] == b[-1]:
373 a.pop()
373 a.pop()
374 b.pop()
374 b.pop()
375 b.reverse()
375 b.reverse()
376 return os.sep.join((['..'] * len(a)) + b) or '.'
376 return os.sep.join((['..'] * len(a)) + b) or '.'
377
377
378 _hgexecutable = None
378 _hgexecutable = None
379
379
380 def mainfrozen():
380 def mainfrozen():
381 """return True if we are a frozen executable.
381 """return True if we are a frozen executable.
382
382
383 The code supports py2exe (most common, Windows only) and tools/freeze
383 The code supports py2exe (most common, Windows only) and tools/freeze
384 (portable, not much used).
384 (portable, not much used).
385 """
385 """
386 return (safehasattr(sys, "frozen") or # new py2exe
386 return (safehasattr(sys, "frozen") or # new py2exe
387 safehasattr(sys, "importers") or # old py2exe
387 safehasattr(sys, "importers") or # old py2exe
388 imp.is_frozen("__main__")) # tools/freeze
388 imp.is_frozen("__main__")) # tools/freeze
389
389
390 def hgexecutable():
390 def hgexecutable():
391 """return location of the 'hg' executable.
391 """return location of the 'hg' executable.
392
392
393 Defaults to $HG or 'hg' in the search path.
393 Defaults to $HG or 'hg' in the search path.
394 """
394 """
395 if _hgexecutable is None:
395 if _hgexecutable is None:
396 hg = os.environ.get('HG')
396 hg = os.environ.get('HG')
397 mainmod = sys.modules['__main__']
397 mainmod = sys.modules['__main__']
398 if hg:
398 if hg:
399 _sethgexecutable(hg)
399 _sethgexecutable(hg)
400 elif mainfrozen():
400 elif mainfrozen():
401 _sethgexecutable(sys.executable)
401 _sethgexecutable(sys.executable)
402 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
402 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
403 _sethgexecutable(mainmod.__file__)
403 _sethgexecutable(mainmod.__file__)
404 else:
404 else:
405 exe = findexe('hg') or os.path.basename(sys.argv[0])
405 exe = findexe('hg') or os.path.basename(sys.argv[0])
406 _sethgexecutable(exe)
406 _sethgexecutable(exe)
407 return _hgexecutable
407 return _hgexecutable
408
408
409 def _sethgexecutable(path):
409 def _sethgexecutable(path):
410 """set location of the 'hg' executable"""
410 """set location of the 'hg' executable"""
411 global _hgexecutable
411 global _hgexecutable
412 _hgexecutable = path
412 _hgexecutable = path
413
413
414 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
414 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
415 '''enhanced shell command execution.
415 '''enhanced shell command execution.
416 run with environment maybe modified, maybe in different dir.
416 run with environment maybe modified, maybe in different dir.
417
417
418 if command fails and onerr is None, return status. if ui object,
418 if command fails and onerr is None, return status. if ui object,
419 print error message and return status, else raise onerr object as
419 print error message and return status, else raise onerr object as
420 exception.
420 exception.
421
421
422 if out is specified, it is assumed to be a file-like object that has a
422 if out is specified, it is assumed to be a file-like object that has a
423 write() method. stdout and stderr will be redirected to out.'''
423 write() method. stdout and stderr will be redirected to out.'''
424 try:
424 try:
425 sys.stdout.flush()
425 sys.stdout.flush()
426 except Exception:
426 except Exception:
427 pass
427 pass
428 def py2shell(val):
428 def py2shell(val):
429 'convert python object into string that is useful to shell'
429 'convert python object into string that is useful to shell'
430 if val is None or val is False:
430 if val is None or val is False:
431 return '0'
431 return '0'
432 if val is True:
432 if val is True:
433 return '1'
433 return '1'
434 return str(val)
434 return str(val)
435 origcmd = cmd
435 origcmd = cmd
436 cmd = quotecommand(cmd)
436 cmd = quotecommand(cmd)
437 if sys.platform == 'plan9':
437 if sys.platform == 'plan9':
438 # subprocess kludge to work around issues in half-baked Python
438 # subprocess kludge to work around issues in half-baked Python
439 # ports, notably bichued/python:
439 # ports, notably bichued/python:
440 if not cwd is None:
440 if not cwd is None:
441 os.chdir(cwd)
441 os.chdir(cwd)
442 rc = os.system(cmd)
442 rc = os.system(cmd)
443 else:
443 else:
444 env = dict(os.environ)
444 env = dict(os.environ)
445 env.update((k, py2shell(v)) for k, v in environ.iteritems())
445 env.update((k, py2shell(v)) for k, v in environ.iteritems())
446 env['HG'] = hgexecutable()
446 env['HG'] = hgexecutable()
447 if out is None or out == sys.__stdout__:
447 if out is None or out == sys.__stdout__:
448 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
448 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
449 env=env, cwd=cwd)
449 env=env, cwd=cwd)
450 else:
450 else:
451 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
451 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
452 env=env, cwd=cwd, stdout=subprocess.PIPE,
452 env=env, cwd=cwd, stdout=subprocess.PIPE,
453 stderr=subprocess.STDOUT)
453 stderr=subprocess.STDOUT)
454 for line in proc.stdout:
454 for line in proc.stdout:
455 out.write(line)
455 out.write(line)
456 proc.wait()
456 proc.wait()
457 rc = proc.returncode
457 rc = proc.returncode
458 if sys.platform == 'OpenVMS' and rc & 1:
458 if sys.platform == 'OpenVMS' and rc & 1:
459 rc = 0
459 rc = 0
460 if rc and onerr:
460 if rc and onerr:
461 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
461 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
462 explainexit(rc)[0])
462 explainexit(rc)[0])
463 if errprefix:
463 if errprefix:
464 errmsg = '%s: %s' % (errprefix, errmsg)
464 errmsg = '%s: %s' % (errprefix, errmsg)
465 try:
465 try:
466 onerr.warn(errmsg + '\n')
466 onerr.warn(errmsg + '\n')
467 except AttributeError:
467 except AttributeError:
468 raise onerr(errmsg)
468 raise onerr(errmsg)
469 return rc
469 return rc
470
470
471 def checksignature(func):
471 def checksignature(func):
472 '''wrap a function with code to check for calling errors'''
472 '''wrap a function with code to check for calling errors'''
473 def check(*args, **kwargs):
473 def check(*args, **kwargs):
474 try:
474 try:
475 return func(*args, **kwargs)
475 return func(*args, **kwargs)
476 except TypeError:
476 except TypeError:
477 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
477 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
478 raise error.SignatureError
478 raise error.SignatureError
479 raise
479 raise
480
480
481 return check
481 return check
482
482
483 def copyfile(src, dest):
483 def copyfile(src, dest):
484 "copy a file, preserving mode and atime/mtime"
484 "copy a file, preserving mode and atime/mtime"
485 if os.path.islink(src):
485 if os.path.islink(src):
486 try:
486 try:
487 os.unlink(dest)
487 os.unlink(dest)
488 except OSError:
488 except OSError:
489 pass
489 pass
490 os.symlink(os.readlink(src), dest)
490 os.symlink(os.readlink(src), dest)
491 else:
491 else:
492 try:
492 try:
493 shutil.copyfile(src, dest)
493 shutil.copyfile(src, dest)
494 shutil.copymode(src, dest)
494 shutil.copymode(src, dest)
495 except shutil.Error, inst:
495 except shutil.Error, inst:
496 raise Abort(str(inst))
496 raise Abort(str(inst))
497
497
498 def copyfiles(src, dst, hardlink=None):
498 def copyfiles(src, dst, hardlink=None):
499 """Copy a directory tree using hardlinks if possible"""
499 """Copy a directory tree using hardlinks if possible"""
500
500
501 if hardlink is None:
501 if hardlink is None:
502 hardlink = (os.stat(src).st_dev ==
502 hardlink = (os.stat(src).st_dev ==
503 os.stat(os.path.dirname(dst)).st_dev)
503 os.stat(os.path.dirname(dst)).st_dev)
504
504
505 num = 0
505 num = 0
506 if os.path.isdir(src):
506 if os.path.isdir(src):
507 os.mkdir(dst)
507 os.mkdir(dst)
508 for name, kind in osutil.listdir(src):
508 for name, kind in osutil.listdir(src):
509 srcname = os.path.join(src, name)
509 srcname = os.path.join(src, name)
510 dstname = os.path.join(dst, name)
510 dstname = os.path.join(dst, name)
511 hardlink, n = copyfiles(srcname, dstname, hardlink)
511 hardlink, n = copyfiles(srcname, dstname, hardlink)
512 num += n
512 num += n
513 else:
513 else:
514 if hardlink:
514 if hardlink:
515 try:
515 try:
516 oslink(src, dst)
516 oslink(src, dst)
517 except (IOError, OSError):
517 except (IOError, OSError):
518 hardlink = False
518 hardlink = False
519 shutil.copy(src, dst)
519 shutil.copy(src, dst)
520 else:
520 else:
521 shutil.copy(src, dst)
521 shutil.copy(src, dst)
522 num += 1
522 num += 1
523
523
524 return hardlink, num
524 return hardlink, num
525
525
526 _winreservednames = '''con prn aux nul
526 _winreservednames = '''con prn aux nul
527 com1 com2 com3 com4 com5 com6 com7 com8 com9
527 com1 com2 com3 com4 com5 com6 com7 com8 com9
528 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
528 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
529 _winreservedchars = ':*?"<>|'
529 _winreservedchars = ':*?"<>|'
530 def checkwinfilename(path):
530 def checkwinfilename(path):
531 '''Check that the base-relative path is a valid filename on Windows.
531 '''Check that the base-relative path is a valid filename on Windows.
532 Returns None if the path is ok, or a UI string describing the problem.
532 Returns None if the path is ok, or a UI string describing the problem.
533
533
534 >>> checkwinfilename("just/a/normal/path")
534 >>> checkwinfilename("just/a/normal/path")
535 >>> checkwinfilename("foo/bar/con.xml")
535 >>> checkwinfilename("foo/bar/con.xml")
536 "filename contains 'con', which is reserved on Windows"
536 "filename contains 'con', which is reserved on Windows"
537 >>> checkwinfilename("foo/con.xml/bar")
537 >>> checkwinfilename("foo/con.xml/bar")
538 "filename contains 'con', which is reserved on Windows"
538 "filename contains 'con', which is reserved on Windows"
539 >>> checkwinfilename("foo/bar/xml.con")
539 >>> checkwinfilename("foo/bar/xml.con")
540 >>> checkwinfilename("foo/bar/AUX/bla.txt")
540 >>> checkwinfilename("foo/bar/AUX/bla.txt")
541 "filename contains 'AUX', which is reserved on Windows"
541 "filename contains 'AUX', which is reserved on Windows"
542 >>> checkwinfilename("foo/bar/bla:.txt")
542 >>> checkwinfilename("foo/bar/bla:.txt")
543 "filename contains ':', which is reserved on Windows"
543 "filename contains ':', which is reserved on Windows"
544 >>> checkwinfilename("foo/bar/b\07la.txt")
544 >>> checkwinfilename("foo/bar/b\07la.txt")
545 "filename contains '\\\\x07', which is invalid on Windows"
545 "filename contains '\\\\x07', which is invalid on Windows"
546 >>> checkwinfilename("foo/bar/bla ")
546 >>> checkwinfilename("foo/bar/bla ")
547 "filename ends with ' ', which is not allowed on Windows"
547 "filename ends with ' ', which is not allowed on Windows"
548 >>> checkwinfilename("../bar")
548 >>> checkwinfilename("../bar")
549 '''
549 '''
550 for n in path.replace('\\', '/').split('/'):
550 for n in path.replace('\\', '/').split('/'):
551 if not n:
551 if not n:
552 continue
552 continue
553 for c in n:
553 for c in n:
554 if c in _winreservedchars:
554 if c in _winreservedchars:
555 return _("filename contains '%s', which is reserved "
555 return _("filename contains '%s', which is reserved "
556 "on Windows") % c
556 "on Windows") % c
557 if ord(c) <= 31:
557 if ord(c) <= 31:
558 return _("filename contains %r, which is invalid "
558 return _("filename contains %r, which is invalid "
559 "on Windows") % c
559 "on Windows") % c
560 base = n.split('.')[0]
560 base = n.split('.')[0]
561 if base and base.lower() in _winreservednames:
561 if base and base.lower() in _winreservednames:
562 return _("filename contains '%s', which is reserved "
562 return _("filename contains '%s', which is reserved "
563 "on Windows") % base
563 "on Windows") % base
564 t = n[-1]
564 t = n[-1]
565 if t in '. ' and n not in '..':
565 if t in '. ' and n not in '..':
566 return _("filename ends with '%s', which is not allowed "
566 return _("filename ends with '%s', which is not allowed "
567 "on Windows") % t
567 "on Windows") % t
568
568
569 if os.name == 'nt':
569 if os.name == 'nt':
570 checkosfilename = checkwinfilename
570 checkosfilename = checkwinfilename
571 else:
571 else:
572 checkosfilename = platform.checkosfilename
572 checkosfilename = platform.checkosfilename
573
573
574 def makelock(info, pathname):
574 def makelock(info, pathname):
575 try:
575 try:
576 return os.symlink(info, pathname)
576 return os.symlink(info, pathname)
577 except OSError, why:
577 except OSError, why:
578 if why.errno == errno.EEXIST:
578 if why.errno == errno.EEXIST:
579 raise
579 raise
580 except AttributeError: # no symlink in os
580 except AttributeError: # no symlink in os
581 pass
581 pass
582
582
583 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
583 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
584 os.write(ld, info)
584 os.write(ld, info)
585 os.close(ld)
585 os.close(ld)
586
586
587 def readlock(pathname):
587 def readlock(pathname):
588 try:
588 try:
589 return os.readlink(pathname)
589 return os.readlink(pathname)
590 except OSError, why:
590 except OSError, why:
591 if why.errno not in (errno.EINVAL, errno.ENOSYS):
591 if why.errno not in (errno.EINVAL, errno.ENOSYS):
592 raise
592 raise
593 except AttributeError: # no symlink in os
593 except AttributeError: # no symlink in os
594 pass
594 pass
595 fp = posixfile(pathname)
595 fp = posixfile(pathname)
596 r = fp.read()
596 r = fp.read()
597 fp.close()
597 fp.close()
598 return r
598 return r
599
599
600 def fstat(fp):
600 def fstat(fp):
601 '''stat file object that may not have fileno method.'''
601 '''stat file object that may not have fileno method.'''
602 try:
602 try:
603 return os.fstat(fp.fileno())
603 return os.fstat(fp.fileno())
604 except AttributeError:
604 except AttributeError:
605 return os.stat(fp.name)
605 return os.stat(fp.name)
606
606
607 # File system features
607 # File system features
608
608
609 def checkcase(path):
609 def checkcase(path):
610 """
610 """
611 Check whether the given path is on a case-sensitive filesystem
611 Check whether the given path is on a case-sensitive filesystem
612
612
613 Requires a path (like /foo/.hg) ending with a foldable final
613 Requires a path (like /foo/.hg) ending with a foldable final
614 directory component.
614 directory component.
615 """
615 """
616 s1 = os.stat(path)
616 s1 = os.stat(path)
617 d, b = os.path.split(path)
617 d, b = os.path.split(path)
618 b2 = b.upper()
618 b2 = b.upper()
619 if b == b2:
619 if b == b2:
620 b2 = b.lower()
620 b2 = b.lower()
621 if b == b2:
621 if b == b2:
622 return True # no evidence against case sensitivity
622 return True # no evidence against case sensitivity
623 p2 = os.path.join(d, b2)
623 p2 = os.path.join(d, b2)
624 try:
624 try:
625 s2 = os.stat(p2)
625 s2 = os.stat(p2)
626 if s2 == s1:
626 if s2 == s1:
627 return False
627 return False
628 return True
628 return True
629 except OSError:
629 except OSError:
630 return True
630 return True
631
631
632 try:
633 import re2
634 _re2 = None
635 except ImportError:
636 _re2 = False
637
638 def compilere(pat):
639 '''Compile a regular expression, using re2 if possible
640
641 For best performance, use only re2-compatible regexp features.'''
642 global _re2
643 if _re2 is None:
644 try:
645 re2.compile
646 _re2 = True
647 except ImportError:
648 _re2 = False
649 if _re2:
650 try:
651 return re2.compile(pat)
652 except re2.error:
653 pass
654 return re.compile(pat)
655
632 _fspathcache = {}
656 _fspathcache = {}
633 def fspath(name, root):
657 def fspath(name, root):
634 '''Get name in the case stored in the filesystem
658 '''Get name in the case stored in the filesystem
635
659
636 The name should be relative to root, and be normcase-ed for efficiency.
660 The name should be relative to root, and be normcase-ed for efficiency.
637
661
638 Note that this function is unnecessary, and should not be
662 Note that this function is unnecessary, and should not be
639 called, for case-sensitive filesystems (simply because it's expensive).
663 called, for case-sensitive filesystems (simply because it's expensive).
640
664
641 The root should be normcase-ed, too.
665 The root should be normcase-ed, too.
642 '''
666 '''
643 def find(p, contents):
667 def find(p, contents):
644 for n in contents:
668 for n in contents:
645 if normcase(n) == p:
669 if normcase(n) == p:
646 return n
670 return n
647 return None
671 return None
648
672
649 seps = os.sep
673 seps = os.sep
650 if os.altsep:
674 if os.altsep:
651 seps = seps + os.altsep
675 seps = seps + os.altsep
652 # Protect backslashes. This gets silly very quickly.
676 # Protect backslashes. This gets silly very quickly.
653 seps.replace('\\','\\\\')
677 seps.replace('\\','\\\\')
654 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
678 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
655 dir = os.path.normpath(root)
679 dir = os.path.normpath(root)
656 result = []
680 result = []
657 for part, sep in pattern.findall(name):
681 for part, sep in pattern.findall(name):
658 if sep:
682 if sep:
659 result.append(sep)
683 result.append(sep)
660 continue
684 continue
661
685
662 if dir not in _fspathcache:
686 if dir not in _fspathcache:
663 _fspathcache[dir] = os.listdir(dir)
687 _fspathcache[dir] = os.listdir(dir)
664 contents = _fspathcache[dir]
688 contents = _fspathcache[dir]
665
689
666 found = find(part, contents)
690 found = find(part, contents)
667 if not found:
691 if not found:
668 # retry "once per directory" per "dirstate.walk" which
692 # retry "once per directory" per "dirstate.walk" which
669 # may take place for each patches of "hg qpush", for example
693 # may take place for each patches of "hg qpush", for example
670 contents = os.listdir(dir)
694 contents = os.listdir(dir)
671 _fspathcache[dir] = contents
695 _fspathcache[dir] = contents
672 found = find(part, contents)
696 found = find(part, contents)
673
697
674 result.append(found or part)
698 result.append(found or part)
675 dir = os.path.join(dir, part)
699 dir = os.path.join(dir, part)
676
700
677 return ''.join(result)
701 return ''.join(result)
678
702
679 def checknlink(testfile):
703 def checknlink(testfile):
680 '''check whether hardlink count reporting works properly'''
704 '''check whether hardlink count reporting works properly'''
681
705
682 # testfile may be open, so we need a separate file for checking to
706 # testfile may be open, so we need a separate file for checking to
683 # work around issue2543 (or testfile may get lost on Samba shares)
707 # work around issue2543 (or testfile may get lost on Samba shares)
684 f1 = testfile + ".hgtmp1"
708 f1 = testfile + ".hgtmp1"
685 if os.path.lexists(f1):
709 if os.path.lexists(f1):
686 return False
710 return False
687 try:
711 try:
688 posixfile(f1, 'w').close()
712 posixfile(f1, 'w').close()
689 except IOError:
713 except IOError:
690 return False
714 return False
691
715
692 f2 = testfile + ".hgtmp2"
716 f2 = testfile + ".hgtmp2"
693 fd = None
717 fd = None
694 try:
718 try:
695 try:
719 try:
696 oslink(f1, f2)
720 oslink(f1, f2)
697 except OSError:
721 except OSError:
698 return False
722 return False
699
723
700 # nlinks() may behave differently for files on Windows shares if
724 # nlinks() may behave differently for files on Windows shares if
701 # the file is open.
725 # the file is open.
702 fd = posixfile(f2)
726 fd = posixfile(f2)
703 return nlinks(f2) > 1
727 return nlinks(f2) > 1
704 finally:
728 finally:
705 if fd is not None:
729 if fd is not None:
706 fd.close()
730 fd.close()
707 for f in (f1, f2):
731 for f in (f1, f2):
708 try:
732 try:
709 os.unlink(f)
733 os.unlink(f)
710 except OSError:
734 except OSError:
711 pass
735 pass
712
736
713 return False
737 return False
714
738
715 def endswithsep(path):
739 def endswithsep(path):
716 '''Check path ends with os.sep or os.altsep.'''
740 '''Check path ends with os.sep or os.altsep.'''
717 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
741 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
718
742
719 def splitpath(path):
743 def splitpath(path):
720 '''Split path by os.sep.
744 '''Split path by os.sep.
721 Note that this function does not use os.altsep because this is
745 Note that this function does not use os.altsep because this is
722 an alternative of simple "xxx.split(os.sep)".
746 an alternative of simple "xxx.split(os.sep)".
723 It is recommended to use os.path.normpath() before using this
747 It is recommended to use os.path.normpath() before using this
724 function if need.'''
748 function if need.'''
725 return path.split(os.sep)
749 return path.split(os.sep)
726
750
727 def gui():
751 def gui():
728 '''Are we running in a GUI?'''
752 '''Are we running in a GUI?'''
729 if sys.platform == 'darwin':
753 if sys.platform == 'darwin':
730 if 'SSH_CONNECTION' in os.environ:
754 if 'SSH_CONNECTION' in os.environ:
731 # handle SSH access to a box where the user is logged in
755 # handle SSH access to a box where the user is logged in
732 return False
756 return False
733 elif getattr(osutil, 'isgui', None):
757 elif getattr(osutil, 'isgui', None):
734 # check if a CoreGraphics session is available
758 # check if a CoreGraphics session is available
735 return osutil.isgui()
759 return osutil.isgui()
736 else:
760 else:
737 # pure build; use a safe default
761 # pure build; use a safe default
738 return True
762 return True
739 else:
763 else:
740 return os.name == "nt" or os.environ.get("DISPLAY")
764 return os.name == "nt" or os.environ.get("DISPLAY")
741
765
742 def mktempcopy(name, emptyok=False, createmode=None):
766 def mktempcopy(name, emptyok=False, createmode=None):
743 """Create a temporary file with the same contents from name
767 """Create a temporary file with the same contents from name
744
768
745 The permission bits are copied from the original file.
769 The permission bits are copied from the original file.
746
770
747 If the temporary file is going to be truncated immediately, you
771 If the temporary file is going to be truncated immediately, you
748 can use emptyok=True as an optimization.
772 can use emptyok=True as an optimization.
749
773
750 Returns the name of the temporary file.
774 Returns the name of the temporary file.
751 """
775 """
752 d, fn = os.path.split(name)
776 d, fn = os.path.split(name)
753 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
777 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
754 os.close(fd)
778 os.close(fd)
755 # Temporary files are created with mode 0600, which is usually not
779 # Temporary files are created with mode 0600, which is usually not
756 # what we want. If the original file already exists, just copy
780 # what we want. If the original file already exists, just copy
757 # its mode. Otherwise, manually obey umask.
781 # its mode. Otherwise, manually obey umask.
758 copymode(name, temp, createmode)
782 copymode(name, temp, createmode)
759 if emptyok:
783 if emptyok:
760 return temp
784 return temp
761 try:
785 try:
762 try:
786 try:
763 ifp = posixfile(name, "rb")
787 ifp = posixfile(name, "rb")
764 except IOError, inst:
788 except IOError, inst:
765 if inst.errno == errno.ENOENT:
789 if inst.errno == errno.ENOENT:
766 return temp
790 return temp
767 if not getattr(inst, 'filename', None):
791 if not getattr(inst, 'filename', None):
768 inst.filename = name
792 inst.filename = name
769 raise
793 raise
770 ofp = posixfile(temp, "wb")
794 ofp = posixfile(temp, "wb")
771 for chunk in filechunkiter(ifp):
795 for chunk in filechunkiter(ifp):
772 ofp.write(chunk)
796 ofp.write(chunk)
773 ifp.close()
797 ifp.close()
774 ofp.close()
798 ofp.close()
775 except: # re-raises
799 except: # re-raises
776 try: os.unlink(temp)
800 try: os.unlink(temp)
777 except OSError: pass
801 except OSError: pass
778 raise
802 raise
779 return temp
803 return temp
780
804
781 class atomictempfile(object):
805 class atomictempfile(object):
782 '''writeable file object that atomically updates a file
806 '''writeable file object that atomically updates a file
783
807
784 All writes will go to a temporary copy of the original file. Call
808 All writes will go to a temporary copy of the original file. Call
785 close() when you are done writing, and atomictempfile will rename
809 close() when you are done writing, and atomictempfile will rename
786 the temporary copy to the original name, making the changes
810 the temporary copy to the original name, making the changes
787 visible. If the object is destroyed without being closed, all your
811 visible. If the object is destroyed without being closed, all your
788 writes are discarded.
812 writes are discarded.
789 '''
813 '''
790 def __init__(self, name, mode='w+b', createmode=None):
814 def __init__(self, name, mode='w+b', createmode=None):
791 self.__name = name # permanent name
815 self.__name = name # permanent name
792 self._tempname = mktempcopy(name, emptyok=('w' in mode),
816 self._tempname = mktempcopy(name, emptyok=('w' in mode),
793 createmode=createmode)
817 createmode=createmode)
794 self._fp = posixfile(self._tempname, mode)
818 self._fp = posixfile(self._tempname, mode)
795
819
796 # delegated methods
820 # delegated methods
797 self.write = self._fp.write
821 self.write = self._fp.write
798 self.fileno = self._fp.fileno
822 self.fileno = self._fp.fileno
799
823
800 def close(self):
824 def close(self):
801 if not self._fp.closed:
825 if not self._fp.closed:
802 self._fp.close()
826 self._fp.close()
803 rename(self._tempname, localpath(self.__name))
827 rename(self._tempname, localpath(self.__name))
804
828
805 def discard(self):
829 def discard(self):
806 if not self._fp.closed:
830 if not self._fp.closed:
807 try:
831 try:
808 os.unlink(self._tempname)
832 os.unlink(self._tempname)
809 except OSError:
833 except OSError:
810 pass
834 pass
811 self._fp.close()
835 self._fp.close()
812
836
813 def __del__(self):
837 def __del__(self):
814 if safehasattr(self, '_fp'): # constructor actually did something
838 if safehasattr(self, '_fp'): # constructor actually did something
815 self.discard()
839 self.discard()
816
840
817 def makedirs(name, mode=None):
841 def makedirs(name, mode=None):
818 """recursive directory creation with parent mode inheritance"""
842 """recursive directory creation with parent mode inheritance"""
819 try:
843 try:
820 os.mkdir(name)
844 os.mkdir(name)
821 except OSError, err:
845 except OSError, err:
822 if err.errno == errno.EEXIST:
846 if err.errno == errno.EEXIST:
823 return
847 return
824 if err.errno != errno.ENOENT or not name:
848 if err.errno != errno.ENOENT or not name:
825 raise
849 raise
826 parent = os.path.dirname(os.path.abspath(name))
850 parent = os.path.dirname(os.path.abspath(name))
827 if parent == name:
851 if parent == name:
828 raise
852 raise
829 makedirs(parent, mode)
853 makedirs(parent, mode)
830 os.mkdir(name)
854 os.mkdir(name)
831 if mode is not None:
855 if mode is not None:
832 os.chmod(name, mode)
856 os.chmod(name, mode)
833
857
834 def readfile(path):
858 def readfile(path):
835 fp = open(path, 'rb')
859 fp = open(path, 'rb')
836 try:
860 try:
837 return fp.read()
861 return fp.read()
838 finally:
862 finally:
839 fp.close()
863 fp.close()
840
864
841 def writefile(path, text):
865 def writefile(path, text):
842 fp = open(path, 'wb')
866 fp = open(path, 'wb')
843 try:
867 try:
844 fp.write(text)
868 fp.write(text)
845 finally:
869 finally:
846 fp.close()
870 fp.close()
847
871
848 def appendfile(path, text):
872 def appendfile(path, text):
849 fp = open(path, 'ab')
873 fp = open(path, 'ab')
850 try:
874 try:
851 fp.write(text)
875 fp.write(text)
852 finally:
876 finally:
853 fp.close()
877 fp.close()
854
878
855 class chunkbuffer(object):
879 class chunkbuffer(object):
856 """Allow arbitrary sized chunks of data to be efficiently read from an
880 """Allow arbitrary sized chunks of data to be efficiently read from an
857 iterator over chunks of arbitrary size."""
881 iterator over chunks of arbitrary size."""
858
882
859 def __init__(self, in_iter):
883 def __init__(self, in_iter):
860 """in_iter is the iterator that's iterating over the input chunks.
884 """in_iter is the iterator that's iterating over the input chunks.
861 targetsize is how big a buffer to try to maintain."""
885 targetsize is how big a buffer to try to maintain."""
862 def splitbig(chunks):
886 def splitbig(chunks):
863 for chunk in chunks:
887 for chunk in chunks:
864 if len(chunk) > 2**20:
888 if len(chunk) > 2**20:
865 pos = 0
889 pos = 0
866 while pos < len(chunk):
890 while pos < len(chunk):
867 end = pos + 2 ** 18
891 end = pos + 2 ** 18
868 yield chunk[pos:end]
892 yield chunk[pos:end]
869 pos = end
893 pos = end
870 else:
894 else:
871 yield chunk
895 yield chunk
872 self.iter = splitbig(in_iter)
896 self.iter = splitbig(in_iter)
873 self._queue = deque()
897 self._queue = deque()
874
898
875 def read(self, l):
899 def read(self, l):
876 """Read L bytes of data from the iterator of chunks of data.
900 """Read L bytes of data from the iterator of chunks of data.
877 Returns less than L bytes if the iterator runs dry."""
901 Returns less than L bytes if the iterator runs dry."""
878 left = l
902 left = l
879 buf = ''
903 buf = ''
880 queue = self._queue
904 queue = self._queue
881 while left > 0:
905 while left > 0:
882 # refill the queue
906 # refill the queue
883 if not queue:
907 if not queue:
884 target = 2**18
908 target = 2**18
885 for chunk in self.iter:
909 for chunk in self.iter:
886 queue.append(chunk)
910 queue.append(chunk)
887 target -= len(chunk)
911 target -= len(chunk)
888 if target <= 0:
912 if target <= 0:
889 break
913 break
890 if not queue:
914 if not queue:
891 break
915 break
892
916
893 chunk = queue.popleft()
917 chunk = queue.popleft()
894 left -= len(chunk)
918 left -= len(chunk)
895 if left < 0:
919 if left < 0:
896 queue.appendleft(chunk[left:])
920 queue.appendleft(chunk[left:])
897 buf += chunk[:left]
921 buf += chunk[:left]
898 else:
922 else:
899 buf += chunk
923 buf += chunk
900
924
901 return buf
925 return buf
902
926
903 def filechunkiter(f, size=65536, limit=None):
927 def filechunkiter(f, size=65536, limit=None):
904 """Create a generator that produces the data in the file size
928 """Create a generator that produces the data in the file size
905 (default 65536) bytes at a time, up to optional limit (default is
929 (default 65536) bytes at a time, up to optional limit (default is
906 to read all data). Chunks may be less than size bytes if the
930 to read all data). Chunks may be less than size bytes if the
907 chunk is the last chunk in the file, or the file is a socket or
931 chunk is the last chunk in the file, or the file is a socket or
908 some other type of file that sometimes reads less data than is
932 some other type of file that sometimes reads less data than is
909 requested."""
933 requested."""
910 assert size >= 0
934 assert size >= 0
911 assert limit is None or limit >= 0
935 assert limit is None or limit >= 0
912 while True:
936 while True:
913 if limit is None:
937 if limit is None:
914 nbytes = size
938 nbytes = size
915 else:
939 else:
916 nbytes = min(limit, size)
940 nbytes = min(limit, size)
917 s = nbytes and f.read(nbytes)
941 s = nbytes and f.read(nbytes)
918 if not s:
942 if not s:
919 break
943 break
920 if limit:
944 if limit:
921 limit -= len(s)
945 limit -= len(s)
922 yield s
946 yield s
923
947
924 def makedate():
948 def makedate():
925 ct = time.time()
949 ct = time.time()
926 if ct < 0:
950 if ct < 0:
927 hint = _("check your clock")
951 hint = _("check your clock")
928 raise Abort(_("negative timestamp: %d") % ct, hint=hint)
952 raise Abort(_("negative timestamp: %d") % ct, hint=hint)
929 delta = (datetime.datetime.utcfromtimestamp(ct) -
953 delta = (datetime.datetime.utcfromtimestamp(ct) -
930 datetime.datetime.fromtimestamp(ct))
954 datetime.datetime.fromtimestamp(ct))
931 tz = delta.days * 86400 + delta.seconds
955 tz = delta.days * 86400 + delta.seconds
932 return ct, tz
956 return ct, tz
933
957
934 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
958 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
935 """represent a (unixtime, offset) tuple as a localized time.
959 """represent a (unixtime, offset) tuple as a localized time.
936 unixtime is seconds since the epoch, and offset is the time zone's
960 unixtime is seconds since the epoch, and offset is the time zone's
937 number of seconds away from UTC. if timezone is false, do not
961 number of seconds away from UTC. if timezone is false, do not
938 append time zone to string."""
962 append time zone to string."""
939 t, tz = date or makedate()
963 t, tz = date or makedate()
940 if t < 0:
964 if t < 0:
941 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
965 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
942 tz = 0
966 tz = 0
943 if "%1" in format or "%2" in format:
967 if "%1" in format or "%2" in format:
944 sign = (tz > 0) and "-" or "+"
968 sign = (tz > 0) and "-" or "+"
945 minutes = abs(tz) // 60
969 minutes = abs(tz) // 60
946 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
970 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
947 format = format.replace("%2", "%02d" % (minutes % 60))
971 format = format.replace("%2", "%02d" % (minutes % 60))
948 try:
972 try:
949 t = time.gmtime(float(t) - tz)
973 t = time.gmtime(float(t) - tz)
950 except ValueError:
974 except ValueError:
951 # time was out of range
975 # time was out of range
952 t = time.gmtime(sys.maxint)
976 t = time.gmtime(sys.maxint)
953 s = time.strftime(format, t)
977 s = time.strftime(format, t)
954 return s
978 return s
955
979
956 def shortdate(date=None):
980 def shortdate(date=None):
957 """turn (timestamp, tzoff) tuple into iso 8631 date."""
981 """turn (timestamp, tzoff) tuple into iso 8631 date."""
958 return datestr(date, format='%Y-%m-%d')
982 return datestr(date, format='%Y-%m-%d')
959
983
960 def strdate(string, format, defaults=[]):
984 def strdate(string, format, defaults=[]):
961 """parse a localized time string and return a (unixtime, offset) tuple.
985 """parse a localized time string and return a (unixtime, offset) tuple.
962 if the string cannot be parsed, ValueError is raised."""
986 if the string cannot be parsed, ValueError is raised."""
963 def timezone(string):
987 def timezone(string):
964 tz = string.split()[-1]
988 tz = string.split()[-1]
965 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
989 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
966 sign = (tz[0] == "+") and 1 or -1
990 sign = (tz[0] == "+") and 1 or -1
967 hours = int(tz[1:3])
991 hours = int(tz[1:3])
968 minutes = int(tz[3:5])
992 minutes = int(tz[3:5])
969 return -sign * (hours * 60 + minutes) * 60
993 return -sign * (hours * 60 + minutes) * 60
970 if tz == "GMT" or tz == "UTC":
994 if tz == "GMT" or tz == "UTC":
971 return 0
995 return 0
972 return None
996 return None
973
997
974 # NOTE: unixtime = localunixtime + offset
998 # NOTE: unixtime = localunixtime + offset
975 offset, date = timezone(string), string
999 offset, date = timezone(string), string
976 if offset is not None:
1000 if offset is not None:
977 date = " ".join(string.split()[:-1])
1001 date = " ".join(string.split()[:-1])
978
1002
979 # add missing elements from defaults
1003 # add missing elements from defaults
980 usenow = False # default to using biased defaults
1004 usenow = False # default to using biased defaults
981 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1005 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
982 found = [True for p in part if ("%"+p) in format]
1006 found = [True for p in part if ("%"+p) in format]
983 if not found:
1007 if not found:
984 date += "@" + defaults[part][usenow]
1008 date += "@" + defaults[part][usenow]
985 format += "@%" + part[0]
1009 format += "@%" + part[0]
986 else:
1010 else:
987 # We've found a specific time element, less specific time
1011 # We've found a specific time element, less specific time
988 # elements are relative to today
1012 # elements are relative to today
989 usenow = True
1013 usenow = True
990
1014
991 timetuple = time.strptime(date, format)
1015 timetuple = time.strptime(date, format)
992 localunixtime = int(calendar.timegm(timetuple))
1016 localunixtime = int(calendar.timegm(timetuple))
993 if offset is None:
1017 if offset is None:
994 # local timezone
1018 # local timezone
995 unixtime = int(time.mktime(timetuple))
1019 unixtime = int(time.mktime(timetuple))
996 offset = unixtime - localunixtime
1020 offset = unixtime - localunixtime
997 else:
1021 else:
998 unixtime = localunixtime + offset
1022 unixtime = localunixtime + offset
999 return unixtime, offset
1023 return unixtime, offset
1000
1024
1001 def parsedate(date, formats=None, bias={}):
1025 def parsedate(date, formats=None, bias={}):
1002 """parse a localized date/time and return a (unixtime, offset) tuple.
1026 """parse a localized date/time and return a (unixtime, offset) tuple.
1003
1027
1004 The date may be a "unixtime offset" string or in one of the specified
1028 The date may be a "unixtime offset" string or in one of the specified
1005 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1029 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1006 """
1030 """
1007 if not date:
1031 if not date:
1008 return 0, 0
1032 return 0, 0
1009 if isinstance(date, tuple) and len(date) == 2:
1033 if isinstance(date, tuple) and len(date) == 2:
1010 return date
1034 return date
1011 if not formats:
1035 if not formats:
1012 formats = defaultdateformats
1036 formats = defaultdateformats
1013 date = date.strip()
1037 date = date.strip()
1014 try:
1038 try:
1015 when, offset = map(int, date.split(' '))
1039 when, offset = map(int, date.split(' '))
1016 except ValueError:
1040 except ValueError:
1017 # fill out defaults
1041 # fill out defaults
1018 now = makedate()
1042 now = makedate()
1019 defaults = {}
1043 defaults = {}
1020 for part in ("d", "mb", "yY", "HI", "M", "S"):
1044 for part in ("d", "mb", "yY", "HI", "M", "S"):
1021 # this piece is for rounding the specific end of unknowns
1045 # this piece is for rounding the specific end of unknowns
1022 b = bias.get(part)
1046 b = bias.get(part)
1023 if b is None:
1047 if b is None:
1024 if part[0] in "HMS":
1048 if part[0] in "HMS":
1025 b = "00"
1049 b = "00"
1026 else:
1050 else:
1027 b = "0"
1051 b = "0"
1028
1052
1029 # this piece is for matching the generic end to today's date
1053 # this piece is for matching the generic end to today's date
1030 n = datestr(now, "%" + part[0])
1054 n = datestr(now, "%" + part[0])
1031
1055
1032 defaults[part] = (b, n)
1056 defaults[part] = (b, n)
1033
1057
1034 for format in formats:
1058 for format in formats:
1035 try:
1059 try:
1036 when, offset = strdate(date, format, defaults)
1060 when, offset = strdate(date, format, defaults)
1037 except (ValueError, OverflowError):
1061 except (ValueError, OverflowError):
1038 pass
1062 pass
1039 else:
1063 else:
1040 break
1064 break
1041 else:
1065 else:
1042 raise Abort(_('invalid date: %r') % date)
1066 raise Abort(_('invalid date: %r') % date)
1043 # validate explicit (probably user-specified) date and
1067 # validate explicit (probably user-specified) date and
1044 # time zone offset. values must fit in signed 32 bits for
1068 # time zone offset. values must fit in signed 32 bits for
1045 # current 32-bit linux runtimes. timezones go from UTC-12
1069 # current 32-bit linux runtimes. timezones go from UTC-12
1046 # to UTC+14
1070 # to UTC+14
1047 if abs(when) > 0x7fffffff:
1071 if abs(when) > 0x7fffffff:
1048 raise Abort(_('date exceeds 32 bits: %d') % when)
1072 raise Abort(_('date exceeds 32 bits: %d') % when)
1049 if when < 0:
1073 if when < 0:
1050 raise Abort(_('negative date value: %d') % when)
1074 raise Abort(_('negative date value: %d') % when)
1051 if offset < -50400 or offset > 43200:
1075 if offset < -50400 or offset > 43200:
1052 raise Abort(_('impossible time zone offset: %d') % offset)
1076 raise Abort(_('impossible time zone offset: %d') % offset)
1053 return when, offset
1077 return when, offset
1054
1078
1055 def matchdate(date):
1079 def matchdate(date):
1056 """Return a function that matches a given date match specifier
1080 """Return a function that matches a given date match specifier
1057
1081
1058 Formats include:
1082 Formats include:
1059
1083
1060 '{date}' match a given date to the accuracy provided
1084 '{date}' match a given date to the accuracy provided
1061
1085
1062 '<{date}' on or before a given date
1086 '<{date}' on or before a given date
1063
1087
1064 '>{date}' on or after a given date
1088 '>{date}' on or after a given date
1065
1089
1066 >>> p1 = parsedate("10:29:59")
1090 >>> p1 = parsedate("10:29:59")
1067 >>> p2 = parsedate("10:30:00")
1091 >>> p2 = parsedate("10:30:00")
1068 >>> p3 = parsedate("10:30:59")
1092 >>> p3 = parsedate("10:30:59")
1069 >>> p4 = parsedate("10:31:00")
1093 >>> p4 = parsedate("10:31:00")
1070 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1094 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1071 >>> f = matchdate("10:30")
1095 >>> f = matchdate("10:30")
1072 >>> f(p1[0])
1096 >>> f(p1[0])
1073 False
1097 False
1074 >>> f(p2[0])
1098 >>> f(p2[0])
1075 True
1099 True
1076 >>> f(p3[0])
1100 >>> f(p3[0])
1077 True
1101 True
1078 >>> f(p4[0])
1102 >>> f(p4[0])
1079 False
1103 False
1080 >>> f(p5[0])
1104 >>> f(p5[0])
1081 False
1105 False
1082 """
1106 """
1083
1107
1084 def lower(date):
1108 def lower(date):
1085 d = dict(mb="1", d="1")
1109 d = dict(mb="1", d="1")
1086 return parsedate(date, extendeddateformats, d)[0]
1110 return parsedate(date, extendeddateformats, d)[0]
1087
1111
1088 def upper(date):
1112 def upper(date):
1089 d = dict(mb="12", HI="23", M="59", S="59")
1113 d = dict(mb="12", HI="23", M="59", S="59")
1090 for days in ("31", "30", "29"):
1114 for days in ("31", "30", "29"):
1091 try:
1115 try:
1092 d["d"] = days
1116 d["d"] = days
1093 return parsedate(date, extendeddateformats, d)[0]
1117 return parsedate(date, extendeddateformats, d)[0]
1094 except Abort:
1118 except Abort:
1095 pass
1119 pass
1096 d["d"] = "28"
1120 d["d"] = "28"
1097 return parsedate(date, extendeddateformats, d)[0]
1121 return parsedate(date, extendeddateformats, d)[0]
1098
1122
1099 date = date.strip()
1123 date = date.strip()
1100
1124
1101 if not date:
1125 if not date:
1102 raise Abort(_("dates cannot consist entirely of whitespace"))
1126 raise Abort(_("dates cannot consist entirely of whitespace"))
1103 elif date[0] == "<":
1127 elif date[0] == "<":
1104 if not date[1:]:
1128 if not date[1:]:
1105 raise Abort(_("invalid day spec, use '<DATE'"))
1129 raise Abort(_("invalid day spec, use '<DATE'"))
1106 when = upper(date[1:])
1130 when = upper(date[1:])
1107 return lambda x: x <= when
1131 return lambda x: x <= when
1108 elif date[0] == ">":
1132 elif date[0] == ">":
1109 if not date[1:]:
1133 if not date[1:]:
1110 raise Abort(_("invalid day spec, use '>DATE'"))
1134 raise Abort(_("invalid day spec, use '>DATE'"))
1111 when = lower(date[1:])
1135 when = lower(date[1:])
1112 return lambda x: x >= when
1136 return lambda x: x >= when
1113 elif date[0] == "-":
1137 elif date[0] == "-":
1114 try:
1138 try:
1115 days = int(date[1:])
1139 days = int(date[1:])
1116 except ValueError:
1140 except ValueError:
1117 raise Abort(_("invalid day spec: %s") % date[1:])
1141 raise Abort(_("invalid day spec: %s") % date[1:])
1118 if days < 0:
1142 if days < 0:
1119 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1143 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1120 % date[1:])
1144 % date[1:])
1121 when = makedate()[0] - days * 3600 * 24
1145 when = makedate()[0] - days * 3600 * 24
1122 return lambda x: x >= when
1146 return lambda x: x >= when
1123 elif " to " in date:
1147 elif " to " in date:
1124 a, b = date.split(" to ")
1148 a, b = date.split(" to ")
1125 start, stop = lower(a), upper(b)
1149 start, stop = lower(a), upper(b)
1126 return lambda x: x >= start and x <= stop
1150 return lambda x: x >= start and x <= stop
1127 else:
1151 else:
1128 start, stop = lower(date), upper(date)
1152 start, stop = lower(date), upper(date)
1129 return lambda x: x >= start and x <= stop
1153 return lambda x: x >= start and x <= stop
1130
1154
1131 def shortuser(user):
1155 def shortuser(user):
1132 """Return a short representation of a user name or email address."""
1156 """Return a short representation of a user name or email address."""
1133 f = user.find('@')
1157 f = user.find('@')
1134 if f >= 0:
1158 if f >= 0:
1135 user = user[:f]
1159 user = user[:f]
1136 f = user.find('<')
1160 f = user.find('<')
1137 if f >= 0:
1161 if f >= 0:
1138 user = user[f + 1:]
1162 user = user[f + 1:]
1139 f = user.find(' ')
1163 f = user.find(' ')
1140 if f >= 0:
1164 if f >= 0:
1141 user = user[:f]
1165 user = user[:f]
1142 f = user.find('.')
1166 f = user.find('.')
1143 if f >= 0:
1167 if f >= 0:
1144 user = user[:f]
1168 user = user[:f]
1145 return user
1169 return user
1146
1170
1147 def emailuser(user):
1171 def emailuser(user):
1148 """Return the user portion of an email address."""
1172 """Return the user portion of an email address."""
1149 f = user.find('@')
1173 f = user.find('@')
1150 if f >= 0:
1174 if f >= 0:
1151 user = user[:f]
1175 user = user[:f]
1152 f = user.find('<')
1176 f = user.find('<')
1153 if f >= 0:
1177 if f >= 0:
1154 user = user[f + 1:]
1178 user = user[f + 1:]
1155 return user
1179 return user
1156
1180
1157 def email(author):
1181 def email(author):
1158 '''get email of author.'''
1182 '''get email of author.'''
1159 r = author.find('>')
1183 r = author.find('>')
1160 if r == -1:
1184 if r == -1:
1161 r = None
1185 r = None
1162 return author[author.find('<') + 1:r]
1186 return author[author.find('<') + 1:r]
1163
1187
1164 def _ellipsis(text, maxlength):
1188 def _ellipsis(text, maxlength):
1165 if len(text) <= maxlength:
1189 if len(text) <= maxlength:
1166 return text, False
1190 return text, False
1167 else:
1191 else:
1168 return "%s..." % (text[:maxlength - 3]), True
1192 return "%s..." % (text[:maxlength - 3]), True
1169
1193
1170 def ellipsis(text, maxlength=400):
1194 def ellipsis(text, maxlength=400):
1171 """Trim string to at most maxlength (default: 400) characters."""
1195 """Trim string to at most maxlength (default: 400) characters."""
1172 try:
1196 try:
1173 # use unicode not to split at intermediate multi-byte sequence
1197 # use unicode not to split at intermediate multi-byte sequence
1174 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1198 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1175 maxlength)
1199 maxlength)
1176 if not truncated:
1200 if not truncated:
1177 return text
1201 return text
1178 return utext.encode(encoding.encoding)
1202 return utext.encode(encoding.encoding)
1179 except (UnicodeDecodeError, UnicodeEncodeError):
1203 except (UnicodeDecodeError, UnicodeEncodeError):
1180 return _ellipsis(text, maxlength)[0]
1204 return _ellipsis(text, maxlength)[0]
1181
1205
1182 _byteunits = (
1206 _byteunits = (
1183 (100, 1 << 30, _('%.0f GB')),
1207 (100, 1 << 30, _('%.0f GB')),
1184 (10, 1 << 30, _('%.1f GB')),
1208 (10, 1 << 30, _('%.1f GB')),
1185 (1, 1 << 30, _('%.2f GB')),
1209 (1, 1 << 30, _('%.2f GB')),
1186 (100, 1 << 20, _('%.0f MB')),
1210 (100, 1 << 20, _('%.0f MB')),
1187 (10, 1 << 20, _('%.1f MB')),
1211 (10, 1 << 20, _('%.1f MB')),
1188 (1, 1 << 20, _('%.2f MB')),
1212 (1, 1 << 20, _('%.2f MB')),
1189 (100, 1 << 10, _('%.0f KB')),
1213 (100, 1 << 10, _('%.0f KB')),
1190 (10, 1 << 10, _('%.1f KB')),
1214 (10, 1 << 10, _('%.1f KB')),
1191 (1, 1 << 10, _('%.2f KB')),
1215 (1, 1 << 10, _('%.2f KB')),
1192 (1, 1, _('%.0f bytes')),
1216 (1, 1, _('%.0f bytes')),
1193 )
1217 )
1194
1218
1195 def bytecount(nbytes):
1219 def bytecount(nbytes):
1196 '''return byte count formatted as readable string, with units'''
1220 '''return byte count formatted as readable string, with units'''
1197
1221
1198 for multiplier, divisor, format in _byteunits:
1222 for multiplier, divisor, format in _byteunits:
1199 if nbytes >= divisor * multiplier:
1223 if nbytes >= divisor * multiplier:
1200 return format % (nbytes / float(divisor))
1224 return format % (nbytes / float(divisor))
1201 return _byteunits[-1][2] % nbytes
1225 return _byteunits[-1][2] % nbytes
1202
1226
1203 def uirepr(s):
1227 def uirepr(s):
1204 # Avoid double backslash in Windows path repr()
1228 # Avoid double backslash in Windows path repr()
1205 return repr(s).replace('\\\\', '\\')
1229 return repr(s).replace('\\\\', '\\')
1206
1230
1207 # delay import of textwrap
1231 # delay import of textwrap
1208 def MBTextWrapper(**kwargs):
1232 def MBTextWrapper(**kwargs):
1209 class tw(textwrap.TextWrapper):
1233 class tw(textwrap.TextWrapper):
1210 """
1234 """
1211 Extend TextWrapper for width-awareness.
1235 Extend TextWrapper for width-awareness.
1212
1236
1213 Neither number of 'bytes' in any encoding nor 'characters' is
1237 Neither number of 'bytes' in any encoding nor 'characters' is
1214 appropriate to calculate terminal columns for specified string.
1238 appropriate to calculate terminal columns for specified string.
1215
1239
1216 Original TextWrapper implementation uses built-in 'len()' directly,
1240 Original TextWrapper implementation uses built-in 'len()' directly,
1217 so overriding is needed to use width information of each characters.
1241 so overriding is needed to use width information of each characters.
1218
1242
1219 In addition, characters classified into 'ambiguous' width are
1243 In addition, characters classified into 'ambiguous' width are
1220 treated as wide in east asian area, but as narrow in other.
1244 treated as wide in east asian area, but as narrow in other.
1221
1245
1222 This requires use decision to determine width of such characters.
1246 This requires use decision to determine width of such characters.
1223 """
1247 """
1224 def __init__(self, **kwargs):
1248 def __init__(self, **kwargs):
1225 textwrap.TextWrapper.__init__(self, **kwargs)
1249 textwrap.TextWrapper.__init__(self, **kwargs)
1226
1250
1227 # for compatibility between 2.4 and 2.6
1251 # for compatibility between 2.4 and 2.6
1228 if getattr(self, 'drop_whitespace', None) is None:
1252 if getattr(self, 'drop_whitespace', None) is None:
1229 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1253 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1230
1254
1231 def _cutdown(self, ucstr, space_left):
1255 def _cutdown(self, ucstr, space_left):
1232 l = 0
1256 l = 0
1233 colwidth = encoding.ucolwidth
1257 colwidth = encoding.ucolwidth
1234 for i in xrange(len(ucstr)):
1258 for i in xrange(len(ucstr)):
1235 l += colwidth(ucstr[i])
1259 l += colwidth(ucstr[i])
1236 if space_left < l:
1260 if space_left < l:
1237 return (ucstr[:i], ucstr[i:])
1261 return (ucstr[:i], ucstr[i:])
1238 return ucstr, ''
1262 return ucstr, ''
1239
1263
1240 # overriding of base class
1264 # overriding of base class
1241 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1265 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1242 space_left = max(width - cur_len, 1)
1266 space_left = max(width - cur_len, 1)
1243
1267
1244 if self.break_long_words:
1268 if self.break_long_words:
1245 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1269 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1246 cur_line.append(cut)
1270 cur_line.append(cut)
1247 reversed_chunks[-1] = res
1271 reversed_chunks[-1] = res
1248 elif not cur_line:
1272 elif not cur_line:
1249 cur_line.append(reversed_chunks.pop())
1273 cur_line.append(reversed_chunks.pop())
1250
1274
1251 # this overriding code is imported from TextWrapper of python 2.6
1275 # this overriding code is imported from TextWrapper of python 2.6
1252 # to calculate columns of string by 'encoding.ucolwidth()'
1276 # to calculate columns of string by 'encoding.ucolwidth()'
1253 def _wrap_chunks(self, chunks):
1277 def _wrap_chunks(self, chunks):
1254 colwidth = encoding.ucolwidth
1278 colwidth = encoding.ucolwidth
1255
1279
1256 lines = []
1280 lines = []
1257 if self.width <= 0:
1281 if self.width <= 0:
1258 raise ValueError("invalid width %r (must be > 0)" % self.width)
1282 raise ValueError("invalid width %r (must be > 0)" % self.width)
1259
1283
1260 # Arrange in reverse order so items can be efficiently popped
1284 # Arrange in reverse order so items can be efficiently popped
1261 # from a stack of chucks.
1285 # from a stack of chucks.
1262 chunks.reverse()
1286 chunks.reverse()
1263
1287
1264 while chunks:
1288 while chunks:
1265
1289
1266 # Start the list of chunks that will make up the current line.
1290 # Start the list of chunks that will make up the current line.
1267 # cur_len is just the length of all the chunks in cur_line.
1291 # cur_len is just the length of all the chunks in cur_line.
1268 cur_line = []
1292 cur_line = []
1269 cur_len = 0
1293 cur_len = 0
1270
1294
1271 # Figure out which static string will prefix this line.
1295 # Figure out which static string will prefix this line.
1272 if lines:
1296 if lines:
1273 indent = self.subsequent_indent
1297 indent = self.subsequent_indent
1274 else:
1298 else:
1275 indent = self.initial_indent
1299 indent = self.initial_indent
1276
1300
1277 # Maximum width for this line.
1301 # Maximum width for this line.
1278 width = self.width - len(indent)
1302 width = self.width - len(indent)
1279
1303
1280 # First chunk on line is whitespace -- drop it, unless this
1304 # First chunk on line is whitespace -- drop it, unless this
1281 # is the very beginning of the text (ie. no lines started yet).
1305 # is the very beginning of the text (ie. no lines started yet).
1282 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1306 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1283 del chunks[-1]
1307 del chunks[-1]
1284
1308
1285 while chunks:
1309 while chunks:
1286 l = colwidth(chunks[-1])
1310 l = colwidth(chunks[-1])
1287
1311
1288 # Can at least squeeze this chunk onto the current line.
1312 # Can at least squeeze this chunk onto the current line.
1289 if cur_len + l <= width:
1313 if cur_len + l <= width:
1290 cur_line.append(chunks.pop())
1314 cur_line.append(chunks.pop())
1291 cur_len += l
1315 cur_len += l
1292
1316
1293 # Nope, this line is full.
1317 # Nope, this line is full.
1294 else:
1318 else:
1295 break
1319 break
1296
1320
1297 # The current line is full, and the next chunk is too big to
1321 # The current line is full, and the next chunk is too big to
1298 # fit on *any* line (not just this one).
1322 # fit on *any* line (not just this one).
1299 if chunks and colwidth(chunks[-1]) > width:
1323 if chunks and colwidth(chunks[-1]) > width:
1300 self._handle_long_word(chunks, cur_line, cur_len, width)
1324 self._handle_long_word(chunks, cur_line, cur_len, width)
1301
1325
1302 # If the last chunk on this line is all whitespace, drop it.
1326 # If the last chunk on this line is all whitespace, drop it.
1303 if (self.drop_whitespace and
1327 if (self.drop_whitespace and
1304 cur_line and cur_line[-1].strip() == ''):
1328 cur_line and cur_line[-1].strip() == ''):
1305 del cur_line[-1]
1329 del cur_line[-1]
1306
1330
1307 # Convert current line back to a string and store it in list
1331 # Convert current line back to a string and store it in list
1308 # of all lines (return value).
1332 # of all lines (return value).
1309 if cur_line:
1333 if cur_line:
1310 lines.append(indent + ''.join(cur_line))
1334 lines.append(indent + ''.join(cur_line))
1311
1335
1312 return lines
1336 return lines
1313
1337
1314 global MBTextWrapper
1338 global MBTextWrapper
1315 MBTextWrapper = tw
1339 MBTextWrapper = tw
1316 return tw(**kwargs)
1340 return tw(**kwargs)
1317
1341
1318 def wrap(line, width, initindent='', hangindent=''):
1342 def wrap(line, width, initindent='', hangindent=''):
1319 maxindent = max(len(hangindent), len(initindent))
1343 maxindent = max(len(hangindent), len(initindent))
1320 if width <= maxindent:
1344 if width <= maxindent:
1321 # adjust for weird terminal size
1345 # adjust for weird terminal size
1322 width = max(78, maxindent + 1)
1346 width = max(78, maxindent + 1)
1323 line = line.decode(encoding.encoding, encoding.encodingmode)
1347 line = line.decode(encoding.encoding, encoding.encodingmode)
1324 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1348 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1325 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1349 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1326 wrapper = MBTextWrapper(width=width,
1350 wrapper = MBTextWrapper(width=width,
1327 initial_indent=initindent,
1351 initial_indent=initindent,
1328 subsequent_indent=hangindent)
1352 subsequent_indent=hangindent)
1329 return wrapper.fill(line).encode(encoding.encoding)
1353 return wrapper.fill(line).encode(encoding.encoding)
1330
1354
1331 def iterlines(iterator):
1355 def iterlines(iterator):
1332 for chunk in iterator:
1356 for chunk in iterator:
1333 for line in chunk.splitlines():
1357 for line in chunk.splitlines():
1334 yield line
1358 yield line
1335
1359
1336 def expandpath(path):
1360 def expandpath(path):
1337 return os.path.expanduser(os.path.expandvars(path))
1361 return os.path.expanduser(os.path.expandvars(path))
1338
1362
1339 def hgcmd():
1363 def hgcmd():
1340 """Return the command used to execute current hg
1364 """Return the command used to execute current hg
1341
1365
1342 This is different from hgexecutable() because on Windows we want
1366 This is different from hgexecutable() because on Windows we want
1343 to avoid things opening new shell windows like batch files, so we
1367 to avoid things opening new shell windows like batch files, so we
1344 get either the python call or current executable.
1368 get either the python call or current executable.
1345 """
1369 """
1346 if mainfrozen():
1370 if mainfrozen():
1347 return [sys.executable]
1371 return [sys.executable]
1348 return gethgcmd()
1372 return gethgcmd()
1349
1373
1350 def rundetached(args, condfn):
1374 def rundetached(args, condfn):
1351 """Execute the argument list in a detached process.
1375 """Execute the argument list in a detached process.
1352
1376
1353 condfn is a callable which is called repeatedly and should return
1377 condfn is a callable which is called repeatedly and should return
1354 True once the child process is known to have started successfully.
1378 True once the child process is known to have started successfully.
1355 At this point, the child process PID is returned. If the child
1379 At this point, the child process PID is returned. If the child
1356 process fails to start or finishes before condfn() evaluates to
1380 process fails to start or finishes before condfn() evaluates to
1357 True, return -1.
1381 True, return -1.
1358 """
1382 """
1359 # Windows case is easier because the child process is either
1383 # Windows case is easier because the child process is either
1360 # successfully starting and validating the condition or exiting
1384 # successfully starting and validating the condition or exiting
1361 # on failure. We just poll on its PID. On Unix, if the child
1385 # on failure. We just poll on its PID. On Unix, if the child
1362 # process fails to start, it will be left in a zombie state until
1386 # process fails to start, it will be left in a zombie state until
1363 # the parent wait on it, which we cannot do since we expect a long
1387 # the parent wait on it, which we cannot do since we expect a long
1364 # running process on success. Instead we listen for SIGCHLD telling
1388 # running process on success. Instead we listen for SIGCHLD telling
1365 # us our child process terminated.
1389 # us our child process terminated.
1366 terminated = set()
1390 terminated = set()
1367 def handler(signum, frame):
1391 def handler(signum, frame):
1368 terminated.add(os.wait())
1392 terminated.add(os.wait())
1369 prevhandler = None
1393 prevhandler = None
1370 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1394 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1371 if SIGCHLD is not None:
1395 if SIGCHLD is not None:
1372 prevhandler = signal.signal(SIGCHLD, handler)
1396 prevhandler = signal.signal(SIGCHLD, handler)
1373 try:
1397 try:
1374 pid = spawndetached(args)
1398 pid = spawndetached(args)
1375 while not condfn():
1399 while not condfn():
1376 if ((pid in terminated or not testpid(pid))
1400 if ((pid in terminated or not testpid(pid))
1377 and not condfn()):
1401 and not condfn()):
1378 return -1
1402 return -1
1379 time.sleep(0.1)
1403 time.sleep(0.1)
1380 return pid
1404 return pid
1381 finally:
1405 finally:
1382 if prevhandler is not None:
1406 if prevhandler is not None:
1383 signal.signal(signal.SIGCHLD, prevhandler)
1407 signal.signal(signal.SIGCHLD, prevhandler)
1384
1408
1385 try:
1409 try:
1386 any, all = any, all
1410 any, all = any, all
1387 except NameError:
1411 except NameError:
1388 def any(iterable):
1412 def any(iterable):
1389 for i in iterable:
1413 for i in iterable:
1390 if i:
1414 if i:
1391 return True
1415 return True
1392 return False
1416 return False
1393
1417
1394 def all(iterable):
1418 def all(iterable):
1395 for i in iterable:
1419 for i in iterable:
1396 if not i:
1420 if not i:
1397 return False
1421 return False
1398 return True
1422 return True
1399
1423
1400 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1424 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1401 """Return the result of interpolating items in the mapping into string s.
1425 """Return the result of interpolating items in the mapping into string s.
1402
1426
1403 prefix is a single character string, or a two character string with
1427 prefix is a single character string, or a two character string with
1404 a backslash as the first character if the prefix needs to be escaped in
1428 a backslash as the first character if the prefix needs to be escaped in
1405 a regular expression.
1429 a regular expression.
1406
1430
1407 fn is an optional function that will be applied to the replacement text
1431 fn is an optional function that will be applied to the replacement text
1408 just before replacement.
1432 just before replacement.
1409
1433
1410 escape_prefix is an optional flag that allows using doubled prefix for
1434 escape_prefix is an optional flag that allows using doubled prefix for
1411 its escaping.
1435 its escaping.
1412 """
1436 """
1413 fn = fn or (lambda s: s)
1437 fn = fn or (lambda s: s)
1414 patterns = '|'.join(mapping.keys())
1438 patterns = '|'.join(mapping.keys())
1415 if escape_prefix:
1439 if escape_prefix:
1416 patterns += '|' + prefix
1440 patterns += '|' + prefix
1417 if len(prefix) > 1:
1441 if len(prefix) > 1:
1418 prefix_char = prefix[1:]
1442 prefix_char = prefix[1:]
1419 else:
1443 else:
1420 prefix_char = prefix
1444 prefix_char = prefix
1421 mapping[prefix_char] = prefix_char
1445 mapping[prefix_char] = prefix_char
1422 r = re.compile(r'%s(%s)' % (prefix, patterns))
1446 r = re.compile(r'%s(%s)' % (prefix, patterns))
1423 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1447 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1424
1448
1425 def getport(port):
1449 def getport(port):
1426 """Return the port for a given network service.
1450 """Return the port for a given network service.
1427
1451
1428 If port is an integer, it's returned as is. If it's a string, it's
1452 If port is an integer, it's returned as is. If it's a string, it's
1429 looked up using socket.getservbyname(). If there's no matching
1453 looked up using socket.getservbyname(). If there's no matching
1430 service, util.Abort is raised.
1454 service, util.Abort is raised.
1431 """
1455 """
1432 try:
1456 try:
1433 return int(port)
1457 return int(port)
1434 except ValueError:
1458 except ValueError:
1435 pass
1459 pass
1436
1460
1437 try:
1461 try:
1438 return socket.getservbyname(port)
1462 return socket.getservbyname(port)
1439 except socket.error:
1463 except socket.error:
1440 raise Abort(_("no port number associated with service '%s'") % port)
1464 raise Abort(_("no port number associated with service '%s'") % port)
1441
1465
1442 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1466 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1443 '0': False, 'no': False, 'false': False, 'off': False,
1467 '0': False, 'no': False, 'false': False, 'off': False,
1444 'never': False}
1468 'never': False}
1445
1469
1446 def parsebool(s):
1470 def parsebool(s):
1447 """Parse s into a boolean.
1471 """Parse s into a boolean.
1448
1472
1449 If s is not a valid boolean, returns None.
1473 If s is not a valid boolean, returns None.
1450 """
1474 """
1451 return _booleans.get(s.lower(), None)
1475 return _booleans.get(s.lower(), None)
1452
1476
1453 _hexdig = '0123456789ABCDEFabcdef'
1477 _hexdig = '0123456789ABCDEFabcdef'
1454 _hextochr = dict((a + b, chr(int(a + b, 16)))
1478 _hextochr = dict((a + b, chr(int(a + b, 16)))
1455 for a in _hexdig for b in _hexdig)
1479 for a in _hexdig for b in _hexdig)
1456
1480
1457 def _urlunquote(s):
1481 def _urlunquote(s):
1458 """unquote('abc%20def') -> 'abc def'."""
1482 """unquote('abc%20def') -> 'abc def'."""
1459 res = s.split('%')
1483 res = s.split('%')
1460 # fastpath
1484 # fastpath
1461 if len(res) == 1:
1485 if len(res) == 1:
1462 return s
1486 return s
1463 s = res[0]
1487 s = res[0]
1464 for item in res[1:]:
1488 for item in res[1:]:
1465 try:
1489 try:
1466 s += _hextochr[item[:2]] + item[2:]
1490 s += _hextochr[item[:2]] + item[2:]
1467 except KeyError:
1491 except KeyError:
1468 s += '%' + item
1492 s += '%' + item
1469 except UnicodeDecodeError:
1493 except UnicodeDecodeError:
1470 s += unichr(int(item[:2], 16)) + item[2:]
1494 s += unichr(int(item[:2], 16)) + item[2:]
1471 return s
1495 return s
1472
1496
1473 class url(object):
1497 class url(object):
1474 r"""Reliable URL parser.
1498 r"""Reliable URL parser.
1475
1499
1476 This parses URLs and provides attributes for the following
1500 This parses URLs and provides attributes for the following
1477 components:
1501 components:
1478
1502
1479 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1503 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1480
1504
1481 Missing components are set to None. The only exception is
1505 Missing components are set to None. The only exception is
1482 fragment, which is set to '' if present but empty.
1506 fragment, which is set to '' if present but empty.
1483
1507
1484 If parsefragment is False, fragment is included in query. If
1508 If parsefragment is False, fragment is included in query. If
1485 parsequery is False, query is included in path. If both are
1509 parsequery is False, query is included in path. If both are
1486 False, both fragment and query are included in path.
1510 False, both fragment and query are included in path.
1487
1511
1488 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1512 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1489
1513
1490 Note that for backward compatibility reasons, bundle URLs do not
1514 Note that for backward compatibility reasons, bundle URLs do not
1491 take host names. That means 'bundle://../' has a path of '../'.
1515 take host names. That means 'bundle://../' has a path of '../'.
1492
1516
1493 Examples:
1517 Examples:
1494
1518
1495 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1519 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1496 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1520 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1497 >>> url('ssh://[::1]:2200//home/joe/repo')
1521 >>> url('ssh://[::1]:2200//home/joe/repo')
1498 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1522 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1499 >>> url('file:///home/joe/repo')
1523 >>> url('file:///home/joe/repo')
1500 <url scheme: 'file', path: '/home/joe/repo'>
1524 <url scheme: 'file', path: '/home/joe/repo'>
1501 >>> url('file:///c:/temp/foo/')
1525 >>> url('file:///c:/temp/foo/')
1502 <url scheme: 'file', path: 'c:/temp/foo/'>
1526 <url scheme: 'file', path: 'c:/temp/foo/'>
1503 >>> url('bundle:foo')
1527 >>> url('bundle:foo')
1504 <url scheme: 'bundle', path: 'foo'>
1528 <url scheme: 'bundle', path: 'foo'>
1505 >>> url('bundle://../foo')
1529 >>> url('bundle://../foo')
1506 <url scheme: 'bundle', path: '../foo'>
1530 <url scheme: 'bundle', path: '../foo'>
1507 >>> url(r'c:\foo\bar')
1531 >>> url(r'c:\foo\bar')
1508 <url path: 'c:\\foo\\bar'>
1532 <url path: 'c:\\foo\\bar'>
1509 >>> url(r'\\blah\blah\blah')
1533 >>> url(r'\\blah\blah\blah')
1510 <url path: '\\\\blah\\blah\\blah'>
1534 <url path: '\\\\blah\\blah\\blah'>
1511 >>> url(r'\\blah\blah\blah#baz')
1535 >>> url(r'\\blah\blah\blah#baz')
1512 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1536 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1513
1537
1514 Authentication credentials:
1538 Authentication credentials:
1515
1539
1516 >>> url('ssh://joe:xyz@x/repo')
1540 >>> url('ssh://joe:xyz@x/repo')
1517 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1541 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1518 >>> url('ssh://joe@x/repo')
1542 >>> url('ssh://joe@x/repo')
1519 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1543 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1520
1544
1521 Query strings and fragments:
1545 Query strings and fragments:
1522
1546
1523 >>> url('http://host/a?b#c')
1547 >>> url('http://host/a?b#c')
1524 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1548 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1525 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1549 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1526 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1550 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1527 """
1551 """
1528
1552
1529 _safechars = "!~*'()+"
1553 _safechars = "!~*'()+"
1530 _safepchars = "/!~*'()+:"
1554 _safepchars = "/!~*'()+:"
1531 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1555 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1532
1556
1533 def __init__(self, path, parsequery=True, parsefragment=True):
1557 def __init__(self, path, parsequery=True, parsefragment=True):
1534 # We slowly chomp away at path until we have only the path left
1558 # We slowly chomp away at path until we have only the path left
1535 self.scheme = self.user = self.passwd = self.host = None
1559 self.scheme = self.user = self.passwd = self.host = None
1536 self.port = self.path = self.query = self.fragment = None
1560 self.port = self.path = self.query = self.fragment = None
1537 self._localpath = True
1561 self._localpath = True
1538 self._hostport = ''
1562 self._hostport = ''
1539 self._origpath = path
1563 self._origpath = path
1540
1564
1541 if parsefragment and '#' in path:
1565 if parsefragment and '#' in path:
1542 path, self.fragment = path.split('#', 1)
1566 path, self.fragment = path.split('#', 1)
1543 if not path:
1567 if not path:
1544 path = None
1568 path = None
1545
1569
1546 # special case for Windows drive letters and UNC paths
1570 # special case for Windows drive letters and UNC paths
1547 if hasdriveletter(path) or path.startswith(r'\\'):
1571 if hasdriveletter(path) or path.startswith(r'\\'):
1548 self.path = path
1572 self.path = path
1549 return
1573 return
1550
1574
1551 # For compatibility reasons, we can't handle bundle paths as
1575 # For compatibility reasons, we can't handle bundle paths as
1552 # normal URLS
1576 # normal URLS
1553 if path.startswith('bundle:'):
1577 if path.startswith('bundle:'):
1554 self.scheme = 'bundle'
1578 self.scheme = 'bundle'
1555 path = path[7:]
1579 path = path[7:]
1556 if path.startswith('//'):
1580 if path.startswith('//'):
1557 path = path[2:]
1581 path = path[2:]
1558 self.path = path
1582 self.path = path
1559 return
1583 return
1560
1584
1561 if self._matchscheme(path):
1585 if self._matchscheme(path):
1562 parts = path.split(':', 1)
1586 parts = path.split(':', 1)
1563 if parts[0]:
1587 if parts[0]:
1564 self.scheme, path = parts
1588 self.scheme, path = parts
1565 self._localpath = False
1589 self._localpath = False
1566
1590
1567 if not path:
1591 if not path:
1568 path = None
1592 path = None
1569 if self._localpath:
1593 if self._localpath:
1570 self.path = ''
1594 self.path = ''
1571 return
1595 return
1572 else:
1596 else:
1573 if self._localpath:
1597 if self._localpath:
1574 self.path = path
1598 self.path = path
1575 return
1599 return
1576
1600
1577 if parsequery and '?' in path:
1601 if parsequery and '?' in path:
1578 path, self.query = path.split('?', 1)
1602 path, self.query = path.split('?', 1)
1579 if not path:
1603 if not path:
1580 path = None
1604 path = None
1581 if not self.query:
1605 if not self.query:
1582 self.query = None
1606 self.query = None
1583
1607
1584 # // is required to specify a host/authority
1608 # // is required to specify a host/authority
1585 if path and path.startswith('//'):
1609 if path and path.startswith('//'):
1586 parts = path[2:].split('/', 1)
1610 parts = path[2:].split('/', 1)
1587 if len(parts) > 1:
1611 if len(parts) > 1:
1588 self.host, path = parts
1612 self.host, path = parts
1589 path = path
1613 path = path
1590 else:
1614 else:
1591 self.host = parts[0]
1615 self.host = parts[0]
1592 path = None
1616 path = None
1593 if not self.host:
1617 if not self.host:
1594 self.host = None
1618 self.host = None
1595 # path of file:///d is /d
1619 # path of file:///d is /d
1596 # path of file:///d:/ is d:/, not /d:/
1620 # path of file:///d:/ is d:/, not /d:/
1597 if path and not hasdriveletter(path):
1621 if path and not hasdriveletter(path):
1598 path = '/' + path
1622 path = '/' + path
1599
1623
1600 if self.host and '@' in self.host:
1624 if self.host and '@' in self.host:
1601 self.user, self.host = self.host.rsplit('@', 1)
1625 self.user, self.host = self.host.rsplit('@', 1)
1602 if ':' in self.user:
1626 if ':' in self.user:
1603 self.user, self.passwd = self.user.split(':', 1)
1627 self.user, self.passwd = self.user.split(':', 1)
1604 if not self.host:
1628 if not self.host:
1605 self.host = None
1629 self.host = None
1606
1630
1607 # Don't split on colons in IPv6 addresses without ports
1631 # Don't split on colons in IPv6 addresses without ports
1608 if (self.host and ':' in self.host and
1632 if (self.host and ':' in self.host and
1609 not (self.host.startswith('[') and self.host.endswith(']'))):
1633 not (self.host.startswith('[') and self.host.endswith(']'))):
1610 self._hostport = self.host
1634 self._hostport = self.host
1611 self.host, self.port = self.host.rsplit(':', 1)
1635 self.host, self.port = self.host.rsplit(':', 1)
1612 if not self.host:
1636 if not self.host:
1613 self.host = None
1637 self.host = None
1614
1638
1615 if (self.host and self.scheme == 'file' and
1639 if (self.host and self.scheme == 'file' and
1616 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1640 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1617 raise Abort(_('file:// URLs can only refer to localhost'))
1641 raise Abort(_('file:// URLs can only refer to localhost'))
1618
1642
1619 self.path = path
1643 self.path = path
1620
1644
1621 # leave the query string escaped
1645 # leave the query string escaped
1622 for a in ('user', 'passwd', 'host', 'port',
1646 for a in ('user', 'passwd', 'host', 'port',
1623 'path', 'fragment'):
1647 'path', 'fragment'):
1624 v = getattr(self, a)
1648 v = getattr(self, a)
1625 if v is not None:
1649 if v is not None:
1626 setattr(self, a, _urlunquote(v))
1650 setattr(self, a, _urlunquote(v))
1627
1651
1628 def __repr__(self):
1652 def __repr__(self):
1629 attrs = []
1653 attrs = []
1630 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1654 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1631 'query', 'fragment'):
1655 'query', 'fragment'):
1632 v = getattr(self, a)
1656 v = getattr(self, a)
1633 if v is not None:
1657 if v is not None:
1634 attrs.append('%s: %r' % (a, v))
1658 attrs.append('%s: %r' % (a, v))
1635 return '<url %s>' % ', '.join(attrs)
1659 return '<url %s>' % ', '.join(attrs)
1636
1660
1637 def __str__(self):
1661 def __str__(self):
1638 r"""Join the URL's components back into a URL string.
1662 r"""Join the URL's components back into a URL string.
1639
1663
1640 Examples:
1664 Examples:
1641
1665
1642 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1666 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1643 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1667 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1644 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1668 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1645 'http://user:pw@host:80/?foo=bar&baz=42'
1669 'http://user:pw@host:80/?foo=bar&baz=42'
1646 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1670 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1647 'http://user:pw@host:80/?foo=bar%3dbaz'
1671 'http://user:pw@host:80/?foo=bar%3dbaz'
1648 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1672 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1649 'ssh://user:pw@[::1]:2200//home/joe#'
1673 'ssh://user:pw@[::1]:2200//home/joe#'
1650 >>> str(url('http://localhost:80//'))
1674 >>> str(url('http://localhost:80//'))
1651 'http://localhost:80//'
1675 'http://localhost:80//'
1652 >>> str(url('http://localhost:80/'))
1676 >>> str(url('http://localhost:80/'))
1653 'http://localhost:80/'
1677 'http://localhost:80/'
1654 >>> str(url('http://localhost:80'))
1678 >>> str(url('http://localhost:80'))
1655 'http://localhost:80/'
1679 'http://localhost:80/'
1656 >>> str(url('bundle:foo'))
1680 >>> str(url('bundle:foo'))
1657 'bundle:foo'
1681 'bundle:foo'
1658 >>> str(url('bundle://../foo'))
1682 >>> str(url('bundle://../foo'))
1659 'bundle:../foo'
1683 'bundle:../foo'
1660 >>> str(url('path'))
1684 >>> str(url('path'))
1661 'path'
1685 'path'
1662 >>> str(url('file:///tmp/foo/bar'))
1686 >>> str(url('file:///tmp/foo/bar'))
1663 'file:///tmp/foo/bar'
1687 'file:///tmp/foo/bar'
1664 >>> str(url('file:///c:/tmp/foo/bar'))
1688 >>> str(url('file:///c:/tmp/foo/bar'))
1665 'file:///c:/tmp/foo/bar'
1689 'file:///c:/tmp/foo/bar'
1666 >>> print url(r'bundle:foo\bar')
1690 >>> print url(r'bundle:foo\bar')
1667 bundle:foo\bar
1691 bundle:foo\bar
1668 """
1692 """
1669 if self._localpath:
1693 if self._localpath:
1670 s = self.path
1694 s = self.path
1671 if self.scheme == 'bundle':
1695 if self.scheme == 'bundle':
1672 s = 'bundle:' + s
1696 s = 'bundle:' + s
1673 if self.fragment:
1697 if self.fragment:
1674 s += '#' + self.fragment
1698 s += '#' + self.fragment
1675 return s
1699 return s
1676
1700
1677 s = self.scheme + ':'
1701 s = self.scheme + ':'
1678 if self.user or self.passwd or self.host:
1702 if self.user or self.passwd or self.host:
1679 s += '//'
1703 s += '//'
1680 elif self.scheme and (not self.path or self.path.startswith('/')
1704 elif self.scheme and (not self.path or self.path.startswith('/')
1681 or hasdriveletter(self.path)):
1705 or hasdriveletter(self.path)):
1682 s += '//'
1706 s += '//'
1683 if hasdriveletter(self.path):
1707 if hasdriveletter(self.path):
1684 s += '/'
1708 s += '/'
1685 if self.user:
1709 if self.user:
1686 s += urllib.quote(self.user, safe=self._safechars)
1710 s += urllib.quote(self.user, safe=self._safechars)
1687 if self.passwd:
1711 if self.passwd:
1688 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1712 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1689 if self.user or self.passwd:
1713 if self.user or self.passwd:
1690 s += '@'
1714 s += '@'
1691 if self.host:
1715 if self.host:
1692 if not (self.host.startswith('[') and self.host.endswith(']')):
1716 if not (self.host.startswith('[') and self.host.endswith(']')):
1693 s += urllib.quote(self.host)
1717 s += urllib.quote(self.host)
1694 else:
1718 else:
1695 s += self.host
1719 s += self.host
1696 if self.port:
1720 if self.port:
1697 s += ':' + urllib.quote(self.port)
1721 s += ':' + urllib.quote(self.port)
1698 if self.host:
1722 if self.host:
1699 s += '/'
1723 s += '/'
1700 if self.path:
1724 if self.path:
1701 # TODO: similar to the query string, we should not unescape the
1725 # TODO: similar to the query string, we should not unescape the
1702 # path when we store it, the path might contain '%2f' = '/',
1726 # path when we store it, the path might contain '%2f' = '/',
1703 # which we should *not* escape.
1727 # which we should *not* escape.
1704 s += urllib.quote(self.path, safe=self._safepchars)
1728 s += urllib.quote(self.path, safe=self._safepchars)
1705 if self.query:
1729 if self.query:
1706 # we store the query in escaped form.
1730 # we store the query in escaped form.
1707 s += '?' + self.query
1731 s += '?' + self.query
1708 if self.fragment is not None:
1732 if self.fragment is not None:
1709 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1733 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1710 return s
1734 return s
1711
1735
1712 def authinfo(self):
1736 def authinfo(self):
1713 user, passwd = self.user, self.passwd
1737 user, passwd = self.user, self.passwd
1714 try:
1738 try:
1715 self.user, self.passwd = None, None
1739 self.user, self.passwd = None, None
1716 s = str(self)
1740 s = str(self)
1717 finally:
1741 finally:
1718 self.user, self.passwd = user, passwd
1742 self.user, self.passwd = user, passwd
1719 if not self.user:
1743 if not self.user:
1720 return (s, None)
1744 return (s, None)
1721 # authinfo[1] is passed to urllib2 password manager, and its
1745 # authinfo[1] is passed to urllib2 password manager, and its
1722 # URIs must not contain credentials. The host is passed in the
1746 # URIs must not contain credentials. The host is passed in the
1723 # URIs list because Python < 2.4.3 uses only that to search for
1747 # URIs list because Python < 2.4.3 uses only that to search for
1724 # a password.
1748 # a password.
1725 return (s, (None, (s, self.host),
1749 return (s, (None, (s, self.host),
1726 self.user, self.passwd or ''))
1750 self.user, self.passwd or ''))
1727
1751
1728 def isabs(self):
1752 def isabs(self):
1729 if self.scheme and self.scheme != 'file':
1753 if self.scheme and self.scheme != 'file':
1730 return True # remote URL
1754 return True # remote URL
1731 if hasdriveletter(self.path):
1755 if hasdriveletter(self.path):
1732 return True # absolute for our purposes - can't be joined()
1756 return True # absolute for our purposes - can't be joined()
1733 if self.path.startswith(r'\\'):
1757 if self.path.startswith(r'\\'):
1734 return True # Windows UNC path
1758 return True # Windows UNC path
1735 if self.path.startswith('/'):
1759 if self.path.startswith('/'):
1736 return True # POSIX-style
1760 return True # POSIX-style
1737 return False
1761 return False
1738
1762
1739 def localpath(self):
1763 def localpath(self):
1740 if self.scheme == 'file' or self.scheme == 'bundle':
1764 if self.scheme == 'file' or self.scheme == 'bundle':
1741 path = self.path or '/'
1765 path = self.path or '/'
1742 # For Windows, we need to promote hosts containing drive
1766 # For Windows, we need to promote hosts containing drive
1743 # letters to paths with drive letters.
1767 # letters to paths with drive letters.
1744 if hasdriveletter(self._hostport):
1768 if hasdriveletter(self._hostport):
1745 path = self._hostport + '/' + self.path
1769 path = self._hostport + '/' + self.path
1746 elif (self.host is not None and self.path
1770 elif (self.host is not None and self.path
1747 and not hasdriveletter(path)):
1771 and not hasdriveletter(path)):
1748 path = '/' + path
1772 path = '/' + path
1749 return path
1773 return path
1750 return self._origpath
1774 return self._origpath
1751
1775
1752 def hasscheme(path):
1776 def hasscheme(path):
1753 return bool(url(path).scheme)
1777 return bool(url(path).scheme)
1754
1778
1755 def hasdriveletter(path):
1779 def hasdriveletter(path):
1756 return path and path[1:2] == ':' and path[0:1].isalpha()
1780 return path and path[1:2] == ':' and path[0:1].isalpha()
1757
1781
1758 def urllocalpath(path):
1782 def urllocalpath(path):
1759 return url(path, parsequery=False, parsefragment=False).localpath()
1783 return url(path, parsequery=False, parsefragment=False).localpath()
1760
1784
1761 def hidepassword(u):
1785 def hidepassword(u):
1762 '''hide user credential in a url string'''
1786 '''hide user credential in a url string'''
1763 u = url(u)
1787 u = url(u)
1764 if u.passwd:
1788 if u.passwd:
1765 u.passwd = '***'
1789 u.passwd = '***'
1766 return str(u)
1790 return str(u)
1767
1791
1768 def removeauth(u):
1792 def removeauth(u):
1769 '''remove all authentication information from a url string'''
1793 '''remove all authentication information from a url string'''
1770 u = url(u)
1794 u = url(u)
1771 u.user = u.passwd = None
1795 u.user = u.passwd = None
1772 return str(u)
1796 return str(u)
1773
1797
1774 def isatty(fd):
1798 def isatty(fd):
1775 try:
1799 try:
1776 return fd.isatty()
1800 return fd.isatty()
1777 except AttributeError:
1801 except AttributeError:
1778 return False
1802 return False
General Comments 0
You need to be logged in to leave comments. Login now