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