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