##// END OF EJS Templates
icasefs: disuse length check against un-normcase()-ed filenames...
FUJIWARA Katsunori -
r15718:c604a3d1 default
parent child Browse files
Show More
@@ -1,1750 +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 lenp = len(p)
626 for n in contents:
625 for n in contents:
627 if lenp == len(n) and normcase(n) == p:
626 if normcase(n) == p:
628 return n
627 return n
629 return None
628 return None
630
629
631 seps = os.sep
630 seps = os.sep
632 if os.altsep:
631 if os.altsep:
633 seps = seps + os.altsep
632 seps = seps + os.altsep
634 # Protect backslashes. This gets silly very quickly.
633 # Protect backslashes. This gets silly very quickly.
635 seps.replace('\\','\\\\')
634 seps.replace('\\','\\\\')
636 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
635 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
637 dir = os.path.normpath(root)
636 dir = os.path.normpath(root)
638 result = []
637 result = []
639 for part, sep in pattern.findall(name):
638 for part, sep in pattern.findall(name):
640 if sep:
639 if sep:
641 result.append(sep)
640 result.append(sep)
642 continue
641 continue
643
642
644 contents = _fspathcache.get(dir, None)
643 contents = _fspathcache.get(dir, None)
645 if contents is None:
644 if contents is None:
646 contents = os.listdir(dir)
645 contents = os.listdir(dir)
647 _fspathcache[dir] = contents
646 _fspathcache[dir] = contents
648
647
649 found = find(part, contents)
648 found = find(part, contents)
650 if not found:
649 if not found:
651 # retry once for the corner case: add files after dir walking
650 # retry once for the corner case: add files after dir walking
652 contents = os.listdir(dir)
651 contents = os.listdir(dir)
653 _fspathcache[dir] = contents
652 _fspathcache[dir] = contents
654 found = find(part, contents)
653 found = find(part, contents)
655
654
656 result.append(found or part)
655 result.append(found or part)
657 dir = os.path.join(dir, part)
656 dir = os.path.join(dir, part)
658
657
659 return ''.join(result)
658 return ''.join(result)
660
659
661 def checknlink(testfile):
660 def checknlink(testfile):
662 '''check whether hardlink count reporting works properly'''
661 '''check whether hardlink count reporting works properly'''
663
662
664 # 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
665 # work around issue2543 (or testfile may get lost on Samba shares)
664 # work around issue2543 (or testfile may get lost on Samba shares)
666 f1 = testfile + ".hgtmp1"
665 f1 = testfile + ".hgtmp1"
667 if os.path.lexists(f1):
666 if os.path.lexists(f1):
668 return False
667 return False
669 try:
668 try:
670 posixfile(f1, 'w').close()
669 posixfile(f1, 'w').close()
671 except IOError:
670 except IOError:
672 return False
671 return False
673
672
674 f2 = testfile + ".hgtmp2"
673 f2 = testfile + ".hgtmp2"
675 fd = None
674 fd = None
676 try:
675 try:
677 try:
676 try:
678 oslink(f1, f2)
677 oslink(f1, f2)
679 except OSError:
678 except OSError:
680 return False
679 return False
681
680
682 # nlinks() may behave differently for files on Windows shares if
681 # nlinks() may behave differently for files on Windows shares if
683 # the file is open.
682 # the file is open.
684 fd = posixfile(f2)
683 fd = posixfile(f2)
685 return nlinks(f2) > 1
684 return nlinks(f2) > 1
686 finally:
685 finally:
687 if fd is not None:
686 if fd is not None:
688 fd.close()
687 fd.close()
689 for f in (f1, f2):
688 for f in (f1, f2):
690 try:
689 try:
691 os.unlink(f)
690 os.unlink(f)
692 except OSError:
691 except OSError:
693 pass
692 pass
694
693
695 return False
694 return False
696
695
697 def endswithsep(path):
696 def endswithsep(path):
698 '''Check path ends with os.sep or os.altsep.'''
697 '''Check path ends with os.sep or os.altsep.'''
699 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)
700
699
701 def splitpath(path):
700 def splitpath(path):
702 '''Split path by os.sep.
701 '''Split path by os.sep.
703 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
704 an alternative of simple "xxx.split(os.sep)".
703 an alternative of simple "xxx.split(os.sep)".
705 It is recommended to use os.path.normpath() before using this
704 It is recommended to use os.path.normpath() before using this
706 function if need.'''
705 function if need.'''
707 return path.split(os.sep)
706 return path.split(os.sep)
708
707
709 def gui():
708 def gui():
710 '''Are we running in a GUI?'''
709 '''Are we running in a GUI?'''
711 if sys.platform == 'darwin':
710 if sys.platform == 'darwin':
712 if 'SSH_CONNECTION' in os.environ:
711 if 'SSH_CONNECTION' in os.environ:
713 # 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
714 return False
713 return False
715 elif getattr(osutil, 'isgui', None):
714 elif getattr(osutil, 'isgui', None):
716 # check if a CoreGraphics session is available
715 # check if a CoreGraphics session is available
717 return osutil.isgui()
716 return osutil.isgui()
718 else:
717 else:
719 # pure build; use a safe default
718 # pure build; use a safe default
720 return True
719 return True
721 else:
720 else:
722 return os.name == "nt" or os.environ.get("DISPLAY")
721 return os.name == "nt" or os.environ.get("DISPLAY")
723
722
724 def mktempcopy(name, emptyok=False, createmode=None):
723 def mktempcopy(name, emptyok=False, createmode=None):
725 """Create a temporary file with the same contents from name
724 """Create a temporary file with the same contents from name
726
725
727 The permission bits are copied from the original file.
726 The permission bits are copied from the original file.
728
727
729 If the temporary file is going to be truncated immediately, you
728 If the temporary file is going to be truncated immediately, you
730 can use emptyok=True as an optimization.
729 can use emptyok=True as an optimization.
731
730
732 Returns the name of the temporary file.
731 Returns the name of the temporary file.
733 """
732 """
734 d, fn = os.path.split(name)
733 d, fn = os.path.split(name)
735 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
734 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
736 os.close(fd)
735 os.close(fd)
737 # Temporary files are created with mode 0600, which is usually not
736 # Temporary files are created with mode 0600, which is usually not
738 # what we want. If the original file already exists, just copy
737 # what we want. If the original file already exists, just copy
739 # its mode. Otherwise, manually obey umask.
738 # its mode. Otherwise, manually obey umask.
740 copymode(name, temp, createmode)
739 copymode(name, temp, createmode)
741 if emptyok:
740 if emptyok:
742 return temp
741 return temp
743 try:
742 try:
744 try:
743 try:
745 ifp = posixfile(name, "rb")
744 ifp = posixfile(name, "rb")
746 except IOError, inst:
745 except IOError, inst:
747 if inst.errno == errno.ENOENT:
746 if inst.errno == errno.ENOENT:
748 return temp
747 return temp
749 if not getattr(inst, 'filename', None):
748 if not getattr(inst, 'filename', None):
750 inst.filename = name
749 inst.filename = name
751 raise
750 raise
752 ofp = posixfile(temp, "wb")
751 ofp = posixfile(temp, "wb")
753 for chunk in filechunkiter(ifp):
752 for chunk in filechunkiter(ifp):
754 ofp.write(chunk)
753 ofp.write(chunk)
755 ifp.close()
754 ifp.close()
756 ofp.close()
755 ofp.close()
757 except:
756 except:
758 try: os.unlink(temp)
757 try: os.unlink(temp)
759 except: pass
758 except: pass
760 raise
759 raise
761 return temp
760 return temp
762
761
763 class atomictempfile(object):
762 class atomictempfile(object):
764 '''writeable file object that atomically updates a file
763 '''writeable file object that atomically updates a file
765
764
766 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
767 close() when you are done writing, and atomictempfile will rename
766 close() when you are done writing, and atomictempfile will rename
768 the temporary copy to the original name, making the changes
767 the temporary copy to the original name, making the changes
769 visible. If the object is destroyed without being closed, all your
768 visible. If the object is destroyed without being closed, all your
770 writes are discarded.
769 writes are discarded.
771 '''
770 '''
772 def __init__(self, name, mode='w+b', createmode=None):
771 def __init__(self, name, mode='w+b', createmode=None):
773 self.__name = name # permanent name
772 self.__name = name # permanent name
774 self._tempname = mktempcopy(name, emptyok=('w' in mode),
773 self._tempname = mktempcopy(name, emptyok=('w' in mode),
775 createmode=createmode)
774 createmode=createmode)
776 self._fp = posixfile(self._tempname, mode)
775 self._fp = posixfile(self._tempname, mode)
777
776
778 # delegated methods
777 # delegated methods
779 self.write = self._fp.write
778 self.write = self._fp.write
780 self.fileno = self._fp.fileno
779 self.fileno = self._fp.fileno
781
780
782 def close(self):
781 def close(self):
783 if not self._fp.closed:
782 if not self._fp.closed:
784 self._fp.close()
783 self._fp.close()
785 rename(self._tempname, localpath(self.__name))
784 rename(self._tempname, localpath(self.__name))
786
785
787 def discard(self):
786 def discard(self):
788 if not self._fp.closed:
787 if not self._fp.closed:
789 try:
788 try:
790 os.unlink(self._tempname)
789 os.unlink(self._tempname)
791 except OSError:
790 except OSError:
792 pass
791 pass
793 self._fp.close()
792 self._fp.close()
794
793
795 def __del__(self):
794 def __del__(self):
796 if safehasattr(self, '_fp'): # constructor actually did something
795 if safehasattr(self, '_fp'): # constructor actually did something
797 self.discard()
796 self.discard()
798
797
799 def makedirs(name, mode=None):
798 def makedirs(name, mode=None):
800 """recursive directory creation with parent mode inheritance"""
799 """recursive directory creation with parent mode inheritance"""
801 try:
800 try:
802 os.mkdir(name)
801 os.mkdir(name)
803 except OSError, err:
802 except OSError, err:
804 if err.errno == errno.EEXIST:
803 if err.errno == errno.EEXIST:
805 return
804 return
806 if err.errno != errno.ENOENT or not name:
805 if err.errno != errno.ENOENT or not name:
807 raise
806 raise
808 parent = os.path.dirname(os.path.abspath(name))
807 parent = os.path.dirname(os.path.abspath(name))
809 if parent == name:
808 if parent == name:
810 raise
809 raise
811 makedirs(parent, mode)
810 makedirs(parent, mode)
812 os.mkdir(name)
811 os.mkdir(name)
813 if mode is not None:
812 if mode is not None:
814 os.chmod(name, mode)
813 os.chmod(name, mode)
815
814
816 def readfile(path):
815 def readfile(path):
817 fp = open(path, 'rb')
816 fp = open(path, 'rb')
818 try:
817 try:
819 return fp.read()
818 return fp.read()
820 finally:
819 finally:
821 fp.close()
820 fp.close()
822
821
823 def writefile(path, text):
822 def writefile(path, text):
824 fp = open(path, 'wb')
823 fp = open(path, 'wb')
825 try:
824 try:
826 fp.write(text)
825 fp.write(text)
827 finally:
826 finally:
828 fp.close()
827 fp.close()
829
828
830 def appendfile(path, text):
829 def appendfile(path, text):
831 fp = open(path, 'ab')
830 fp = open(path, 'ab')
832 try:
831 try:
833 fp.write(text)
832 fp.write(text)
834 finally:
833 finally:
835 fp.close()
834 fp.close()
836
835
837 class chunkbuffer(object):
836 class chunkbuffer(object):
838 """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
839 iterator over chunks of arbitrary size."""
838 iterator over chunks of arbitrary size."""
840
839
841 def __init__(self, in_iter):
840 def __init__(self, in_iter):
842 """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.
843 targetsize is how big a buffer to try to maintain."""
842 targetsize is how big a buffer to try to maintain."""
844 def splitbig(chunks):
843 def splitbig(chunks):
845 for chunk in chunks:
844 for chunk in chunks:
846 if len(chunk) > 2**20:
845 if len(chunk) > 2**20:
847 pos = 0
846 pos = 0
848 while pos < len(chunk):
847 while pos < len(chunk):
849 end = pos + 2 ** 18
848 end = pos + 2 ** 18
850 yield chunk[pos:end]
849 yield chunk[pos:end]
851 pos = end
850 pos = end
852 else:
851 else:
853 yield chunk
852 yield chunk
854 self.iter = splitbig(in_iter)
853 self.iter = splitbig(in_iter)
855 self._queue = []
854 self._queue = []
856
855
857 def read(self, l):
856 def read(self, l):
858 """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.
859 Returns less than L bytes if the iterator runs dry."""
858 Returns less than L bytes if the iterator runs dry."""
860 left = l
859 left = l
861 buf = ''
860 buf = ''
862 queue = self._queue
861 queue = self._queue
863 while left > 0:
862 while left > 0:
864 # refill the queue
863 # refill the queue
865 if not queue:
864 if not queue:
866 target = 2**18
865 target = 2**18
867 for chunk in self.iter:
866 for chunk in self.iter:
868 queue.append(chunk)
867 queue.append(chunk)
869 target -= len(chunk)
868 target -= len(chunk)
870 if target <= 0:
869 if target <= 0:
871 break
870 break
872 if not queue:
871 if not queue:
873 break
872 break
874
873
875 chunk = queue.pop(0)
874 chunk = queue.pop(0)
876 left -= len(chunk)
875 left -= len(chunk)
877 if left < 0:
876 if left < 0:
878 queue.insert(0, chunk[left:])
877 queue.insert(0, chunk[left:])
879 buf += chunk[:left]
878 buf += chunk[:left]
880 else:
879 else:
881 buf += chunk
880 buf += chunk
882
881
883 return buf
882 return buf
884
883
885 def filechunkiter(f, size=65536, limit=None):
884 def filechunkiter(f, size=65536, limit=None):
886 """Create a generator that produces the data in the file size
885 """Create a generator that produces the data in the file size
887 (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
888 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
889 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
890 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
891 requested."""
890 requested."""
892 assert size >= 0
891 assert size >= 0
893 assert limit is None or limit >= 0
892 assert limit is None or limit >= 0
894 while True:
893 while True:
895 if limit is None:
894 if limit is None:
896 nbytes = size
895 nbytes = size
897 else:
896 else:
898 nbytes = min(limit, size)
897 nbytes = min(limit, size)
899 s = nbytes and f.read(nbytes)
898 s = nbytes and f.read(nbytes)
900 if not s:
899 if not s:
901 break
900 break
902 if limit:
901 if limit:
903 limit -= len(s)
902 limit -= len(s)
904 yield s
903 yield s
905
904
906 def makedate():
905 def makedate():
907 ct = time.time()
906 ct = time.time()
908 if ct < 0:
907 if ct < 0:
909 hint = _("check your clock")
908 hint = _("check your clock")
910 raise Abort(_("negative timestamp: %d") % ct, hint=hint)
909 raise Abort(_("negative timestamp: %d") % ct, hint=hint)
911 delta = (datetime.datetime.utcfromtimestamp(ct) -
910 delta = (datetime.datetime.utcfromtimestamp(ct) -
912 datetime.datetime.fromtimestamp(ct))
911 datetime.datetime.fromtimestamp(ct))
913 tz = delta.days * 86400 + delta.seconds
912 tz = delta.days * 86400 + delta.seconds
914 return ct, tz
913 return ct, tz
915
914
916 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'):
917 """represent a (unixtime, offset) tuple as a localized time.
916 """represent a (unixtime, offset) tuple as a localized time.
918 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
919 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
920 append time zone to string."""
919 append time zone to string."""
921 t, tz = date or makedate()
920 t, tz = date or makedate()
922 if t < 0:
921 if t < 0:
923 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
922 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
924 tz = 0
923 tz = 0
925 if "%1" in format or "%2" in format:
924 if "%1" in format or "%2" in format:
926 sign = (tz > 0) and "-" or "+"
925 sign = (tz > 0) and "-" or "+"
927 minutes = abs(tz) // 60
926 minutes = abs(tz) // 60
928 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
927 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
929 format = format.replace("%2", "%02d" % (minutes % 60))
928 format = format.replace("%2", "%02d" % (minutes % 60))
930 try:
929 try:
931 t = time.gmtime(float(t) - tz)
930 t = time.gmtime(float(t) - tz)
932 except ValueError:
931 except ValueError:
933 # time was out of range
932 # time was out of range
934 t = time.gmtime(sys.maxint)
933 t = time.gmtime(sys.maxint)
935 s = time.strftime(format, t)
934 s = time.strftime(format, t)
936 return s
935 return s
937
936
938 def shortdate(date=None):
937 def shortdate(date=None):
939 """turn (timestamp, tzoff) tuple into iso 8631 date."""
938 """turn (timestamp, tzoff) tuple into iso 8631 date."""
940 return datestr(date, format='%Y-%m-%d')
939 return datestr(date, format='%Y-%m-%d')
941
940
942 def strdate(string, format, defaults=[]):
941 def strdate(string, format, defaults=[]):
943 """parse a localized time string and return a (unixtime, offset) tuple.
942 """parse a localized time string and return a (unixtime, offset) tuple.
944 if the string cannot be parsed, ValueError is raised."""
943 if the string cannot be parsed, ValueError is raised."""
945 def timezone(string):
944 def timezone(string):
946 tz = string.split()[-1]
945 tz = string.split()[-1]
947 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():
948 sign = (tz[0] == "+") and 1 or -1
947 sign = (tz[0] == "+") and 1 or -1
949 hours = int(tz[1:3])
948 hours = int(tz[1:3])
950 minutes = int(tz[3:5])
949 minutes = int(tz[3:5])
951 return -sign * (hours * 60 + minutes) * 60
950 return -sign * (hours * 60 + minutes) * 60
952 if tz == "GMT" or tz == "UTC":
951 if tz == "GMT" or tz == "UTC":
953 return 0
952 return 0
954 return None
953 return None
955
954
956 # NOTE: unixtime = localunixtime + offset
955 # NOTE: unixtime = localunixtime + offset
957 offset, date = timezone(string), string
956 offset, date = timezone(string), string
958 if offset is not None:
957 if offset is not None:
959 date = " ".join(string.split()[:-1])
958 date = " ".join(string.split()[:-1])
960
959
961 # add missing elements from defaults
960 # add missing elements from defaults
962 usenow = False # default to using biased defaults
961 usenow = False # default to using biased defaults
963 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
962 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
964 found = [True for p in part if ("%"+p) in format]
963 found = [True for p in part if ("%"+p) in format]
965 if not found:
964 if not found:
966 date += "@" + defaults[part][usenow]
965 date += "@" + defaults[part][usenow]
967 format += "@%" + part[0]
966 format += "@%" + part[0]
968 else:
967 else:
969 # We've found a specific time element, less specific time
968 # We've found a specific time element, less specific time
970 # elements are relative to today
969 # elements are relative to today
971 usenow = True
970 usenow = True
972
971
973 timetuple = time.strptime(date, format)
972 timetuple = time.strptime(date, format)
974 localunixtime = int(calendar.timegm(timetuple))
973 localunixtime = int(calendar.timegm(timetuple))
975 if offset is None:
974 if offset is None:
976 # local timezone
975 # local timezone
977 unixtime = int(time.mktime(timetuple))
976 unixtime = int(time.mktime(timetuple))
978 offset = unixtime - localunixtime
977 offset = unixtime - localunixtime
979 else:
978 else:
980 unixtime = localunixtime + offset
979 unixtime = localunixtime + offset
981 return unixtime, offset
980 return unixtime, offset
982
981
983 def parsedate(date, formats=None, bias={}):
982 def parsedate(date, formats=None, bias={}):
984 """parse a localized date/time and return a (unixtime, offset) tuple.
983 """parse a localized date/time and return a (unixtime, offset) tuple.
985
984
986 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
987 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.
988 """
987 """
989 if not date:
988 if not date:
990 return 0, 0
989 return 0, 0
991 if isinstance(date, tuple) and len(date) == 2:
990 if isinstance(date, tuple) and len(date) == 2:
992 return date
991 return date
993 if not formats:
992 if not formats:
994 formats = defaultdateformats
993 formats = defaultdateformats
995 date = date.strip()
994 date = date.strip()
996 try:
995 try:
997 when, offset = map(int, date.split(' '))
996 when, offset = map(int, date.split(' '))
998 except ValueError:
997 except ValueError:
999 # fill out defaults
998 # fill out defaults
1000 now = makedate()
999 now = makedate()
1001 defaults = {}
1000 defaults = {}
1002 for part in ("d", "mb", "yY", "HI", "M", "S"):
1001 for part in ("d", "mb", "yY", "HI", "M", "S"):
1003 # this piece is for rounding the specific end of unknowns
1002 # this piece is for rounding the specific end of unknowns
1004 b = bias.get(part)
1003 b = bias.get(part)
1005 if b is None:
1004 if b is None:
1006 if part[0] in "HMS":
1005 if part[0] in "HMS":
1007 b = "00"
1006 b = "00"
1008 else:
1007 else:
1009 b = "0"
1008 b = "0"
1010
1009
1011 # 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
1012 n = datestr(now, "%" + part[0])
1011 n = datestr(now, "%" + part[0])
1013
1012
1014 defaults[part] = (b, n)
1013 defaults[part] = (b, n)
1015
1014
1016 for format in formats:
1015 for format in formats:
1017 try:
1016 try:
1018 when, offset = strdate(date, format, defaults)
1017 when, offset = strdate(date, format, defaults)
1019 except (ValueError, OverflowError):
1018 except (ValueError, OverflowError):
1020 pass
1019 pass
1021 else:
1020 else:
1022 break
1021 break
1023 else:
1022 else:
1024 raise Abort(_('invalid date: %r') % date)
1023 raise Abort(_('invalid date: %r') % date)
1025 # validate explicit (probably user-specified) date and
1024 # validate explicit (probably user-specified) date and
1026 # time zone offset. values must fit in signed 32 bits for
1025 # time zone offset. values must fit in signed 32 bits for
1027 # current 32-bit linux runtimes. timezones go from UTC-12
1026 # current 32-bit linux runtimes. timezones go from UTC-12
1028 # to UTC+14
1027 # to UTC+14
1029 if abs(when) > 0x7fffffff:
1028 if abs(when) > 0x7fffffff:
1030 raise Abort(_('date exceeds 32 bits: %d') % when)
1029 raise Abort(_('date exceeds 32 bits: %d') % when)
1031 if when < 0:
1030 if when < 0:
1032 raise Abort(_('negative date value: %d') % when)
1031 raise Abort(_('negative date value: %d') % when)
1033 if offset < -50400 or offset > 43200:
1032 if offset < -50400 or offset > 43200:
1034 raise Abort(_('impossible time zone offset: %d') % offset)
1033 raise Abort(_('impossible time zone offset: %d') % offset)
1035 return when, offset
1034 return when, offset
1036
1035
1037 def matchdate(date):
1036 def matchdate(date):
1038 """Return a function that matches a given date match specifier
1037 """Return a function that matches a given date match specifier
1039
1038
1040 Formats include:
1039 Formats include:
1041
1040
1042 '{date}' match a given date to the accuracy provided
1041 '{date}' match a given date to the accuracy provided
1043
1042
1044 '<{date}' on or before a given date
1043 '<{date}' on or before a given date
1045
1044
1046 '>{date}' on or after a given date
1045 '>{date}' on or after a given date
1047
1046
1048 >>> p1 = parsedate("10:29:59")
1047 >>> p1 = parsedate("10:29:59")
1049 >>> p2 = parsedate("10:30:00")
1048 >>> p2 = parsedate("10:30:00")
1050 >>> p3 = parsedate("10:30:59")
1049 >>> p3 = parsedate("10:30:59")
1051 >>> p4 = parsedate("10:31:00")
1050 >>> p4 = parsedate("10:31:00")
1052 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1051 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1053 >>> f = matchdate("10:30")
1052 >>> f = matchdate("10:30")
1054 >>> f(p1[0])
1053 >>> f(p1[0])
1055 False
1054 False
1056 >>> f(p2[0])
1055 >>> f(p2[0])
1057 True
1056 True
1058 >>> f(p3[0])
1057 >>> f(p3[0])
1059 True
1058 True
1060 >>> f(p4[0])
1059 >>> f(p4[0])
1061 False
1060 False
1062 >>> f(p5[0])
1061 >>> f(p5[0])
1063 False
1062 False
1064 """
1063 """
1065
1064
1066 def lower(date):
1065 def lower(date):
1067 d = dict(mb="1", d="1")
1066 d = dict(mb="1", d="1")
1068 return parsedate(date, extendeddateformats, d)[0]
1067 return parsedate(date, extendeddateformats, d)[0]
1069
1068
1070 def upper(date):
1069 def upper(date):
1071 d = dict(mb="12", HI="23", M="59", S="59")
1070 d = dict(mb="12", HI="23", M="59", S="59")
1072 for days in ("31", "30", "29"):
1071 for days in ("31", "30", "29"):
1073 try:
1072 try:
1074 d["d"] = days
1073 d["d"] = days
1075 return parsedate(date, extendeddateformats, d)[0]
1074 return parsedate(date, extendeddateformats, d)[0]
1076 except:
1075 except:
1077 pass
1076 pass
1078 d["d"] = "28"
1077 d["d"] = "28"
1079 return parsedate(date, extendeddateformats, d)[0]
1078 return parsedate(date, extendeddateformats, d)[0]
1080
1079
1081 date = date.strip()
1080 date = date.strip()
1082
1081
1083 if not date:
1082 if not date:
1084 raise Abort(_("dates cannot consist entirely of whitespace"))
1083 raise Abort(_("dates cannot consist entirely of whitespace"))
1085 elif date[0] == "<":
1084 elif date[0] == "<":
1086 if not date[1:]:
1085 if not date[1:]:
1087 raise Abort(_("invalid day spec, use '<DATE'"))
1086 raise Abort(_("invalid day spec, use '<DATE'"))
1088 when = upper(date[1:])
1087 when = upper(date[1:])
1089 return lambda x: x <= when
1088 return lambda x: x <= when
1090 elif date[0] == ">":
1089 elif date[0] == ">":
1091 if not date[1:]:
1090 if not date[1:]:
1092 raise Abort(_("invalid day spec, use '>DATE'"))
1091 raise Abort(_("invalid day spec, use '>DATE'"))
1093 when = lower(date[1:])
1092 when = lower(date[1:])
1094 return lambda x: x >= when
1093 return lambda x: x >= when
1095 elif date[0] == "-":
1094 elif date[0] == "-":
1096 try:
1095 try:
1097 days = int(date[1:])
1096 days = int(date[1:])
1098 except ValueError:
1097 except ValueError:
1099 raise Abort(_("invalid day spec: %s") % date[1:])
1098 raise Abort(_("invalid day spec: %s") % date[1:])
1100 if days < 0:
1099 if days < 0:
1101 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1100 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1102 % date[1:])
1101 % date[1:])
1103 when = makedate()[0] - days * 3600 * 24
1102 when = makedate()[0] - days * 3600 * 24
1104 return lambda x: x >= when
1103 return lambda x: x >= when
1105 elif " to " in date:
1104 elif " to " in date:
1106 a, b = date.split(" to ")
1105 a, b = date.split(" to ")
1107 start, stop = lower(a), upper(b)
1106 start, stop = lower(a), upper(b)
1108 return lambda x: x >= start and x <= stop
1107 return lambda x: x >= start and x <= stop
1109 else:
1108 else:
1110 start, stop = lower(date), upper(date)
1109 start, stop = lower(date), upper(date)
1111 return lambda x: x >= start and x <= stop
1110 return lambda x: x >= start and x <= stop
1112
1111
1113 def shortuser(user):
1112 def shortuser(user):
1114 """Return a short representation of a user name or email address."""
1113 """Return a short representation of a user name or email address."""
1115 f = user.find('@')
1114 f = user.find('@')
1116 if f >= 0:
1115 if f >= 0:
1117 user = user[:f]
1116 user = user[:f]
1118 f = user.find('<')
1117 f = user.find('<')
1119 if f >= 0:
1118 if f >= 0:
1120 user = user[f + 1:]
1119 user = user[f + 1:]
1121 f = user.find(' ')
1120 f = user.find(' ')
1122 if f >= 0:
1121 if f >= 0:
1123 user = user[:f]
1122 user = user[:f]
1124 f = user.find('.')
1123 f = user.find('.')
1125 if f >= 0:
1124 if f >= 0:
1126 user = user[:f]
1125 user = user[:f]
1127 return user
1126 return user
1128
1127
1129 def email(author):
1128 def email(author):
1130 '''get email of author.'''
1129 '''get email of author.'''
1131 r = author.find('>')
1130 r = author.find('>')
1132 if r == -1:
1131 if r == -1:
1133 r = None
1132 r = None
1134 return author[author.find('<') + 1:r]
1133 return author[author.find('<') + 1:r]
1135
1134
1136 def _ellipsis(text, maxlength):
1135 def _ellipsis(text, maxlength):
1137 if len(text) <= maxlength:
1136 if len(text) <= maxlength:
1138 return text, False
1137 return text, False
1139 else:
1138 else:
1140 return "%s..." % (text[:maxlength - 3]), True
1139 return "%s..." % (text[:maxlength - 3]), True
1141
1140
1142 def ellipsis(text, maxlength=400):
1141 def ellipsis(text, maxlength=400):
1143 """Trim string to at most maxlength (default: 400) characters."""
1142 """Trim string to at most maxlength (default: 400) characters."""
1144 try:
1143 try:
1145 # use unicode not to split at intermediate multi-byte sequence
1144 # use unicode not to split at intermediate multi-byte sequence
1146 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1145 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1147 maxlength)
1146 maxlength)
1148 if not truncated:
1147 if not truncated:
1149 return text
1148 return text
1150 return utext.encode(encoding.encoding)
1149 return utext.encode(encoding.encoding)
1151 except (UnicodeDecodeError, UnicodeEncodeError):
1150 except (UnicodeDecodeError, UnicodeEncodeError):
1152 return _ellipsis(text, maxlength)[0]
1151 return _ellipsis(text, maxlength)[0]
1153
1152
1154 def bytecount(nbytes):
1153 def bytecount(nbytes):
1155 '''return byte count formatted as readable string, with units'''
1154 '''return byte count formatted as readable string, with units'''
1156
1155
1157 units = (
1156 units = (
1158 (100, 1 << 30, _('%.0f GB')),
1157 (100, 1 << 30, _('%.0f GB')),
1159 (10, 1 << 30, _('%.1f GB')),
1158 (10, 1 << 30, _('%.1f GB')),
1160 (1, 1 << 30, _('%.2f GB')),
1159 (1, 1 << 30, _('%.2f GB')),
1161 (100, 1 << 20, _('%.0f MB')),
1160 (100, 1 << 20, _('%.0f MB')),
1162 (10, 1 << 20, _('%.1f MB')),
1161 (10, 1 << 20, _('%.1f MB')),
1163 (1, 1 << 20, _('%.2f MB')),
1162 (1, 1 << 20, _('%.2f MB')),
1164 (100, 1 << 10, _('%.0f KB')),
1163 (100, 1 << 10, _('%.0f KB')),
1165 (10, 1 << 10, _('%.1f KB')),
1164 (10, 1 << 10, _('%.1f KB')),
1166 (1, 1 << 10, _('%.2f KB')),
1165 (1, 1 << 10, _('%.2f KB')),
1167 (1, 1, _('%.0f bytes')),
1166 (1, 1, _('%.0f bytes')),
1168 )
1167 )
1169
1168
1170 for multiplier, divisor, format in units:
1169 for multiplier, divisor, format in units:
1171 if nbytes >= divisor * multiplier:
1170 if nbytes >= divisor * multiplier:
1172 return format % (nbytes / float(divisor))
1171 return format % (nbytes / float(divisor))
1173 return units[-1][2] % nbytes
1172 return units[-1][2] % nbytes
1174
1173
1175 def uirepr(s):
1174 def uirepr(s):
1176 # Avoid double backslash in Windows path repr()
1175 # Avoid double backslash in Windows path repr()
1177 return repr(s).replace('\\\\', '\\')
1176 return repr(s).replace('\\\\', '\\')
1178
1177
1179 # delay import of textwrap
1178 # delay import of textwrap
1180 def MBTextWrapper(**kwargs):
1179 def MBTextWrapper(**kwargs):
1181 class tw(textwrap.TextWrapper):
1180 class tw(textwrap.TextWrapper):
1182 """
1181 """
1183 Extend TextWrapper for width-awareness.
1182 Extend TextWrapper for width-awareness.
1184
1183
1185 Neither number of 'bytes' in any encoding nor 'characters' is
1184 Neither number of 'bytes' in any encoding nor 'characters' is
1186 appropriate to calculate terminal columns for specified string.
1185 appropriate to calculate terminal columns for specified string.
1187
1186
1188 Original TextWrapper implementation uses built-in 'len()' directly,
1187 Original TextWrapper implementation uses built-in 'len()' directly,
1189 so overriding is needed to use width information of each characters.
1188 so overriding is needed to use width information of each characters.
1190
1189
1191 In addition, characters classified into 'ambiguous' width are
1190 In addition, characters classified into 'ambiguous' width are
1192 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.
1193
1192
1194 This requires use decision to determine width of such characters.
1193 This requires use decision to determine width of such characters.
1195 """
1194 """
1196 def __init__(self, **kwargs):
1195 def __init__(self, **kwargs):
1197 textwrap.TextWrapper.__init__(self, **kwargs)
1196 textwrap.TextWrapper.__init__(self, **kwargs)
1198
1197
1199 # for compatibility between 2.4 and 2.6
1198 # for compatibility between 2.4 and 2.6
1200 if getattr(self, 'drop_whitespace', None) is None:
1199 if getattr(self, 'drop_whitespace', None) is None:
1201 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1200 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1202
1201
1203 def _cutdown(self, ucstr, space_left):
1202 def _cutdown(self, ucstr, space_left):
1204 l = 0
1203 l = 0
1205 colwidth = encoding.ucolwidth
1204 colwidth = encoding.ucolwidth
1206 for i in xrange(len(ucstr)):
1205 for i in xrange(len(ucstr)):
1207 l += colwidth(ucstr[i])
1206 l += colwidth(ucstr[i])
1208 if space_left < l:
1207 if space_left < l:
1209 return (ucstr[:i], ucstr[i:])
1208 return (ucstr[:i], ucstr[i:])
1210 return ucstr, ''
1209 return ucstr, ''
1211
1210
1212 # overriding of base class
1211 # overriding of base class
1213 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):
1214 space_left = max(width - cur_len, 1)
1213 space_left = max(width - cur_len, 1)
1215
1214
1216 if self.break_long_words:
1215 if self.break_long_words:
1217 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1216 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1218 cur_line.append(cut)
1217 cur_line.append(cut)
1219 reversed_chunks[-1] = res
1218 reversed_chunks[-1] = res
1220 elif not cur_line:
1219 elif not cur_line:
1221 cur_line.append(reversed_chunks.pop())
1220 cur_line.append(reversed_chunks.pop())
1222
1221
1223 # this overriding code is imported from TextWrapper of python 2.6
1222 # this overriding code is imported from TextWrapper of python 2.6
1224 # to calculate columns of string by 'encoding.ucolwidth()'
1223 # to calculate columns of string by 'encoding.ucolwidth()'
1225 def _wrap_chunks(self, chunks):
1224 def _wrap_chunks(self, chunks):
1226 colwidth = encoding.ucolwidth
1225 colwidth = encoding.ucolwidth
1227
1226
1228 lines = []
1227 lines = []
1229 if self.width <= 0:
1228 if self.width <= 0:
1230 raise ValueError("invalid width %r (must be > 0)" % self.width)
1229 raise ValueError("invalid width %r (must be > 0)" % self.width)
1231
1230
1232 # Arrange in reverse order so items can be efficiently popped
1231 # Arrange in reverse order so items can be efficiently popped
1233 # from a stack of chucks.
1232 # from a stack of chucks.
1234 chunks.reverse()
1233 chunks.reverse()
1235
1234
1236 while chunks:
1235 while chunks:
1237
1236
1238 # 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.
1239 # 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.
1240 cur_line = []
1239 cur_line = []
1241 cur_len = 0
1240 cur_len = 0
1242
1241
1243 # Figure out which static string will prefix this line.
1242 # Figure out which static string will prefix this line.
1244 if lines:
1243 if lines:
1245 indent = self.subsequent_indent
1244 indent = self.subsequent_indent
1246 else:
1245 else:
1247 indent = self.initial_indent
1246 indent = self.initial_indent
1248
1247
1249 # Maximum width for this line.
1248 # Maximum width for this line.
1250 width = self.width - len(indent)
1249 width = self.width - len(indent)
1251
1250
1252 # First chunk on line is whitespace -- drop it, unless this
1251 # First chunk on line is whitespace -- drop it, unless this
1253 # 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).
1254 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1253 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1255 del chunks[-1]
1254 del chunks[-1]
1256
1255
1257 while chunks:
1256 while chunks:
1258 l = colwidth(chunks[-1])
1257 l = colwidth(chunks[-1])
1259
1258
1260 # Can at least squeeze this chunk onto the current line.
1259 # Can at least squeeze this chunk onto the current line.
1261 if cur_len + l <= width:
1260 if cur_len + l <= width:
1262 cur_line.append(chunks.pop())
1261 cur_line.append(chunks.pop())
1263 cur_len += l
1262 cur_len += l
1264
1263
1265 # Nope, this line is full.
1264 # Nope, this line is full.
1266 else:
1265 else:
1267 break
1266 break
1268
1267
1269 # 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
1270 # fit on *any* line (not just this one).
1269 # fit on *any* line (not just this one).
1271 if chunks and colwidth(chunks[-1]) > width:
1270 if chunks and colwidth(chunks[-1]) > width:
1272 self._handle_long_word(chunks, cur_line, cur_len, width)
1271 self._handle_long_word(chunks, cur_line, cur_len, width)
1273
1272
1274 # 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.
1275 if (self.drop_whitespace and
1274 if (self.drop_whitespace and
1276 cur_line and cur_line[-1].strip() == ''):
1275 cur_line and cur_line[-1].strip() == ''):
1277 del cur_line[-1]
1276 del cur_line[-1]
1278
1277
1279 # 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
1280 # of all lines (return value).
1279 # of all lines (return value).
1281 if cur_line:
1280 if cur_line:
1282 lines.append(indent + ''.join(cur_line))
1281 lines.append(indent + ''.join(cur_line))
1283
1282
1284 return lines
1283 return lines
1285
1284
1286 global MBTextWrapper
1285 global MBTextWrapper
1287 MBTextWrapper = tw
1286 MBTextWrapper = tw
1288 return tw(**kwargs)
1287 return tw(**kwargs)
1289
1288
1290 def wrap(line, width, initindent='', hangindent=''):
1289 def wrap(line, width, initindent='', hangindent=''):
1291 maxindent = max(len(hangindent), len(initindent))
1290 maxindent = max(len(hangindent), len(initindent))
1292 if width <= maxindent:
1291 if width <= maxindent:
1293 # adjust for weird terminal size
1292 # adjust for weird terminal size
1294 width = max(78, maxindent + 1)
1293 width = max(78, maxindent + 1)
1295 line = line.decode(encoding.encoding, encoding.encodingmode)
1294 line = line.decode(encoding.encoding, encoding.encodingmode)
1296 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1295 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1297 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1296 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1298 wrapper = MBTextWrapper(width=width,
1297 wrapper = MBTextWrapper(width=width,
1299 initial_indent=initindent,
1298 initial_indent=initindent,
1300 subsequent_indent=hangindent)
1299 subsequent_indent=hangindent)
1301 return wrapper.fill(line).encode(encoding.encoding)
1300 return wrapper.fill(line).encode(encoding.encoding)
1302
1301
1303 def iterlines(iterator):
1302 def iterlines(iterator):
1304 for chunk in iterator:
1303 for chunk in iterator:
1305 for line in chunk.splitlines():
1304 for line in chunk.splitlines():
1306 yield line
1305 yield line
1307
1306
1308 def expandpath(path):
1307 def expandpath(path):
1309 return os.path.expanduser(os.path.expandvars(path))
1308 return os.path.expanduser(os.path.expandvars(path))
1310
1309
1311 def hgcmd():
1310 def hgcmd():
1312 """Return the command used to execute current hg
1311 """Return the command used to execute current hg
1313
1312
1314 This is different from hgexecutable() because on Windows we want
1313 This is different from hgexecutable() because on Windows we want
1315 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
1316 get either the python call or current executable.
1315 get either the python call or current executable.
1317 """
1316 """
1318 if mainfrozen():
1317 if mainfrozen():
1319 return [sys.executable]
1318 return [sys.executable]
1320 return gethgcmd()
1319 return gethgcmd()
1321
1320
1322 def rundetached(args, condfn):
1321 def rundetached(args, condfn):
1323 """Execute the argument list in a detached process.
1322 """Execute the argument list in a detached process.
1324
1323
1325 condfn is a callable which is called repeatedly and should return
1324 condfn is a callable which is called repeatedly and should return
1326 True once the child process is known to have started successfully.
1325 True once the child process is known to have started successfully.
1327 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
1328 process fails to start or finishes before condfn() evaluates to
1327 process fails to start or finishes before condfn() evaluates to
1329 True, return -1.
1328 True, return -1.
1330 """
1329 """
1331 # Windows case is easier because the child process is either
1330 # Windows case is easier because the child process is either
1332 # successfully starting and validating the condition or exiting
1331 # successfully starting and validating the condition or exiting
1333 # 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
1334 # 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
1335 # 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
1336 # running process on success. Instead we listen for SIGCHLD telling
1335 # running process on success. Instead we listen for SIGCHLD telling
1337 # us our child process terminated.
1336 # us our child process terminated.
1338 terminated = set()
1337 terminated = set()
1339 def handler(signum, frame):
1338 def handler(signum, frame):
1340 terminated.add(os.wait())
1339 terminated.add(os.wait())
1341 prevhandler = None
1340 prevhandler = None
1342 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1341 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1343 if SIGCHLD is not None:
1342 if SIGCHLD is not None:
1344 prevhandler = signal.signal(SIGCHLD, handler)
1343 prevhandler = signal.signal(SIGCHLD, handler)
1345 try:
1344 try:
1346 pid = spawndetached(args)
1345 pid = spawndetached(args)
1347 while not condfn():
1346 while not condfn():
1348 if ((pid in terminated or not testpid(pid))
1347 if ((pid in terminated or not testpid(pid))
1349 and not condfn()):
1348 and not condfn()):
1350 return -1
1349 return -1
1351 time.sleep(0.1)
1350 time.sleep(0.1)
1352 return pid
1351 return pid
1353 finally:
1352 finally:
1354 if prevhandler is not None:
1353 if prevhandler is not None:
1355 signal.signal(signal.SIGCHLD, prevhandler)
1354 signal.signal(signal.SIGCHLD, prevhandler)
1356
1355
1357 try:
1356 try:
1358 any, all = any, all
1357 any, all = any, all
1359 except NameError:
1358 except NameError:
1360 def any(iterable):
1359 def any(iterable):
1361 for i in iterable:
1360 for i in iterable:
1362 if i:
1361 if i:
1363 return True
1362 return True
1364 return False
1363 return False
1365
1364
1366 def all(iterable):
1365 def all(iterable):
1367 for i in iterable:
1366 for i in iterable:
1368 if not i:
1367 if not i:
1369 return False
1368 return False
1370 return True
1369 return True
1371
1370
1372 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1371 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1373 """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.
1374
1373
1375 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
1376 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
1377 a regular expression.
1376 a regular expression.
1378
1377
1379 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
1380 just before replacement.
1379 just before replacement.
1381
1380
1382 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
1383 its escaping.
1382 its escaping.
1384 """
1383 """
1385 fn = fn or (lambda s: s)
1384 fn = fn or (lambda s: s)
1386 patterns = '|'.join(mapping.keys())
1385 patterns = '|'.join(mapping.keys())
1387 if escape_prefix:
1386 if escape_prefix:
1388 patterns += '|' + prefix
1387 patterns += '|' + prefix
1389 if len(prefix) > 1:
1388 if len(prefix) > 1:
1390 prefix_char = prefix[1:]
1389 prefix_char = prefix[1:]
1391 else:
1390 else:
1392 prefix_char = prefix
1391 prefix_char = prefix
1393 mapping[prefix_char] = prefix_char
1392 mapping[prefix_char] = prefix_char
1394 r = re.compile(r'%s(%s)' % (prefix, patterns))
1393 r = re.compile(r'%s(%s)' % (prefix, patterns))
1395 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1394 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1396
1395
1397 def getport(port):
1396 def getport(port):
1398 """Return the port for a given network service.
1397 """Return the port for a given network service.
1399
1398
1400 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
1401 looked up using socket.getservbyname(). If there's no matching
1400 looked up using socket.getservbyname(). If there's no matching
1402 service, util.Abort is raised.
1401 service, util.Abort is raised.
1403 """
1402 """
1404 try:
1403 try:
1405 return int(port)
1404 return int(port)
1406 except ValueError:
1405 except ValueError:
1407 pass
1406 pass
1408
1407
1409 try:
1408 try:
1410 return socket.getservbyname(port)
1409 return socket.getservbyname(port)
1411 except socket.error:
1410 except socket.error:
1412 raise Abort(_("no port number associated with service '%s'") % port)
1411 raise Abort(_("no port number associated with service '%s'") % port)
1413
1412
1414 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1413 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1415 '0': False, 'no': False, 'false': False, 'off': False,
1414 '0': False, 'no': False, 'false': False, 'off': False,
1416 'never': False}
1415 'never': False}
1417
1416
1418 def parsebool(s):
1417 def parsebool(s):
1419 """Parse s into a boolean.
1418 """Parse s into a boolean.
1420
1419
1421 If s is not a valid boolean, returns None.
1420 If s is not a valid boolean, returns None.
1422 """
1421 """
1423 return _booleans.get(s.lower(), None)
1422 return _booleans.get(s.lower(), None)
1424
1423
1425 _hexdig = '0123456789ABCDEFabcdef'
1424 _hexdig = '0123456789ABCDEFabcdef'
1426 _hextochr = dict((a + b, chr(int(a + b, 16)))
1425 _hextochr = dict((a + b, chr(int(a + b, 16)))
1427 for a in _hexdig for b in _hexdig)
1426 for a in _hexdig for b in _hexdig)
1428
1427
1429 def _urlunquote(s):
1428 def _urlunquote(s):
1430 """unquote('abc%20def') -> 'abc def'."""
1429 """unquote('abc%20def') -> 'abc def'."""
1431 res = s.split('%')
1430 res = s.split('%')
1432 # fastpath
1431 # fastpath
1433 if len(res) == 1:
1432 if len(res) == 1:
1434 return s
1433 return s
1435 s = res[0]
1434 s = res[0]
1436 for item in res[1:]:
1435 for item in res[1:]:
1437 try:
1436 try:
1438 s += _hextochr[item[:2]] + item[2:]
1437 s += _hextochr[item[:2]] + item[2:]
1439 except KeyError:
1438 except KeyError:
1440 s += '%' + item
1439 s += '%' + item
1441 except UnicodeDecodeError:
1440 except UnicodeDecodeError:
1442 s += unichr(int(item[:2], 16)) + item[2:]
1441 s += unichr(int(item[:2], 16)) + item[2:]
1443 return s
1442 return s
1444
1443
1445 class url(object):
1444 class url(object):
1446 r"""Reliable URL parser.
1445 r"""Reliable URL parser.
1447
1446
1448 This parses URLs and provides attributes for the following
1447 This parses URLs and provides attributes for the following
1449 components:
1448 components:
1450
1449
1451 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1450 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1452
1451
1453 Missing components are set to None. The only exception is
1452 Missing components are set to None. The only exception is
1454 fragment, which is set to '' if present but empty.
1453 fragment, which is set to '' if present but empty.
1455
1454
1456 If parsefragment is False, fragment is included in query. If
1455 If parsefragment is False, fragment is included in query. If
1457 parsequery is False, query is included in path. If both are
1456 parsequery is False, query is included in path. If both are
1458 False, both fragment and query are included in path.
1457 False, both fragment and query are included in path.
1459
1458
1460 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1459 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1461
1460
1462 Note that for backward compatibility reasons, bundle URLs do not
1461 Note that for backward compatibility reasons, bundle URLs do not
1463 take host names. That means 'bundle://../' has a path of '../'.
1462 take host names. That means 'bundle://../' has a path of '../'.
1464
1463
1465 Examples:
1464 Examples:
1466
1465
1467 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1466 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1468 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1467 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1469 >>> url('ssh://[::1]:2200//home/joe/repo')
1468 >>> url('ssh://[::1]:2200//home/joe/repo')
1470 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1469 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1471 >>> url('file:///home/joe/repo')
1470 >>> url('file:///home/joe/repo')
1472 <url scheme: 'file', path: '/home/joe/repo'>
1471 <url scheme: 'file', path: '/home/joe/repo'>
1473 >>> url('file:///c:/temp/foo/')
1472 >>> url('file:///c:/temp/foo/')
1474 <url scheme: 'file', path: 'c:/temp/foo/'>
1473 <url scheme: 'file', path: 'c:/temp/foo/'>
1475 >>> url('bundle:foo')
1474 >>> url('bundle:foo')
1476 <url scheme: 'bundle', path: 'foo'>
1475 <url scheme: 'bundle', path: 'foo'>
1477 >>> url('bundle://../foo')
1476 >>> url('bundle://../foo')
1478 <url scheme: 'bundle', path: '../foo'>
1477 <url scheme: 'bundle', path: '../foo'>
1479 >>> url(r'c:\foo\bar')
1478 >>> url(r'c:\foo\bar')
1480 <url path: 'c:\\foo\\bar'>
1479 <url path: 'c:\\foo\\bar'>
1481 >>> url(r'\\blah\blah\blah')
1480 >>> url(r'\\blah\blah\blah')
1482 <url path: '\\\\blah\\blah\\blah'>
1481 <url path: '\\\\blah\\blah\\blah'>
1483 >>> url(r'\\blah\blah\blah#baz')
1482 >>> url(r'\\blah\blah\blah#baz')
1484 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1483 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1485
1484
1486 Authentication credentials:
1485 Authentication credentials:
1487
1486
1488 >>> url('ssh://joe:xyz@x/repo')
1487 >>> url('ssh://joe:xyz@x/repo')
1489 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1488 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1490 >>> url('ssh://joe@x/repo')
1489 >>> url('ssh://joe@x/repo')
1491 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1490 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1492
1491
1493 Query strings and fragments:
1492 Query strings and fragments:
1494
1493
1495 >>> url('http://host/a?b#c')
1494 >>> url('http://host/a?b#c')
1496 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1495 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1497 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1496 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1498 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1497 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1499 """
1498 """
1500
1499
1501 _safechars = "!~*'()+"
1500 _safechars = "!~*'()+"
1502 _safepchars = "/!~*'()+:"
1501 _safepchars = "/!~*'()+:"
1503 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1502 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1504
1503
1505 def __init__(self, path, parsequery=True, parsefragment=True):
1504 def __init__(self, path, parsequery=True, parsefragment=True):
1506 # 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
1507 self.scheme = self.user = self.passwd = self.host = None
1506 self.scheme = self.user = self.passwd = self.host = None
1508 self.port = self.path = self.query = self.fragment = None
1507 self.port = self.path = self.query = self.fragment = None
1509 self._localpath = True
1508 self._localpath = True
1510 self._hostport = ''
1509 self._hostport = ''
1511 self._origpath = path
1510 self._origpath = path
1512
1511
1513 if parsefragment and '#' in path:
1512 if parsefragment and '#' in path:
1514 path, self.fragment = path.split('#', 1)
1513 path, self.fragment = path.split('#', 1)
1515 if not path:
1514 if not path:
1516 path = None
1515 path = None
1517
1516
1518 # special case for Windows drive letters and UNC paths
1517 # special case for Windows drive letters and UNC paths
1519 if hasdriveletter(path) or path.startswith(r'\\'):
1518 if hasdriveletter(path) or path.startswith(r'\\'):
1520 self.path = path
1519 self.path = path
1521 return
1520 return
1522
1521
1523 # For compatibility reasons, we can't handle bundle paths as
1522 # For compatibility reasons, we can't handle bundle paths as
1524 # normal URLS
1523 # normal URLS
1525 if path.startswith('bundle:'):
1524 if path.startswith('bundle:'):
1526 self.scheme = 'bundle'
1525 self.scheme = 'bundle'
1527 path = path[7:]
1526 path = path[7:]
1528 if path.startswith('//'):
1527 if path.startswith('//'):
1529 path = path[2:]
1528 path = path[2:]
1530 self.path = path
1529 self.path = path
1531 return
1530 return
1532
1531
1533 if self._matchscheme(path):
1532 if self._matchscheme(path):
1534 parts = path.split(':', 1)
1533 parts = path.split(':', 1)
1535 if parts[0]:
1534 if parts[0]:
1536 self.scheme, path = parts
1535 self.scheme, path = parts
1537 self._localpath = False
1536 self._localpath = False
1538
1537
1539 if not path:
1538 if not path:
1540 path = None
1539 path = None
1541 if self._localpath:
1540 if self._localpath:
1542 self.path = ''
1541 self.path = ''
1543 return
1542 return
1544 else:
1543 else:
1545 if self._localpath:
1544 if self._localpath:
1546 self.path = path
1545 self.path = path
1547 return
1546 return
1548
1547
1549 if parsequery and '?' in path:
1548 if parsequery and '?' in path:
1550 path, self.query = path.split('?', 1)
1549 path, self.query = path.split('?', 1)
1551 if not path:
1550 if not path:
1552 path = None
1551 path = None
1553 if not self.query:
1552 if not self.query:
1554 self.query = None
1553 self.query = None
1555
1554
1556 # // is required to specify a host/authority
1555 # // is required to specify a host/authority
1557 if path and path.startswith('//'):
1556 if path and path.startswith('//'):
1558 parts = path[2:].split('/', 1)
1557 parts = path[2:].split('/', 1)
1559 if len(parts) > 1:
1558 if len(parts) > 1:
1560 self.host, path = parts
1559 self.host, path = parts
1561 path = path
1560 path = path
1562 else:
1561 else:
1563 self.host = parts[0]
1562 self.host = parts[0]
1564 path = None
1563 path = None
1565 if not self.host:
1564 if not self.host:
1566 self.host = None
1565 self.host = None
1567 # path of file:///d is /d
1566 # path of file:///d is /d
1568 # path of file:///d:/ is d:/, not /d:/
1567 # path of file:///d:/ is d:/, not /d:/
1569 if path and not hasdriveletter(path):
1568 if path and not hasdriveletter(path):
1570 path = '/' + path
1569 path = '/' + path
1571
1570
1572 if self.host and '@' in self.host:
1571 if self.host and '@' in self.host:
1573 self.user, self.host = self.host.rsplit('@', 1)
1572 self.user, self.host = self.host.rsplit('@', 1)
1574 if ':' in self.user:
1573 if ':' in self.user:
1575 self.user, self.passwd = self.user.split(':', 1)
1574 self.user, self.passwd = self.user.split(':', 1)
1576 if not self.host:
1575 if not self.host:
1577 self.host = None
1576 self.host = None
1578
1577
1579 # Don't split on colons in IPv6 addresses without ports
1578 # Don't split on colons in IPv6 addresses without ports
1580 if (self.host and ':' in self.host and
1579 if (self.host and ':' in self.host and
1581 not (self.host.startswith('[') and self.host.endswith(']'))):
1580 not (self.host.startswith('[') and self.host.endswith(']'))):
1582 self._hostport = self.host
1581 self._hostport = self.host
1583 self.host, self.port = self.host.rsplit(':', 1)
1582 self.host, self.port = self.host.rsplit(':', 1)
1584 if not self.host:
1583 if not self.host:
1585 self.host = None
1584 self.host = None
1586
1585
1587 if (self.host and self.scheme == 'file' and
1586 if (self.host and self.scheme == 'file' and
1588 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1587 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1589 raise Abort(_('file:// URLs can only refer to localhost'))
1588 raise Abort(_('file:// URLs can only refer to localhost'))
1590
1589
1591 self.path = path
1590 self.path = path
1592
1591
1593 # leave the query string escaped
1592 # leave the query string escaped
1594 for a in ('user', 'passwd', 'host', 'port',
1593 for a in ('user', 'passwd', 'host', 'port',
1595 'path', 'fragment'):
1594 'path', 'fragment'):
1596 v = getattr(self, a)
1595 v = getattr(self, a)
1597 if v is not None:
1596 if v is not None:
1598 setattr(self, a, _urlunquote(v))
1597 setattr(self, a, _urlunquote(v))
1599
1598
1600 def __repr__(self):
1599 def __repr__(self):
1601 attrs = []
1600 attrs = []
1602 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1601 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1603 'query', 'fragment'):
1602 'query', 'fragment'):
1604 v = getattr(self, a)
1603 v = getattr(self, a)
1605 if v is not None:
1604 if v is not None:
1606 attrs.append('%s: %r' % (a, v))
1605 attrs.append('%s: %r' % (a, v))
1607 return '<url %s>' % ', '.join(attrs)
1606 return '<url %s>' % ', '.join(attrs)
1608
1607
1609 def __str__(self):
1608 def __str__(self):
1610 r"""Join the URL's components back into a URL string.
1609 r"""Join the URL's components back into a URL string.
1611
1610
1612 Examples:
1611 Examples:
1613
1612
1614 >>> 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'))
1615 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1614 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1616 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1615 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1617 'http://user:pw@host:80/?foo=bar&baz=42'
1616 'http://user:pw@host:80/?foo=bar&baz=42'
1618 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1617 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1619 'http://user:pw@host:80/?foo=bar%3dbaz'
1618 'http://user:pw@host:80/?foo=bar%3dbaz'
1620 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1619 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1621 'ssh://user:pw@[::1]:2200//home/joe#'
1620 'ssh://user:pw@[::1]:2200//home/joe#'
1622 >>> str(url('http://localhost:80//'))
1621 >>> str(url('http://localhost:80//'))
1623 'http://localhost:80//'
1622 'http://localhost:80//'
1624 >>> str(url('http://localhost:80/'))
1623 >>> str(url('http://localhost:80/'))
1625 'http://localhost:80/'
1624 'http://localhost:80/'
1626 >>> str(url('http://localhost:80'))
1625 >>> str(url('http://localhost:80'))
1627 'http://localhost:80/'
1626 'http://localhost:80/'
1628 >>> str(url('bundle:foo'))
1627 >>> str(url('bundle:foo'))
1629 'bundle:foo'
1628 'bundle:foo'
1630 >>> str(url('bundle://../foo'))
1629 >>> str(url('bundle://../foo'))
1631 'bundle:../foo'
1630 'bundle:../foo'
1632 >>> str(url('path'))
1631 >>> str(url('path'))
1633 'path'
1632 'path'
1634 >>> str(url('file:///tmp/foo/bar'))
1633 >>> str(url('file:///tmp/foo/bar'))
1635 'file:///tmp/foo/bar'
1634 'file:///tmp/foo/bar'
1636 >>> str(url('file:///c:/tmp/foo/bar'))
1635 >>> str(url('file:///c:/tmp/foo/bar'))
1637 'file:///c:/tmp/foo/bar'
1636 'file:///c:/tmp/foo/bar'
1638 >>> print url(r'bundle:foo\bar')
1637 >>> print url(r'bundle:foo\bar')
1639 bundle:foo\bar
1638 bundle:foo\bar
1640 """
1639 """
1641 if self._localpath:
1640 if self._localpath:
1642 s = self.path
1641 s = self.path
1643 if self.scheme == 'bundle':
1642 if self.scheme == 'bundle':
1644 s = 'bundle:' + s
1643 s = 'bundle:' + s
1645 if self.fragment:
1644 if self.fragment:
1646 s += '#' + self.fragment
1645 s += '#' + self.fragment
1647 return s
1646 return s
1648
1647
1649 s = self.scheme + ':'
1648 s = self.scheme + ':'
1650 if self.user or self.passwd or self.host:
1649 if self.user or self.passwd or self.host:
1651 s += '//'
1650 s += '//'
1652 elif self.scheme and (not self.path or self.path.startswith('/')
1651 elif self.scheme and (not self.path or self.path.startswith('/')
1653 or hasdriveletter(self.path)):
1652 or hasdriveletter(self.path)):
1654 s += '//'
1653 s += '//'
1655 if hasdriveletter(self.path):
1654 if hasdriveletter(self.path):
1656 s += '/'
1655 s += '/'
1657 if self.user:
1656 if self.user:
1658 s += urllib.quote(self.user, safe=self._safechars)
1657 s += urllib.quote(self.user, safe=self._safechars)
1659 if self.passwd:
1658 if self.passwd:
1660 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1659 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1661 if self.user or self.passwd:
1660 if self.user or self.passwd:
1662 s += '@'
1661 s += '@'
1663 if self.host:
1662 if self.host:
1664 if not (self.host.startswith('[') and self.host.endswith(']')):
1663 if not (self.host.startswith('[') and self.host.endswith(']')):
1665 s += urllib.quote(self.host)
1664 s += urllib.quote(self.host)
1666 else:
1665 else:
1667 s += self.host
1666 s += self.host
1668 if self.port:
1667 if self.port:
1669 s += ':' + urllib.quote(self.port)
1668 s += ':' + urllib.quote(self.port)
1670 if self.host:
1669 if self.host:
1671 s += '/'
1670 s += '/'
1672 if self.path:
1671 if self.path:
1673 # TODO: similar to the query string, we should not unescape the
1672 # TODO: similar to the query string, we should not unescape the
1674 # path when we store it, the path might contain '%2f' = '/',
1673 # path when we store it, the path might contain '%2f' = '/',
1675 # which we should *not* escape.
1674 # which we should *not* escape.
1676 s += urllib.quote(self.path, safe=self._safepchars)
1675 s += urllib.quote(self.path, safe=self._safepchars)
1677 if self.query:
1676 if self.query:
1678 # we store the query in escaped form.
1677 # we store the query in escaped form.
1679 s += '?' + self.query
1678 s += '?' + self.query
1680 if self.fragment is not None:
1679 if self.fragment is not None:
1681 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1680 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1682 return s
1681 return s
1683
1682
1684 def authinfo(self):
1683 def authinfo(self):
1685 user, passwd = self.user, self.passwd
1684 user, passwd = self.user, self.passwd
1686 try:
1685 try:
1687 self.user, self.passwd = None, None
1686 self.user, self.passwd = None, None
1688 s = str(self)
1687 s = str(self)
1689 finally:
1688 finally:
1690 self.user, self.passwd = user, passwd
1689 self.user, self.passwd = user, passwd
1691 if not self.user:
1690 if not self.user:
1692 return (s, None)
1691 return (s, None)
1693 # authinfo[1] is passed to urllib2 password manager, and its
1692 # authinfo[1] is passed to urllib2 password manager, and its
1694 # URIs must not contain credentials. The host is passed in the
1693 # URIs must not contain credentials. The host is passed in the
1695 # 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
1696 # a password.
1695 # a password.
1697 return (s, (None, (s, self.host),
1696 return (s, (None, (s, self.host),
1698 self.user, self.passwd or ''))
1697 self.user, self.passwd or ''))
1699
1698
1700 def isabs(self):
1699 def isabs(self):
1701 if self.scheme and self.scheme != 'file':
1700 if self.scheme and self.scheme != 'file':
1702 return True # remote URL
1701 return True # remote URL
1703 if hasdriveletter(self.path):
1702 if hasdriveletter(self.path):
1704 return True # absolute for our purposes - can't be joined()
1703 return True # absolute for our purposes - can't be joined()
1705 if self.path.startswith(r'\\'):
1704 if self.path.startswith(r'\\'):
1706 return True # Windows UNC path
1705 return True # Windows UNC path
1707 if self.path.startswith('/'):
1706 if self.path.startswith('/'):
1708 return True # POSIX-style
1707 return True # POSIX-style
1709 return False
1708 return False
1710
1709
1711 def localpath(self):
1710 def localpath(self):
1712 if self.scheme == 'file' or self.scheme == 'bundle':
1711 if self.scheme == 'file' or self.scheme == 'bundle':
1713 path = self.path or '/'
1712 path = self.path or '/'
1714 # For Windows, we need to promote hosts containing drive
1713 # For Windows, we need to promote hosts containing drive
1715 # letters to paths with drive letters.
1714 # letters to paths with drive letters.
1716 if hasdriveletter(self._hostport):
1715 if hasdriveletter(self._hostport):
1717 path = self._hostport + '/' + self.path
1716 path = self._hostport + '/' + self.path
1718 elif (self.host is not None and self.path
1717 elif (self.host is not None and self.path
1719 and not hasdriveletter(path)):
1718 and not hasdriveletter(path)):
1720 path = '/' + path
1719 path = '/' + path
1721 return path
1720 return path
1722 return self._origpath
1721 return self._origpath
1723
1722
1724 def hasscheme(path):
1723 def hasscheme(path):
1725 return bool(url(path).scheme)
1724 return bool(url(path).scheme)
1726
1725
1727 def hasdriveletter(path):
1726 def hasdriveletter(path):
1728 return path and path[1:2] == ':' and path[0:1].isalpha()
1727 return path and path[1:2] == ':' and path[0:1].isalpha()
1729
1728
1730 def urllocalpath(path):
1729 def urllocalpath(path):
1731 return url(path, parsequery=False, parsefragment=False).localpath()
1730 return url(path, parsequery=False, parsefragment=False).localpath()
1732
1731
1733 def hidepassword(u):
1732 def hidepassword(u):
1734 '''hide user credential in a url string'''
1733 '''hide user credential in a url string'''
1735 u = url(u)
1734 u = url(u)
1736 if u.passwd:
1735 if u.passwd:
1737 u.passwd = '***'
1736 u.passwd = '***'
1738 return str(u)
1737 return str(u)
1739
1738
1740 def removeauth(u):
1739 def removeauth(u):
1741 '''remove all authentication information from a url string'''
1740 '''remove all authentication information from a url string'''
1742 u = url(u)
1741 u = url(u)
1743 u.user = u.passwd = None
1742 u.user = u.passwd = None
1744 return str(u)
1743 return str(u)
1745
1744
1746 def isatty(fd):
1745 def isatty(fd):
1747 try:
1746 try:
1748 return fd.isatty()
1747 return fd.isatty()
1749 except AttributeError:
1748 except AttributeError:
1750 return False
1749 return False
General Comments 0
You need to be logged in to leave comments. Login now