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