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