url: move URL parsing functions into util to improve startup time
The introduction of the new URL parsing code has created a startup
time regression. This is mainly due to the use of url.hasscheme() in
the ui class. It ends up importing many libraries that the url module
requires.
This fix helps marginally, but if we can get rid of the urllib import
in the URL parser all together, startup time will go back to normal.
perfstartup time before the URL refactoring (8796fb6af67e):
! wall 0.050692 comb 0.000000 user 0.000000 sys 0.000000 (best of 100)
current startup time (139fb11210bb):
! wall 0.070685 comb 0.000000 user 0.000000 sys 0.000000 (best of 100)
after this change:
! wall 0.064667 comb 0.000000 user 0.000000 sys 0.000000 (best of 100)
--- a/hgext/fetch.py Sat Apr 30 16:33:47 2011 +0200
+++ b/hgext/fetch.py Sat Apr 30 09:43:20 2011 -0700
@@ -9,7 +9,7 @@
from mercurial.i18n import _
from mercurial.node import nullid, short
-from mercurial import commands, cmdutil, hg, util, url, error
+from mercurial import commands, cmdutil, hg, util, error
from mercurial.lock import release
def fetch(ui, repo, source='default', **opts):
@@ -66,7 +66,7 @@
other = hg.repository(hg.remoteui(repo, opts),
ui.expandpath(source))
ui.status(_('pulling from %s\n') %
- url.hidepassword(ui.expandpath(source)))
+ util.hidepassword(ui.expandpath(source)))
revs = None
if opts['rev']:
try:
@@ -125,7 +125,7 @@
# we don't translate commit messages
message = (cmdutil.logmessage(opts) or
('Automated merge with %s' %
- url.removeauth(other.url())))
+ util.removeauth(other.url())))
editor = cmdutil.commiteditor
if opts.get('force_editor') or opts.get('edit'):
editor = cmdutil.commitforceeditor
--- a/hgext/patchbomb.py Sat Apr 30 16:33:47 2011 +0200
+++ b/hgext/patchbomb.py Sat Apr 30 09:43:20 2011 -0700
@@ -48,7 +48,7 @@
import os, errno, socket, tempfile, cStringIO, time
import email.MIMEMultipart, email.MIMEBase
import email.Utils, email.Encoders, email.Generator
-from mercurial import cmdutil, commands, hg, mail, patch, util, discovery, url
+from mercurial import cmdutil, commands, hg, mail, patch, util, discovery
from mercurial.i18n import _
from mercurial.node import bin
@@ -239,7 +239,7 @@
dest, branches = hg.parseurl(dest)
revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
other = hg.repository(hg.remoteui(repo, opts), dest)
- ui.status(_('comparing with %s\n') % url.hidepassword(dest))
+ ui.status(_('comparing with %s\n') % util.hidepassword(dest))
common, _anyinc, _heads = discovery.findcommonincoming(repo, other)
nodes = revs and map(repo.lookup, revs) or revs
o = repo.changelog.findmissing(common, heads=nodes)
--- a/hgext/schemes.py Sat Apr 30 16:33:47 2011 +0200
+++ b/hgext/schemes.py Sat Apr 30 09:43:20 2011 -0700
@@ -41,7 +41,7 @@
"""
import os, re
-from mercurial import extensions, hg, templater, url as urlmod, util
+from mercurial import extensions, hg, templater, util
from mercurial.i18n import _
@@ -95,4 +95,4 @@
'letter %s:\\\n') % (scheme, scheme.upper()))
hg.schemes[scheme] = ShortRepository(url, scheme, t)
- extensions.wrapfunction(urlmod, 'hasdriveletter', hasdriveletter)
+ extensions.wrapfunction(util, 'hasdriveletter', hasdriveletter)
--- a/mercurial/bundlerepo.py Sat Apr 30 16:33:47 2011 +0200
+++ b/mercurial/bundlerepo.py Sat Apr 30 09:43:20 2011 -0700
@@ -15,7 +15,7 @@
from i18n import _
import os, struct, tempfile, shutil
import changegroup, util, mdiff, discovery
-import localrepo, changelog, manifest, filelog, revlog, error, url
+import localrepo, changelog, manifest, filelog, revlog, error
class bundlerevlog(revlog.revlog):
def __init__(self, opener, indexfile, bundle,
@@ -274,7 +274,7 @@
cwd = os.path.join(cwd,'')
if parentpath.startswith(cwd):
parentpath = parentpath[len(cwd):]
- u = url.url(path)
+ u = util.url(path)
path = u.localpath()
if u.scheme == 'bundle':
s = path.split("+", 1)
--- a/mercurial/commands.py Sat Apr 30 16:33:47 2011 +0200
+++ b/mercurial/commands.py Sat Apr 30 09:43:20 2011 -0700
@@ -2607,7 +2607,7 @@
if 'bookmarks' not in other.listkeys('namespaces'):
ui.warn(_("remote doesn't support bookmarks\n"))
return 0
- ui.status(_('comparing with %s\n') % url.hidepassword(source))
+ ui.status(_('comparing with %s\n') % util.hidepassword(source))
return bookmarks.diff(ui, repo, other)
ret = hg.incoming(ui, repo, source, opts)
@@ -2894,7 +2894,7 @@
if 'bookmarks' not in other.listkeys('namespaces'):
ui.warn(_("remote doesn't support bookmarks\n"))
return 0
- ui.status(_('comparing with %s\n') % url.hidepassword(dest))
+ ui.status(_('comparing with %s\n') % util.hidepassword(dest))
return bookmarks.diff(ui, other, repo)
ret = hg.outgoing(ui, repo, dest, opts)
@@ -2968,13 +2968,13 @@
if search:
for name, path in ui.configitems("paths"):
if name == search:
- ui.write("%s\n" % url.hidepassword(path))
+ ui.write("%s\n" % util.hidepassword(path))
return
ui.warn(_("not found!\n"))
return 1
else:
for name, path in ui.configitems("paths"):
- ui.write("%s = %s\n" % (name, url.hidepassword(path)))
+ ui.write("%s = %s\n" % (name, util.hidepassword(path)))
def postincoming(ui, repo, modheads, optupdate, checkout):
if modheads == 0:
@@ -3017,7 +3017,7 @@
"""
source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
other = hg.repository(hg.remoteui(repo, opts), source)
- ui.status(_('pulling from %s\n') % url.hidepassword(source))
+ ui.status(_('pulling from %s\n') % util.hidepassword(source))
revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
if opts.get('bookmark'):
@@ -3100,7 +3100,7 @@
dest = ui.expandpath(dest or 'default-push', dest or 'default')
dest, branches = hg.parseurl(dest, opts.get('branch'))
- ui.status(_('pushing to %s\n') % url.hidepassword(dest))
+ ui.status(_('pushing to %s\n') % util.hidepassword(dest))
revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
other = hg.repository(hg.remoteui(repo, opts), dest)
if revs:
@@ -3919,7 +3919,7 @@
source, branches = hg.parseurl(ui.expandpath('default'))
other = hg.repository(hg.remoteui(repo, {}), source)
revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
- ui.debug('comparing with %s\n' % url.hidepassword(source))
+ ui.debug('comparing with %s\n' % util.hidepassword(source))
repo.ui.pushbuffer()
common, incoming, rheads = discovery.findcommonincoming(repo, other)
repo.ui.popbuffer()
@@ -3929,7 +3929,7 @@
dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
other = hg.repository(hg.remoteui(repo, {}), dest)
- ui.debug('comparing with %s\n' % url.hidepassword(dest))
+ ui.debug('comparing with %s\n' % util.hidepassword(dest))
repo.ui.pushbuffer()
common, _anyinc, _heads = discovery.findcommonincoming(repo, other)
repo.ui.popbuffer()
--- a/mercurial/hg.py Sat Apr 30 16:33:47 2011 +0200
+++ b/mercurial/hg.py Sat Apr 30 09:43:20 2011 -0700
@@ -11,13 +11,13 @@
from node import hex, nullid
import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo, bookmarks
import lock, util, extensions, error, node
-import cmdutil, discovery, url
+import cmdutil, discovery
import merge as mergemod
import verify as verifymod
import errno, os, shutil
def _local(path):
- path = util.expandpath(url.localpath(path))
+ path = util.expandpath(util.localpath(path))
return (os.path.isfile(path) and bundlerepo or localrepo)
def addbranchrevs(lrepo, repo, branches, revs):
@@ -54,7 +54,7 @@
def parseurl(path, branches=None):
'''parse url#branch, returning (url, (branch, branches))'''
- u = url.url(path)
+ u = util.url(path)
branch = None
if u.fragment:
branch = u.fragment
@@ -71,7 +71,7 @@
}
def _lookup(path):
- u = url.url(path)
+ u = util.url(path)
scheme = u.scheme or 'file'
thing = schemes.get(scheme) or schemes['file']
try:
@@ -221,8 +221,8 @@
else:
dest = ui.expandpath(dest)
- dest = url.localpath(dest)
- source = url.localpath(source)
+ dest = util.localpath(dest)
+ source = util.localpath(source)
if os.path.exists(dest):
if not os.path.isdir(dest):
@@ -248,7 +248,7 @@
abspath = origsource
copy = False
if src_repo.cancopy() and islocal(dest):
- abspath = os.path.abspath(url.localpath(origsource))
+ abspath = os.path.abspath(util.localpath(origsource))
copy = not pull and not rev
if copy:
@@ -421,7 +421,7 @@
"""
source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
other = repository(remoteui(repo, opts), source)
- ui.status(_('comparing with %s\n') % url.hidepassword(source))
+ ui.status(_('comparing with %s\n') % util.hidepassword(source))
revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
if revs:
@@ -477,7 +477,7 @@
def _outgoing(ui, repo, dest, opts):
dest = ui.expandpath(dest or 'default-push', dest or 'default')
dest, branches = parseurl(dest, opts.get('branch'))
- ui.status(_('comparing with %s\n') % url.hidepassword(dest))
+ ui.status(_('comparing with %s\n') % util.hidepassword(dest))
revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
if revs:
revs = [repo.lookup(rev) for rev in revs]
--- a/mercurial/hgweb/hgwebdir_mod.py Sat Apr 30 16:33:47 2011 +0200
+++ b/mercurial/hgweb/hgwebdir_mod.py Sat Apr 30 09:43:20 2011 -0700
@@ -9,7 +9,7 @@
import os, re, time
from mercurial.i18n import _
from mercurial import ui, hg, scmutil, util, templater
-from mercurial import error, encoding, url
+from mercurial import error, encoding
from common import ErrorResponse, get_mtime, staticfile, paritygen, \
get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
from hgweb_mod import hgweb
@@ -364,7 +364,7 @@
def updatereqenv(self, env):
if self._baseurl is not None:
- u = url.url(self._baseurl)
+ u = util.url(self._baseurl)
env['SERVER_NAME'] = u.host
if u.port:
env['SERVER_PORT'] = u.port
--- a/mercurial/httprepo.py Sat Apr 30 16:33:47 2011 +0200
+++ b/mercurial/httprepo.py Sat Apr 30 09:43:20 2011 -0700
@@ -28,7 +28,7 @@
self.path = path
self.caps = None
self.handler = None
- u = url.url(path)
+ u = util.url(path)
if u.query or u.fragment:
raise util.Abort(_('unsupported URL component: "%s"') %
(u.query or u.fragment))
@@ -111,12 +111,12 @@
except AttributeError:
proto = resp.headers['content-type']
- safeurl = url.hidepassword(self._url)
+ safeurl = util.hidepassword(self._url)
# accept old "text/plain" and "application/hg-changegroup" for now
if not (proto.startswith('application/mercurial-') or
proto.startswith('text/plain') or
proto.startswith('application/hg-changegroup')):
- self.ui.debug("requested URL: '%s'\n" % url.hidepassword(cu))
+ self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu))
raise error.RepoError(
_("'%s' does not appear to be an hg repository:\n"
"---%%<--- (%s)\n%s\n---%%<---\n")
--- a/mercurial/localrepo.py Sat Apr 30 16:33:47 2011 +0200
+++ b/mercurial/localrepo.py Sat Apr 30 09:43:20 2011 -0700
@@ -14,7 +14,6 @@
import match as matchmod
import merge as mergemod
import tags as tagsmod
-import url as urlmod
from lock import release
import weakref, errno, os, time, inspect
propertycache = util.propertycache
@@ -1695,7 +1694,7 @@
cl.delayupdate()
oldheads = cl.heads()
- tr = self.transaction("\n".join([srctype, urlmod.hidepassword(url)]))
+ tr = self.transaction("\n".join([srctype, util.hidepassword(url)]))
try:
trp = weakref.proxy(tr)
# pull off the changeset group
@@ -1937,7 +1936,7 @@
return a
def instance(ui, path, create):
- return localrepository(ui, urlmod.localpath(path), create)
+ return localrepository(ui, util.localpath(path), create)
def islocal(path):
return True
--- a/mercurial/sshrepo.py Sat Apr 30 16:33:47 2011 +0200
+++ b/mercurial/sshrepo.py Sat Apr 30 09:43:20 2011 -0700
@@ -6,7 +6,7 @@
# GNU General Public License version 2 or any later version.
from i18n import _
-import util, error, wireproto, url
+import util, error, wireproto
class remotelock(object):
def __init__(self, repo):
@@ -23,7 +23,7 @@
self._url = path
self.ui = ui
- u = url.url(path, parsequery=False, parsefragment=False)
+ u = util.url(path, parsequery=False, parsefragment=False)
if u.scheme != 'ssh' or not u.host or u.path is None:
self._abort(error.RepoError(_("couldn't parse location %s") % path))
--- a/mercurial/statichttprepo.py Sat Apr 30 16:33:47 2011 +0200
+++ b/mercurial/statichttprepo.py Sat Apr 30 09:43:20 2011 -0700
@@ -85,7 +85,7 @@
self.ui = ui
self.root = path
- u = url.url(path.rstrip('/') + "/.hg")
+ u = util.url(path.rstrip('/') + "/.hg")
self.path, authinfo = u.authinfo()
opener = build_opener(ui, authinfo)
--- a/mercurial/subrepo.py Sat Apr 30 16:33:47 2011 +0200
+++ b/mercurial/subrepo.py Sat Apr 30 09:43:20 2011 -0700
@@ -8,7 +8,7 @@
import errno, os, re, xml.dom.minidom, shutil, posixpath
import stat, subprocess, tarfile
from i18n import _
-import config, scmutil, util, node, error, cmdutil, url, bookmarks
+import config, scmutil, util, node, error, cmdutil, bookmarks
hg = None
propertycache = util.propertycache
@@ -194,13 +194,13 @@
"""return pull/push path of repo - either based on parent repo .hgsub info
or on the top repo config. Abort or return None if no source found."""
if hasattr(repo, '_subparent'):
- source = url.url(repo._subsource)
+ source = util.url(repo._subsource)
source.path = posixpath.normpath(source.path)
if posixpath.isabs(source.path) or source.scheme:
return str(source)
parent = _abssource(repo._subparent, push, abort=False)
if parent:
- parent = url.url(parent)
+ parent = util.url(parent)
parent.path = posixpath.join(parent.path, source.path)
parent.path = posixpath.normpath(parent.path)
return str(parent)
--- a/mercurial/ui.py Sat Apr 30 16:33:47 2011 +0200
+++ b/mercurial/ui.py Sat Apr 30 09:43:20 2011 -0700
@@ -7,7 +7,7 @@
from i18n import _
import errno, getpass, os, socket, sys, tempfile, traceback
-import config, scmutil, util, error, url
+import config, scmutil, util, error
class ui(object):
def __init__(self, src=None):
@@ -111,7 +111,7 @@
% (n, p, self.configsource('paths', n)))
p = p.replace('%%', '%')
p = util.expandpath(p)
- if not url.hasscheme(p) and not os.path.isabs(p):
+ if not util.hasscheme(p) and not os.path.isabs(p):
p = os.path.normpath(os.path.join(root, p))
c.set("paths", n, p)
@@ -332,7 +332,7 @@
def expandpath(self, loc, default=None):
"""Return repository location relative to cwd or from [paths]"""
- if url.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
+ if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
return loc
path = self.config('paths', loc)
--- a/mercurial/url.py Sat Apr 30 16:33:47 2011 +0200
+++ b/mercurial/url.py Sat Apr 30 09:43:20 2011 -0700
@@ -7,273 +7,11 @@
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
-import urllib, urllib2, httplib, os, socket, cStringIO, re
+import urllib, urllib2, httplib, os, socket, cStringIO
import __builtin__
from i18n import _
import keepalive, util
-class url(object):
- """Reliable URL parser.
-
- This parses URLs and provides attributes for the following
- components:
-
- <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
-
- Missing components are set to None. The only exception is
- fragment, which is set to '' if present but empty.
-
- If parsefragment is False, fragment is included in query. If
- parsequery is False, query is included in path. If both are
- False, both fragment and query are included in path.
-
- See http://www.ietf.org/rfc/rfc2396.txt for more information.
-
- Note that for backward compatibility reasons, bundle URLs do not
- take host names. That means 'bundle://../' has a path of '../'.
-
- Examples:
-
- >>> url('http://www.ietf.org/rfc/rfc2396.txt')
- <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
- >>> url('ssh://[::1]:2200//home/joe/repo')
- <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
- >>> url('file:///home/joe/repo')
- <url scheme: 'file', path: '/home/joe/repo'>
- >>> url('bundle:foo')
- <url scheme: 'bundle', path: 'foo'>
- >>> url('bundle://../foo')
- <url scheme: 'bundle', path: '../foo'>
- >>> url('c:\\\\foo\\\\bar')
- <url path: 'c:\\\\foo\\\\bar'>
-
- Authentication credentials:
-
- >>> url('ssh://joe:xyz@x/repo')
- <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
- >>> url('ssh://joe@x/repo')
- <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
-
- Query strings and fragments:
-
- >>> url('http://host/a?b#c')
- <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
- >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
- <url scheme: 'http', host: 'host', path: 'a?b#c'>
- """
-
- _safechars = "!~*'()+"
- _safepchars = "/!~*'()+"
- _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
-
- def __init__(self, path, parsequery=True, parsefragment=True):
- # We slowly chomp away at path until we have only the path left
- self.scheme = self.user = self.passwd = self.host = None
- self.port = self.path = self.query = self.fragment = None
- self._localpath = True
- self._hostport = ''
- self._origpath = path
-
- # special case for Windows drive letters
- if hasdriveletter(path):
- self.path = path
- return
-
- # For compatibility reasons, we can't handle bundle paths as
- # normal URLS
- if path.startswith('bundle:'):
- self.scheme = 'bundle'
- path = path[7:]
- if path.startswith('//'):
- path = path[2:]
- self.path = path
- return
-
- if self._matchscheme(path):
- parts = path.split(':', 1)
- if parts[0]:
- self.scheme, path = parts
- self._localpath = False
-
- if not path:
- path = None
- if self._localpath:
- self.path = ''
- return
- else:
- if parsefragment and '#' in path:
- path, self.fragment = path.split('#', 1)
- if not path:
- path = None
- if self._localpath:
- self.path = path
- return
-
- if parsequery and '?' in path:
- path, self.query = path.split('?', 1)
- if not path:
- path = None
- if not self.query:
- self.query = None
-
- # // is required to specify a host/authority
- if path and path.startswith('//'):
- parts = path[2:].split('/', 1)
- if len(parts) > 1:
- self.host, path = parts
- path = path
- else:
- self.host = parts[0]
- path = None
- if not self.host:
- self.host = None
- if path:
- path = '/' + path
-
- if self.host and '@' in self.host:
- self.user, self.host = self.host.rsplit('@', 1)
- if ':' in self.user:
- self.user, self.passwd = self.user.split(':', 1)
- if not self.host:
- self.host = None
-
- # Don't split on colons in IPv6 addresses without ports
- if (self.host and ':' in self.host and
- not (self.host.startswith('[') and self.host.endswith(']'))):
- self._hostport = self.host
- self.host, self.port = self.host.rsplit(':', 1)
- if not self.host:
- self.host = None
-
- if (self.host and self.scheme == 'file' and
- self.host not in ('localhost', '127.0.0.1', '[::1]')):
- raise util.Abort(_('file:// URLs can only refer to localhost'))
-
- self.path = path
-
- for a in ('user', 'passwd', 'host', 'port',
- 'path', 'query', 'fragment'):
- v = getattr(self, a)
- if v is not None:
- setattr(self, a, urllib.unquote(v))
-
- def __repr__(self):
- attrs = []
- for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
- 'query', 'fragment'):
- v = getattr(self, a)
- if v is not None:
- attrs.append('%s: %r' % (a, v))
- return '<url %s>' % ', '.join(attrs)
-
- def __str__(self):
- """Join the URL's components back into a URL string.
-
- Examples:
-
- >>> str(url('http://user:pw@host:80/?foo#bar'))
- 'http://user:pw@host:80/?foo#bar'
- >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
- 'ssh://user:pw@[::1]:2200//home/joe#'
- >>> str(url('http://localhost:80//'))
- 'http://localhost:80//'
- >>> str(url('http://localhost:80/'))
- 'http://localhost:80/'
- >>> str(url('http://localhost:80'))
- 'http://localhost:80/'
- >>> str(url('bundle:foo'))
- 'bundle:foo'
- >>> str(url('bundle://../foo'))
- 'bundle:../foo'
- >>> str(url('path'))
- 'path'
- """
- if self._localpath:
- s = self.path
- if self.scheme == 'bundle':
- s = 'bundle:' + s
- if self.fragment:
- s += '#' + self.fragment
- return s
-
- s = self.scheme + ':'
- if (self.user or self.passwd or self.host or
- self.scheme and not self.path):
- s += '//'
- if self.user:
- s += urllib.quote(self.user, safe=self._safechars)
- if self.passwd:
- s += ':' + urllib.quote(self.passwd, safe=self._safechars)
- if self.user or self.passwd:
- s += '@'
- if self.host:
- if not (self.host.startswith('[') and self.host.endswith(']')):
- s += urllib.quote(self.host)
- else:
- s += self.host
- if self.port:
- s += ':' + urllib.quote(self.port)
- if self.host:
- s += '/'
- if self.path:
- s += urllib.quote(self.path, safe=self._safepchars)
- if self.query:
- s += '?' + urllib.quote(self.query, safe=self._safepchars)
- if self.fragment is not None:
- s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
- return s
-
- def authinfo(self):
- user, passwd = self.user, self.passwd
- try:
- self.user, self.passwd = None, None
- s = str(self)
- finally:
- self.user, self.passwd = user, passwd
- if not self.user:
- return (s, None)
- return (s, (None, (str(self), self.host),
- self.user, self.passwd or ''))
-
- def localpath(self):
- if self.scheme == 'file' or self.scheme == 'bundle':
- path = self.path or '/'
- # For Windows, we need to promote hosts containing drive
- # letters to paths with drive letters.
- if hasdriveletter(self._hostport):
- path = self._hostport + '/' + self.path
- elif self.host is not None and self.path:
- path = '/' + path
- # We also need to handle the case of file:///C:/, which
- # should return C:/, not /C:/.
- elif hasdriveletter(path):
- # Strip leading slash from paths with drive names
- return path[1:]
- return path
- return self._origpath
-
-def hasscheme(path):
- return bool(url(path).scheme)
-
-def hasdriveletter(path):
- return path[1:2] == ':' and path[0:1].isalpha()
-
-def localpath(path):
- return url(path, parsequery=False, parsefragment=False).localpath()
-
-def hidepassword(u):
- '''hide user credential in a url string'''
- u = url(u)
- if u.passwd:
- u.passwd = '***'
- return str(u)
-
-def removeauth(u):
- '''remove all authentication information from a url string'''
- u = url(u)
- u.user = u.passwd = None
- return str(u)
-
def readauthforuri(ui, uri):
# Read configuration
config = dict()
@@ -357,7 +95,7 @@
if not (proxyurl.startswith('http:') or
proxyurl.startswith('https:')):
proxyurl = 'http://' + proxyurl + '/'
- proxy = url(proxyurl)
+ proxy = util.url(proxyurl)
if not proxy.user:
proxy.user = ui.config("http_proxy", "user")
proxy.passwd = ui.config("http_proxy", "passwd")
@@ -545,7 +283,7 @@
new_tunnel = False
if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
- u = url(tunnel_host)
+ u = util.url(tunnel_host)
if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
h.realhostport = ':'.join([u.host, (u.port or '443')])
h.headers = req.headers.copy()
@@ -876,7 +614,7 @@
return opener
def open(ui, url_, data=None):
- u = url(url_)
+ u = util.url(url_)
if u.scheme:
u.scheme = u.scheme.lower()
url_, authinfo = u.authinfo()
--- a/mercurial/util.py Sat Apr 30 16:33:47 2011 +0200
+++ b/mercurial/util.py Sat Apr 30 09:43:20 2011 -0700
@@ -17,7 +17,7 @@
import error, osutil, encoding
import errno, re, shutil, sys, tempfile, traceback
import os, time, calendar, textwrap, unicodedata, signal
-import imp, socket
+import imp, socket, urllib
# Python compatibility
@@ -1283,3 +1283,265 @@
If s is not a valid boolean, returns None.
"""
return _booleans.get(s.lower(), None)
+
+class url(object):
+ """Reliable URL parser.
+
+ This parses URLs and provides attributes for the following
+ components:
+
+ <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
+
+ Missing components are set to None. The only exception is
+ fragment, which is set to '' if present but empty.
+
+ If parsefragment is False, fragment is included in query. If
+ parsequery is False, query is included in path. If both are
+ False, both fragment and query are included in path.
+
+ See http://www.ietf.org/rfc/rfc2396.txt for more information.
+
+ Note that for backward compatibility reasons, bundle URLs do not
+ take host names. That means 'bundle://../' has a path of '../'.
+
+ Examples:
+
+ >>> url('http://www.ietf.org/rfc/rfc2396.txt')
+ <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
+ >>> url('ssh://[::1]:2200//home/joe/repo')
+ <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
+ >>> url('file:///home/joe/repo')
+ <url scheme: 'file', path: '/home/joe/repo'>
+ >>> url('bundle:foo')
+ <url scheme: 'bundle', path: 'foo'>
+ >>> url('bundle://../foo')
+ <url scheme: 'bundle', path: '../foo'>
+ >>> url('c:\\\\foo\\\\bar')
+ <url path: 'c:\\\\foo\\\\bar'>
+
+ Authentication credentials:
+
+ >>> url('ssh://joe:xyz@x/repo')
+ <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
+ >>> url('ssh://joe@x/repo')
+ <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
+
+ Query strings and fragments:
+
+ >>> url('http://host/a?b#c')
+ <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
+ >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
+ <url scheme: 'http', host: 'host', path: 'a?b#c'>
+ """
+
+ _safechars = "!~*'()+"
+ _safepchars = "/!~*'()+"
+ _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
+
+ def __init__(self, path, parsequery=True, parsefragment=True):
+ # We slowly chomp away at path until we have only the path left
+ self.scheme = self.user = self.passwd = self.host = None
+ self.port = self.path = self.query = self.fragment = None
+ self._localpath = True
+ self._hostport = ''
+ self._origpath = path
+
+ # special case for Windows drive letters
+ if hasdriveletter(path):
+ self.path = path
+ return
+
+ # For compatibility reasons, we can't handle bundle paths as
+ # normal URLS
+ if path.startswith('bundle:'):
+ self.scheme = 'bundle'
+ path = path[7:]
+ if path.startswith('//'):
+ path = path[2:]
+ self.path = path
+ return
+
+ if self._matchscheme(path):
+ parts = path.split(':', 1)
+ if parts[0]:
+ self.scheme, path = parts
+ self._localpath = False
+
+ if not path:
+ path = None
+ if self._localpath:
+ self.path = ''
+ return
+ else:
+ if parsefragment and '#' in path:
+ path, self.fragment = path.split('#', 1)
+ if not path:
+ path = None
+ if self._localpath:
+ self.path = path
+ return
+
+ if parsequery and '?' in path:
+ path, self.query = path.split('?', 1)
+ if not path:
+ path = None
+ if not self.query:
+ self.query = None
+
+ # // is required to specify a host/authority
+ if path and path.startswith('//'):
+ parts = path[2:].split('/', 1)
+ if len(parts) > 1:
+ self.host, path = parts
+ path = path
+ else:
+ self.host = parts[0]
+ path = None
+ if not self.host:
+ self.host = None
+ if path:
+ path = '/' + path
+
+ if self.host and '@' in self.host:
+ self.user, self.host = self.host.rsplit('@', 1)
+ if ':' in self.user:
+ self.user, self.passwd = self.user.split(':', 1)
+ if not self.host:
+ self.host = None
+
+ # Don't split on colons in IPv6 addresses without ports
+ if (self.host and ':' in self.host and
+ not (self.host.startswith('[') and self.host.endswith(']'))):
+ self._hostport = self.host
+ self.host, self.port = self.host.rsplit(':', 1)
+ if not self.host:
+ self.host = None
+
+ if (self.host and self.scheme == 'file' and
+ self.host not in ('localhost', '127.0.0.1', '[::1]')):
+ raise Abort(_('file:// URLs can only refer to localhost'))
+
+ self.path = path
+
+ for a in ('user', 'passwd', 'host', 'port',
+ 'path', 'query', 'fragment'):
+ v = getattr(self, a)
+ if v is not None:
+ setattr(self, a, urllib.unquote(v))
+
+ def __repr__(self):
+ attrs = []
+ for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
+ 'query', 'fragment'):
+ v = getattr(self, a)
+ if v is not None:
+ attrs.append('%s: %r' % (a, v))
+ return '<url %s>' % ', '.join(attrs)
+
+ def __str__(self):
+ """Join the URL's components back into a URL string.
+
+ Examples:
+
+ >>> str(url('http://user:pw@host:80/?foo#bar'))
+ 'http://user:pw@host:80/?foo#bar'
+ >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
+ 'ssh://user:pw@[::1]:2200//home/joe#'
+ >>> str(url('http://localhost:80//'))
+ 'http://localhost:80//'
+ >>> str(url('http://localhost:80/'))
+ 'http://localhost:80/'
+ >>> str(url('http://localhost:80'))
+ 'http://localhost:80/'
+ >>> str(url('bundle:foo'))
+ 'bundle:foo'
+ >>> str(url('bundle://../foo'))
+ 'bundle:../foo'
+ >>> str(url('path'))
+ 'path'
+ """
+ if self._localpath:
+ s = self.path
+ if self.scheme == 'bundle':
+ s = 'bundle:' + s
+ if self.fragment:
+ s += '#' + self.fragment
+ return s
+
+ s = self.scheme + ':'
+ if (self.user or self.passwd or self.host or
+ self.scheme and not self.path):
+ s += '//'
+ if self.user:
+ s += urllib.quote(self.user, safe=self._safechars)
+ if self.passwd:
+ s += ':' + urllib.quote(self.passwd, safe=self._safechars)
+ if self.user or self.passwd:
+ s += '@'
+ if self.host:
+ if not (self.host.startswith('[') and self.host.endswith(']')):
+ s += urllib.quote(self.host)
+ else:
+ s += self.host
+ if self.port:
+ s += ':' + urllib.quote(self.port)
+ if self.host:
+ s += '/'
+ if self.path:
+ s += urllib.quote(self.path, safe=self._safepchars)
+ if self.query:
+ s += '?' + urllib.quote(self.query, safe=self._safepchars)
+ if self.fragment is not None:
+ s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
+ return s
+
+ def authinfo(self):
+ user, passwd = self.user, self.passwd
+ try:
+ self.user, self.passwd = None, None
+ s = str(self)
+ finally:
+ self.user, self.passwd = user, passwd
+ if not self.user:
+ return (s, None)
+ return (s, (None, (str(self), self.host),
+ self.user, self.passwd or ''))
+
+ def localpath(self):
+ if self.scheme == 'file' or self.scheme == 'bundle':
+ path = self.path or '/'
+ # For Windows, we need to promote hosts containing drive
+ # letters to paths with drive letters.
+ if hasdriveletter(self._hostport):
+ path = self._hostport + '/' + self.path
+ elif self.host is not None and self.path:
+ path = '/' + path
+ # We also need to handle the case of file:///C:/, which
+ # should return C:/, not /C:/.
+ elif hasdriveletter(path):
+ # Strip leading slash from paths with drive names
+ return path[1:]
+ return path
+ return self._origpath
+
+def hasscheme(path):
+ return bool(url(path).scheme)
+
+def hasdriveletter(path):
+ return path[1:2] == ':' and path[0:1].isalpha()
+
+def localpath(path):
+ return url(path, parsequery=False, parsefragment=False).localpath()
+
+def hidepassword(u):
+ '''hide user credential in a url string'''
+ u = url(u)
+ if u.passwd:
+ u.passwd = '***'
+ return str(u)
+
+def removeauth(u):
+ '''remove all authentication information from a url string'''
+ u = url(u)
+ u.user = u.passwd = None
+ return str(u)
--- a/tests/test-url.py Sat Apr 30 16:33:47 2011 +0200
+++ b/tests/test-url.py Sat Apr 30 09:43:20 2011 -0700
@@ -53,7 +53,7 @@
def test_url():
"""
- >>> from mercurial.url import url
+ >>> from mercurial.util import url
This tests for edge cases in url.URL's parsing algorithm. Most of
these aren't useful for documentation purposes, so they aren't