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