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