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