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