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