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