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