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