Patchwork [5,of,8] python3: handle urllib/urllib2 refactoring

login
register
mail settings
Submitter timeless@mozdev.org
Date March 29, 2016, 6:13 p.m.
Message ID <9cc313935891c1805c27.1459275187@waste.org>
Download mbox | patch
Permalink /patch/14155/
State Accepted
Headers show

Comments

timeless@mozdev.org - March 29, 2016, 6:13 p.m.
# HG changeset patch
# User timeless <timeless@mozdev.org>
# Date 1459202612 0
#      Mon Mar 28 22:03:32 2016 +0000
# Node ID 9cc313935891c1805c271ef56ec524156eccc806
# Parent  b2205d49ee6f9a8c6ce8316bce8fd25e5d593775
python3: handle urllib/urllib2 refactoring
timeless - March 30, 2016, 4:24 a.m.
There's a minor bug in this commit, if it's queued, could someone unqueue it?

request.urlencode should be parse.urlencode

On Tue, Mar 29, 2016 at 2:13 PM, timeless <timeless@mozdev.org> wrote:
> # HG changeset patch
> # User timeless <timeless@mozdev.org>
> # Date 1459202612 0
> #      Mon Mar 28 22:03:32 2016 +0000
> # Node ID 9cc313935891c1805c271ef56ec524156eccc806
> # Parent  b2205d49ee6f9a8c6ce8316bce8fd25e5d593775
> python3: handle urllib/urllib2 refactoring
>
> diff --git a/hgext/acl.py b/hgext/acl.py
> --- a/hgext/acl.py
> +++ b/hgext/acl.py
> @@ -194,7 +194,13 @@
>  from __future__ import absolute_import
>
>  import getpass
> -import urllib
> +
> +try:
> +    import urllib
> +    unquote = urllib.unquote
> +except AttributeError:
> +    import urllib.request
> +    unquote = urllib.request.unquote
>
>  from mercurial.i18n import _
>  from mercurial import (
> @@ -287,7 +293,7 @@
>      if source == 'serve' and 'url' in kwargs:
>          url = kwargs['url'].split(':')
>          if url[0] == 'remote' and url[1].startswith('http'):
> -            user = urllib.unquote(url[3])
> +            user = unquote(url[3])
>
>      if user is None:
>          user = getpass.getuser()
> diff --git a/hgext/convert/subversion.py b/hgext/convert/subversion.py
> --- a/hgext/convert/subversion.py
> +++ b/hgext/convert/subversion.py
> @@ -9,10 +9,25 @@
>  import re
>  import sys
>  import tempfile
> -import urllib
> -import urllib2
>  import xml.dom.minidom
>
> +try:
> +    import urllib2
> +    import urllib
> +    httperror = urllib2.HTTPError
> +    build_opener = urllib2.build_opener
> +    quote = urllib.quote
> +    unquote = urllib.unquote
> +    url2pathname = urllib.url2pathname
> +except ImportError:
> +    import urllib.request
> +    import urllib.error
> +    httperror = urllib.error.HTTPError
> +    build_opener = urllib.request.build_opener
> +    quote = urllib.request.quote
> +    unquote = urllib.request.unquote
> +    url2pathname = urllib.request.url2pathname
> +
>  from mercurial import (
>      encoding,
>      error,
> @@ -87,7 +102,7 @@
>          mod = '/' + parts[1]
>      return uuid, mod, revnum
>
> -def quote(s):
> +def svnquote(s):
>      # As of svn 1.7, many svn calls expect "canonical" paths. In
>      # theory, we should call svn.core.*canonicalize() on all paths
>      # before passing them to the API.  Instead, we assume the base url
> @@ -95,7 +110,7 @@
>      # so we can extend it safely with new components. The "safe"
>      # characters were taken from the "svn_uri__char_validity" table in
>      # libsvn_subr/path.c.
> -    return urllib.quote(s, "!$&'()*+,-./:=@_~")
> +    return quote(s, "!$&'()*+,-./:=@_~")
>
>  def geturl(path):
>      try:
> @@ -110,7 +125,7 @@
>          # Module URL is later compared with the repository URL returned
>          # by svn API, which is UTF-8.
>          path = encoding.tolocal(path)
> -        path = 'file://%s' % quote(path)
> +        path = 'file://%s' % svnquote(path)
>      return svn.core.svn_path_canonicalize(path)
>
>  def optrev(number):
> @@ -237,7 +252,7 @@
>          opener = urllib2.build_opener()
>          rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path))
>          data = rsp.read()
> -    except urllib2.HTTPError as inst:
> +    except httperror as inst:
>          if inst.code != 404:
>              # Except for 404 we cannot know for sure this is not an svn repo
>              ui.warn(_('svn: cannot probe remote repository, assume it could '
> @@ -261,7 +276,7 @@
>              if (os.name == 'nt' and path[:1] == '/' and path[1:2].isalpha()
>                  and path[2:6].lower() == '%3a/'):
>                  path = path[:2] + ':/' + path[6:]
> -            path = urllib.url2pathname(path)
> +            path = url2pathname(path)
>      except ValueError:
>          proto = 'file'
>          path = os.path.abspath(url)
> @@ -331,7 +346,7 @@
>              self.baseurl = svn.ra.get_repos_root(self.ra)
>              # Module is either empty or a repository path starting with
>              # a slash and not ending with a slash.
> -            self.module = urllib.unquote(self.url[len(self.baseurl):])
> +            self.module = unquote(self.url[len(self.baseurl):])
>              self.prevmodule = None
>              self.rootmodule = self.module
>              self.commits = {}
> @@ -395,7 +410,7 @@
>
>      def exists(self, path, optrev):
>          try:
> -            svn.client.ls(self.url.rstrip('/') + '/' + quote(path),
> +            svn.client.ls(self.url.rstrip('/') + '/' + svnquote(path),
>                                   optrev, False, self.ctx)
>              return True
>          except svn.core.SubversionException:
> @@ -447,7 +462,7 @@
>          # Check if branches bring a few more heads to the list
>          if branches:
>              rpath = self.url.strip('/')
> -            branchnames = svn.client.ls(rpath + '/' + quote(branches),
> +            branchnames = svn.client.ls(rpath + '/' + svnquote(branches),
>                                          rev, False, self.ctx)
>              for branch in sorted(branchnames):
>                  module = '%s/%s/%s' % (oldmodule, branches, branch)
> @@ -481,7 +496,7 @@
>          if full or not parents:
>              # Perform a full checkout on roots
>              uuid, module, revnum = revsplit(rev)
> -            entries = svn.client.ls(self.baseurl + quote(module),
> +            entries = svn.client.ls(self.baseurl + svnquote(module),
>                                      optrev(revnum), True, self.ctx)
>              files = [n for n, e in entries.iteritems()
>                       if e.kind == svn.core.svn_node_file]
> @@ -729,7 +744,7 @@
>          """Reparent the svn transport and return the previous parent."""
>          if self.prevmodule == module:
>              return module
> -        svnurl = self.baseurl + quote(module)
> +        svnurl = self.baseurl + svnquote(module)
>          prevmodule = self.prevmodule
>          if prevmodule is None:
>              prevmodule = ''
> @@ -1012,7 +1027,7 @@
>          """Enumerate all files in path at revnum, recursively."""
>          path = path.strip('/')
>          pool = svn.core.Pool()
> -        rpath = '/'.join([self.baseurl, quote(path)]).strip('/')
> +        rpath = '/'.join([self.baseurl, svnquote(path)]).strip('/')
>          entries = svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool)
>          if path:
>              path += '/'
> diff --git a/hgext/largefiles/proto.py b/hgext/largefiles/proto.py
> --- a/hgext/largefiles/proto.py
> +++ b/hgext/largefiles/proto.py
> @@ -4,9 +4,15 @@
>  # GNU General Public License version 2 or any later version.
>
>  import os
> -import urllib2
>  import re
>
> +try:
> +    import urllib2
> +    httperror = urllib2.HTTPError
> +except ImportError:
> +    import urllib.error
> +    httperror = urllib.error.HTTPError
> +
>  from mercurial import error, httppeer, util, wireproto
>  from mercurial.i18n import _
>
> @@ -140,7 +146,7 @@
>              yield result, f
>              try:
>                  yield int(f.value)
> -            except (ValueError, urllib2.HTTPError):
> +            except (ValueError, httperror):
>                  # If the server returns anything but an integer followed by a
>                  # newline, newline, it's not speaking our language; if we get
>                  # an HTTP error, we can't be sure the largefile is present;
> diff --git a/hgext/largefiles/remotestore.py b/hgext/largefiles/remotestore.py
> --- a/hgext/largefiles/remotestore.py
> +++ b/hgext/largefiles/remotestore.py
> @@ -6,7 +6,14 @@
>
>  '''remote largefile store; the base class for wirestore'''
>
> -import urllib2
> +try:
> +    import urllib2
> +    httperror = urllib2.HTTPError
> +    urlerror = urllib2.URLError
> +except ImportError:
> +    import urllib.error
> +    httperror = urllib.error.HTTPError
> +    urlerror = urllib.error.URLError
>
>  from mercurial import util, wireproto, error
>  from mercurial.i18n import _
> @@ -49,11 +56,11 @@
>      def _getfile(self, tmpfile, filename, hash):
>          try:
>              chunks = self._get(hash)
> -        except urllib2.HTTPError as e:
> +        except httperror as e:
>              # 401s get converted to error.Aborts; everything else is fine being
>              # turned into a StoreError
>              raise basestore.StoreError(filename, hash, self.url, str(e))
> -        except urllib2.URLError as e:
> +        except urlerror as e:
>              # This usually indicates a connection problem, so don't
>              # keep trying with the other files... they will probably
>              # all fail too.
> diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
> --- a/mercurial/bundle2.py
> +++ b/mercurial/bundle2.py
> @@ -152,7 +152,15 @@
>  import string
>  import struct
>  import sys
> -import urllib
> +
> +try:
> +    import urllib
> +    quote = urllib.quote
> +    unquote = urllib.unquote
> +except AttributeError:
> +    import urllib.request
> +    quote = urllib.request.quote
> +    unquote = urllib.request.unquote
>
>  from .i18n import _
>  from . import (
> @@ -467,8 +475,8 @@
>      chunks = []
>      for ca in sorted(caps):
>          vals = caps[ca]
> -        ca = urllib.quote(ca)
> -        vals = [urllib.quote(v) for v in vals]
> +        ca = quote(ca)
> +        vals = [quote(v) for v in vals]
>          if vals:
>              ca = "%s=%s" % (ca, ','.join(vals))
>          chunks.append(ca)
> @@ -570,9 +578,9 @@
>          """return a encoded version of all stream parameters"""
>          blocks = []
>          for par, value in self._params:
> -            par = urllib.quote(par)
> +            par = quote(par)
>              if value is not None:
> -                value = urllib.quote(value)
> +                value = quote(value)
>                  par = '%s=%s' % (par, value)
>              blocks.append(par)
>          return ' '.join(blocks)
> diff --git a/mercurial/byterange.py b/mercurial/byterange.py
> --- a/mercurial/byterange.py
> +++ b/mercurial/byterange.py
> @@ -26,22 +26,45 @@
>  import re
>  import socket
>  import stat
> -import urllib
> -import urllib2
>
> -addclosehook = urllib.addclosehook
> -addinfourl = urllib.addinfourl
> -splitattr = urllib.splitattr
> -splitpasswd = urllib.splitpasswd
> -splitport = urllib.splitport
> -splituser = urllib.splituser
> -unquote = urllib.unquote
> +try:
> +    import urllib2
> +    import urllib
> +    basehandler = urllib2.BaseHandler
> +    filehandler = urllib2.FileHandler
> +    ftphandler = urllib2.FTPHandler
> +    ftpwrapper = urllib.ftpwrapper
> +    urlerror = urllib2.URLError
> +    addclosehook = urllib.addclosehook
> +    addinfourl = urllib.addinfourl
> +    splitattr = urllib.splitattr
> +    splitpasswd = urllib.splitpasswd
> +    splitport = urllib.splitport
> +    splituser = urllib.splituser
> +    unquote = urllib.unquote
> +    url2pathname = urllib.url2pathname
> +except ImportError:
> +    import urllib.request
> +    import urllib.error
> +    basehandler = urllib.request.BaseHandler
> +    filehandler = urllib.request.FileHandler
> +    ftphandler = urllib.request.FTPHandler
> +    ftpwrapper = urllib.request.ftpwrapper
> +    urlerror = urllib.error.URLError
> +    addclosehook = urllib.request.addclosehook
> +    addinfourl = urllib.request.addinfourl
> +    splitattr = urllib.request.splitattr
> +    splitpasswd = urllib.request.splitpasswd
> +    splitport = urllib.request.splitport
> +    splituser = urllib.request.splituser
> +    unquote = urllib.request.unquote
> +    url2pathname = urllib.request.url2pathname
>
>  class RangeError(IOError):
>      """Error raised when an unsatisfiable range is requested."""
>      pass
>
> -class HTTPRangeHandler(urllib2.BaseHandler):
> +class HTTPRangeHandler(basehandler):
>      """Handler that enables HTTP Range headers.
>
>      This was extremely simple. The Range header is a HTTP feature to
> @@ -67,7 +90,7 @@
>
>      def http_error_206(self, req, fp, code, msg, hdrs):
>          # 206 Partial Content Response
> -        r = urllib.addinfourl(fp, hdrs, req.get_full_url())
> +        r = addinfourl(fp, hdrs, req.get_full_url())
>          r.code = code
>          r.msg = msg
>          return r
> @@ -204,7 +227,7 @@
>                  raise RangeError('Requested Range Not Satisfiable')
>              pos += bufsize
>
> -class FileRangeHandler(urllib2.FileHandler):
> +class FileRangeHandler(filehandler):
>      """FileHandler subclass that adds Range support.
>      This class handles Range headers exactly like an HTTP
>      server would.
> @@ -212,15 +235,15 @@
>      def open_local_file(self, req):
>          host = req.get_host()
>          file = req.get_selector()
> -        localfile = urllib.url2pathname(file)
> +        localfile = url2pathname(file)
>          stats = os.stat(localfile)
>          size = stats[stat.ST_SIZE]
>          modified = email.Utils.formatdate(stats[stat.ST_MTIME])
>          mtype = mimetypes.guess_type(file)[0]
>          if host:
> -            host, port = urllib.splitport(host)
> +            host, port = splitport(host)
>              if port or socket.gethostbyname(host) not in self.get_names():
> -                raise urllib2.URLError('file not on local host')
> +                raise urlerror('file not on local host')
>          fo = open(localfile,'rb')
>          brange = req.headers.get('Range', None)
>          brange = range_header_to_tuple(brange)
> @@ -236,7 +259,7 @@
>          headers = email.message_from_string(
>              'Content-Type: %s\nContent-Length: %d\nLast-Modified: %s\n' %
>              (mtype or 'text/plain', size, modified))
> -        return urllib.addinfourl(fo, headers, 'file:'+file)
> +        return addinfourl(fo, headers, 'file:'+file)
>
>
>  # FTP Range Support
> @@ -246,7 +269,7 @@
>  # follows:
>  # -- range support modifications start/end here
>
> -class FTPRangeHandler(urllib2.FTPHandler):
> +class FTPRangeHandler(ftphandler):
>      def ftp_open(self, req):
>          host = req.get_host()
>          if not host:
> @@ -270,7 +293,7 @@
>          try:
>              host = socket.gethostbyname(host)
>          except socket.error as msg:
> -            raise urllib2.URLError(msg)
> +            raise urlerror(msg)
>          path, attrs = splitattr(req.get_selector())
>          dirs = path.split('/')
>          dirs = map(unquote, dirs)
> @@ -331,10 +354,10 @@
>              raise IOError('ftp error', msg)
>
>      def connect_ftp(self, user, passwd, host, port, dirs):
> -        fw = ftpwrapper(user, passwd, host, port, dirs)
> +        fw = ftpwrapperrest(user, passwd, host, port, dirs)
>          return fw
>
> -class ftpwrapper(urllib.ftpwrapper):
> +class ftpwrapperrest(ftpwrapper):
>      # range support note:
>      # this ftpwrapper code is copied directly from
>      # urllib. The only enhancement is to add the rest
> diff --git a/mercurial/exchange.py b/mercurial/exchange.py
> --- a/mercurial/exchange.py
> +++ b/mercurial/exchange.py
> @@ -8,8 +8,20 @@
>  from __future__ import absolute_import
>
>  import errno
> -import urllib
> -import urllib2
> +
> +try:
> +    import urllib2
> +    import urllib
> +    httperror = urllib2.HTTPError
> +    urlerror = urllib2.URLError
> +    quote = urllib.quote
> +    unquote = urllib.unquote
> +except ImportError:
> +    import urllib.error
> +    httperror = urllib.error.HTTPError
> +    urlerror = urllib.error.URLError
> +    quote = urllib.request.quote
> +    unquote = urllib.request.unquote
>
>  from .i18n import _
>  from .node import (
> @@ -97,8 +109,8 @@
>                        'missing "=" in parameter: %s') % p)
>
>              key, value = p.split('=', 1)
> -            key = urllib.unquote(key)
> -            value = urllib.unquote(value)
> +            key = unquote(key)
> +            value = unquote(value)
>              params[key] = value
>
>          return version, params
> @@ -236,7 +248,7 @@
>      elif isinstance(b, streamclone.streamcloneapplier):
>          requirements = streamclone.readbundle1header(fh)[2]
>          params = 'requirements=%s' % ','.join(sorted(requirements))
> -        return 'none-packed1;%s' % urllib.quote(params)
> +        return 'none-packed1;%s' % quote(params)
>      else:
>          raise error.Abort(_('unknown bundle type: %s') % b)
>
> @@ -1469,7 +1481,7 @@
>      """return a set with appropriate options to use bundle20 during getbundle"""
>      caps = set(['HG20'])
>      capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
> -    caps.add('bundle2=' + urllib.quote(capsblob))
> +    caps.add('bundle2=' + quote(capsblob))
>      return caps
>
>  # List of names of steps to perform for a bundle2 for getbundle, order matters.
> @@ -1535,7 +1547,7 @@
>      b2caps = {}
>      for bcaps in bundlecaps:
>          if bcaps.startswith('bundle2='):
> -            blob = urllib.unquote(bcaps[len('bundle2='):])
> +            blob = unquote(bcaps[len('bundle2='):])
>              b2caps.update(bundle2.decodecaps(blob))
>      bundler = bundle2.bundle20(repo.ui, b2caps)
>
> @@ -1804,8 +1816,8 @@
>          attrs = {'URL': fields[0]}
>          for rawattr in fields[1:]:
>              key, value = rawattr.split('=', 1)
> -            key = urllib.unquote(key)
> -            value = urllib.unquote(value)
> +            key = unquote(key)
> +            value = unquote(value)
>              attrs[key] = value
>
>              # Parse BUNDLESPEC into components. This makes client-side
> diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py
> --- a/mercurial/hgweb/protocol.py
> +++ b/mercurial/hgweb/protocol.py
> @@ -8,7 +8,6 @@
>  from __future__ import absolute_import
>
>  import cgi
> -import urllib
>  import zlib
>
>  try:
> @@ -16,6 +15,13 @@
>  except ImportError:
>      import io
>
> +try:
> +    import urllib
> +    quote = urllib.quote
> +except AttributeError:
> +    import urllib.request
> +    quote = urllib.request.quote
> +
>  from .common import (
>      HTTP_OK,
>  )
> @@ -86,8 +92,8 @@
>      def _client(self):
>          return 'remote:%s:%s:%s' % (
>              self.req.env.get('wsgi.url_scheme') or 'http',
> -            urllib.quote(self.req.env.get('REMOTE_HOST', '')),
> -            urllib.quote(self.req.env.get('REMOTE_USER', '')))
> +            quote(self.req.env.get('REMOTE_HOST', '')),
> +            quote(self.req.env.get('REMOTE_USER', '')))
>
>  def iscmd(cmd):
>      return cmd in wireproto.commands
> diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
> --- a/mercurial/hgweb/server.py
> +++ b/mercurial/hgweb/server.py
> @@ -15,7 +15,13 @@
>  import socket
>  import sys
>  import traceback
> -import urllib
> +
> +try:
> +    import urllib
> +    unquote = urllib.unquote
> +except AttributeError:
> +    import urllib.request
> +    unquote = urllib.request.unquote
>
>  from ..i18n import _
>
> @@ -38,7 +44,7 @@
>          path, query = uri.split('?', 1)
>      else:
>          path, query = uri, ''
> -    return urllib.unquote(path), query
> +    return unquote(path), query
>
>  class _error_logger(object):
>      def __init__(self, handler):
> diff --git a/mercurial/httpconnection.py b/mercurial/httpconnection.py
> --- a/mercurial/httpconnection.py
> +++ b/mercurial/httpconnection.py
> @@ -13,8 +13,23 @@
>  import logging
>  import os
>  import socket
> -import urllib
> -import urllib2
> +
> +try:
> +    import urllib2
> +    import urllib
> +    httphandler = urllib2.HTTPHandler
> +    httpshandler = urllib2.HTTPSHandler
> +    abstracthttphandler = urllib2.AbstractHTTPHandler
> +    addinfourl = urllib.addinfourl
> +    urlerror = urllib2.URLError
> +except ImportError:
> +    import urllib.request
> +    import urllib.error
> +    httphandler = urllib.request.HTTPHandler
> +    httpshandler = urllib.request.HTTPSHandler
> +    abstracthttphandler = urllib.request.AbstractHTTPHandler
> +    addinfourl = urllib.request.addinfourl
> +    urlerror = urllib.error.URLError
>
>  from .i18n import _
>  from . import (
> @@ -123,10 +138,10 @@
>  # Subclass BOTH of these because otherwise urllib2 "helpfully"
>  # reinserts them since it notices we don't include any subclasses of
>  # them.
> -class http2handler(urllib2.HTTPHandler, urllib2.HTTPSHandler):
> +class http2handler(httphandler, httpshandler):
>      def __init__(self, ui, pwmgr):
>          global _configuredlogging
> -        urllib2.AbstractHTTPHandler.__init__(self)
> +        abstracthttphandler.__init__(self)
>          self.ui = ui
>          self.pwmgr = pwmgr
>          self._connections = {}
> @@ -187,7 +202,7 @@
>              proxy = None
>
>          if not host:
> -            raise urllib2.URLError('no host given')
> +            raise urlerror('no host given')
>
>          connkey = use_ssl, host, proxy
>          allconns = self._connections.get(connkey, [])
> @@ -217,13 +232,13 @@
>              h.request(req.get_method(), path, req.data, headers)
>              r = h.getresponse()
>          except socket.error as err: # XXX what error?
> -            raise urllib2.URLError(err)
> +            raise urlerror(err)
>
>          # Pick apart the HTTPResponse object to get the addinfourl
>          # object initialized properly.
>          r.recv = r.read
>
> -        resp = urllib.addinfourl(r, r.headers, req.get_full_url())
> +        resp = addinfourl(r, r.headers, req.get_full_url())
>          resp.code = r.status
>          resp.msg = r.reason
>          return resp
> diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
> --- a/mercurial/httppeer.py
> +++ b/mercurial/httppeer.py
> @@ -13,10 +13,21 @@
>  import os
>  import socket
>  import tempfile
> -import urllib
> -import urllib2
>  import zlib
>
> +try:
> +    import urllib2
> +    import urllib
> +    request = urllib2.Request
> +    httperror = urllib2.HTTPError
> +    urlencode = urllib.urlencode
> +except ImportError:
> +    import urllib.request
> +    import urllib.error
> +    request = urllib.request.Request
> +    httperror = urllib.error.HTTPError
> +    urlencode = urllib.request.urlencode
> +
>  from .i18n import _
>  from .node import nullid
>  from . import (
> @@ -59,7 +70,7 @@
>          self.ui.debug('using %s\n' % self._url)
>
>          self.urlopener = url.opener(ui, authinfo)
> -        self.requestbuilder = urllib2.Request
> +        self.requestbuilder = request
>
>      def __del__(self):
>          if self.urlopener:
> @@ -105,7 +116,7 @@
>          # object rather than a basestring
>          canmungedata = not data or isinstance(data, basestring)
>          if postargsok and canmungedata:
> -            strargs = urllib.urlencode(sorted(args.items()))
> +            strargs = urlencode(sorted(args.items()))
>              if strargs:
>                  if not data:
>                      data = strargs
> @@ -119,7 +130,7 @@
>                      headersize = int(httpheader.split(',', 1)[0])
>              if headersize > 0:
>                  # The headers can typically carry more data than the URL.
> -                encargs = urllib.urlencode(sorted(args.items()))
> +                encargs = urlencode(sorted(args.items()))
>                  headerfmt = 'X-HgArg-%s'
>                  contentlen = headersize - len(headerfmt % '000' + ': \r\n')
>                  headernum = 0
> @@ -132,7 +143,7 @@
>                  headers['Vary'] = ','.join(varyheaders)
>              else:
>                  q += sorted(args.items())
> -        qs = '?%s' % urllib.urlencode(q)
> +        qs = '?%s' % urlencode(q)
>          cu = "%s%s" % (self._url, qs)
>          size = 0
>          if util.safehasattr(data, 'length'):
> @@ -150,7 +161,7 @@
>              req.add_unredirected_header('Content-Length', '%d' % size)
>          try:
>              resp = self.urlopener.open(req)
> -        except urllib2.HTTPError as inst:
> +        except httperror as inst:
>              if inst.code == 401:
>                  raise error.Abort(_('authorization failed'))
>              raise
> diff --git a/mercurial/keepalive.py b/mercurial/keepalive.py
> --- a/mercurial/keepalive.py
> +++ b/mercurial/keepalive.py
> @@ -25,13 +25,23 @@
>
>  """An HTTP handler for urllib2 that supports HTTP 1.1 and keepalive.
>
> ->>> import urllib2
> +>>> try:
> +...     import urllib2
> +...     build_opener = urllib2.build_opener
> +...     install_opener = urllib2.install_opener
> +...     urlopen = urllib2.urlopen
> +... except ImportError:
> +...     import urllib.request
> +...     build_opener = urllib.request.build_opener
> +...     install_opener = urllib.request.install_opener
> +...     urlopen = urllib.request.urlopen
> +
>  >>> from keepalive import HTTPHandler
>  >>> keepalive_handler = HTTPHandler()
> ->>> opener = urllib2.build_opener(keepalive_handler)
> ->>> urllib2.install_opener(opener)
> +>>> opener = build_opener(keepalive_handler)
> +>>> install_opener(opener)
>  >>>
> ->>> fo = urllib2.urlopen('http://www.python.org')
> +>>> fo = urlopen('http://www.python.org')
>
>  If a connection to a given host is requested, and all of the existing
>  connections are still in use, another connection will be opened.  If
> @@ -114,7 +124,22 @@
>  import socket
>  import sys
>  import thread
> -import urllib2
> +
> +try:
> +    import urllib2
> +    build_opener = urllib2.build_opener
> +    install_opener = urllib2.install_opener
> +    urlopen = urllib2.urlopen
> +    httphandler = urllib2.HTTPHandler
> +    urlerror = urllib2.URLError
> +except ImportError:
> +    import urllib.request
> +    import urllib.error
> +    build_opener = urllib.request.build_opener
> +    install_opener = urllib.request.install_opener
> +    urlopen = urllib.request.urlopen
> +    httphandler = urllib.request.HTTPHandler
> +    urlerror = urllib.error.URLError
>
>  DEBUG = None
>
> @@ -227,7 +252,7 @@
>      def do_open(self, http_class, req):
>          host = req.get_host()
>          if not host:
> -            raise urllib2.URLError('no host given')
> +            raise urlerror('no host given')
>
>          try:
>              h = self._cm.get_ready_conn(host)
> @@ -254,7 +279,7 @@
>                  self._start_transaction(h, req)
>                  r = h.getresponse()
>          except (socket.error, httplib.HTTPException) as err:
> -            raise urllib2.URLError(err)
> +            raise urlerror(err)
>
>          # if not a persistent connection, don't try to reuse it
>          if r.will_close:
> @@ -346,14 +371,14 @@
>              else:
>                  h.putrequest('GET', req.get_selector(), **skipheaders)
>          except socket.error as err:
> -            raise urllib2.URLError(err)
> +            raise urlerror(err)
>          for k, v in headers.items():
>              h.putheader(k, v)
>          h.endheaders()
>          if req.has_data():
>              h.send(data)
>
> -class HTTPHandler(KeepAliveHandler, urllib2.HTTPHandler):
> +class HTTPHandler(KeepAliveHandler, httphandler):
>      pass
>
>  class HTTPResponse(httplib.HTTPResponse):
> @@ -593,14 +618,14 @@
>      global HANDLE_ERRORS
>      orig = HANDLE_ERRORS
>      keepalive_handler = HTTPHandler()
> -    opener = urllib2.build_opener(keepalive_handler)
> -    urllib2.install_opener(opener)
> +    opener = build_opener(keepalive_handler)
> +    install_opener(opener)
>      pos = {0: 'off', 1: 'on'}
>      for i in (0, 1):
>          print("  fancy error handling %s (HANDLE_ERRORS = %i)" % (pos[i], i))
>          HANDLE_ERRORS = i
>          try:
> -            fo = urllib2.urlopen(url)
> +            fo = urlopen(url)
>              fo.read()
>              fo.close()
>              try:
> @@ -623,25 +648,25 @@
>      format = '%25s: %s'
>
>      # first fetch the file with the normal http handler
> -    opener = urllib2.build_opener()
> -    urllib2.install_opener(opener)
> -    fo = urllib2.urlopen(url)
> +    opener = build_opener()
> +    install_opener(opener)
> +    fo = urlopen(url)
>      foo = fo.read()
>      fo.close()
>      m = md5(foo)
>      print(format % ('normal urllib', m.hexdigest()))
>
>      # now install the keepalive handler and try again
> -    opener = urllib2.build_opener(HTTPHandler())
> -    urllib2.install_opener(opener)
> +    opener = build_opener(HTTPHandler())
> +    install_opener(opener)
>
> -    fo = urllib2.urlopen(url)
> +    fo = urlopen(url)
>      foo = fo.read()
>      fo.close()
>      m = md5(foo)
>      print(format % ('keepalive read', m.hexdigest()))
>
> -    fo = urllib2.urlopen(url)
> +    fo = urlopen(url)
>      foo = ''
>      while True:
>          f = fo.readline()
> @@ -657,15 +682,15 @@
>
>      sys.stdout.write('  first using the normal urllib handlers')
>      # first use normal opener
> -    opener = urllib2.build_opener()
> -    urllib2.install_opener(opener)
> +    opener = build_opener()
> +    install_opener(opener)
>      t1 = fetch(N, url)
>      print('  TIME: %.3f s' % t1)
>
>      sys.stdout.write('  now using the keepalive handler       ')
>      # now install the keepalive handler and try again
> -    opener = urllib2.build_opener(HTTPHandler())
> -    urllib2.install_opener(opener)
> +    opener = build_opener(HTTPHandler())
> +    install_opener(opener)
>      t2 = fetch(N, url)
>      print('  TIME: %.3f s' % t2)
>      print('  improvement factor: %.2f' % (t1 / t2))
> @@ -677,7 +702,7 @@
>      for i in range(N):
>          if delay and i > 0:
>              time.sleep(delay)
> -        fo = urllib2.urlopen(url)
> +        fo = urlopen(url)
>          foo = fo.read()
>          fo.close()
>          lens.append(len(foo))
> @@ -700,7 +725,7 @@
>          info = warning = error = debug
>      DEBUG = FakeLogger()
>      print("  fetching the file to establish a connection")
> -    fo = urllib2.urlopen(url)
> +    fo = urlopen(url)
>      data1 = fo.read()
>      fo.close()
>
> @@ -714,7 +739,7 @@
>      sys.stderr.write('\r')
>
>      print("  fetching the file a second time")
> -    fo = urllib2.urlopen(url)
> +    fo = urlopen(url)
>      data2 = fo.read()
>      fo.close()
>
> diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
> --- a/mercurial/localrepo.py
> +++ b/mercurial/localrepo.py
> @@ -12,9 +12,15 @@
>  import os
>  import random
>  import time
> -import urllib
>  import weakref
>
> +try:
> +    import urllib
> +    quote = urllib.quote
> +except AttributeError:
> +    import urllib.request
> +    quote = urllib.request.quote
> +
>  from .i18n import _
>  from .node import (
>      hex,
> @@ -366,7 +372,7 @@
>          if self.ui.configbool('experimental', 'bundle2-advertise', True):
>              caps = set(caps)
>              capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
> -            caps.add('bundle2=' + urllib.quote(capsblob))
> +            caps.add('bundle2=' + quote(capsblob))
>          return caps
>
>      def _applyopenerreqs(self):
> diff --git a/mercurial/statichttprepo.py b/mercurial/statichttprepo.py
> --- a/mercurial/statichttprepo.py
> +++ b/mercurial/statichttprepo.py
> @@ -11,8 +11,21 @@
>
>  import errno
>  import os
> -import urllib
> -import urllib2
> +
> +try:
> +    import urllib2
> +    import urllib
> +    request = urllib2.Request
> +    httperror = urllib2.HTTPError
> +    quote = urllib.quote
> +    urlerror = urllib2.URLError
> +except ImportError:
> +    import urllib.request
> +    import urllib.error
> +    request = urllib.request.Request
> +    httperror = urllib.error.HTTPError
> +    quote = urllib.request.quote
> +    urlerror = urllib.error.URLError
>
>  from .i18n import _
>  from . import (
> @@ -45,7 +58,7 @@
>      def seek(self, pos):
>          self.pos = pos
>      def read(self, bytes=None):
> -        req = urllib2.Request(self.url)
> +        req = request(self.url)
>          end = ''
>          if bytes:
>              end = self.pos + bytes - 1
> @@ -56,10 +69,10 @@
>              f = self.opener.open(req)
>              data = f.read()
>              code = f.code
> -        except urllib2.HTTPError as inst:
> +        except httperror as inst:
>              num = inst.code == 404 and errno.ENOENT or None
>              raise IOError(num, inst)
> -        except urllib2.URLError as inst:
> +        except urlerror as inst:
>              raise IOError(None, inst.reason[1])
>
>          if code == 200:
> @@ -92,7 +105,7 @@
>          def __call__(self, path, mode='r', *args, **kw):
>              if mode not in ('r', 'rb'):
>                  raise IOError('Permission denied')
> -            f = "/".join((self.base, urllib.quote(path)))
> +            f = "/".join((self.base, quote(path)))
>              return httprangereader(f, urlopener)
>
>          def join(self, path):
> diff --git a/mercurial/templatefilters.py b/mercurial/templatefilters.py
> --- a/mercurial/templatefilters.py
> +++ b/mercurial/templatefilters.py
> @@ -11,7 +11,13 @@
>  import os
>  import re
>  import time
> -import urllib
> +
> +try:
> +    import urllib
> +    quote = urllib.quote
> +except AttributeError:
> +    import urllib.request
> +    quote = urllib.request.quote
>
>  from . import (
>      encoding,
> @@ -269,7 +275,7 @@
>      Forward slashes are escaped twice to prevent web servers from prematurely
>      unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
>      """
> -    return urllib.quote(text, safe='/@').replace('/', '%252F')
> +    return quote(text, safe='/@').replace('/', '%252F')
>
>  def rfc3339date(text):
>      """:rfc3339date: Date. Returns a date using the Internet date format
> @@ -342,7 +348,7 @@
>      """:urlescape: Any text. Escapes all "special" characters. For example,
>      "foo bar" becomes "foo%20bar".
>      """
> -    return urllib.quote(text)
> +    return quote(text)
>
>  def userfilter(text):
>      """:user: Any text. Returns a short representation of a user name or email
> diff --git a/mercurial/url.py b/mercurial/url.py
> --- a/mercurial/url.py
> +++ b/mercurial/url.py
> @@ -13,8 +13,39 @@
>  import httplib
>  import os
>  import socket
> -import urllib
> -import urllib2
> +
> +try:
> +    import urllib2
> +    import urllib
> +    build_opener = urllib2.build_opener
> +    pathname2url = urllib.pathname2url
> +    pyhttpbasicauthhandler = urllib2.HTTPBasicAuthHandler
> +    pyhttpdigestauthhandler = urllib2.HTTPDigestAuthHandler
> +    pyhttppasswordmgrwithdefaultrealm = urllib2.HTTPPasswordMgrWithDefaultRealm
> +    pyproxyhandler = urllib2.ProxyHandler
> +
> +    try:
> +        pyhttpshandler = urllib2.HTTPSHandler
> +        has_https = True
> +    except AttributeError:
> +        has_https = False
> +
> +except ImportError:
> +    import urllib.request
> +    import urllib.error
> +    build_opener = urllib.request.build_opener
> +    pathname2url = urllib.request.pathname2url
> +    pyhttpbasicauthhandler = urllib.request.HTTPBasicAuthHandler
> +    pyhttpdigestauthhandler = urllib.request.HTTPDigestAuthHandler
> +    pyhttppasswordmgrwithdefaultrealm = \
> +        urllib.request.HTTPPasswordMgrWithDefaultRealm
> +    pyproxyhandler = urllib.request.ProxyHandler
> +
> +    try:
> +        pyhttpshandler = urllib.request.HTTPSHandler
> +        has_https = True
> +    except AttributeError:
> +        has_https = False
>
>  try:
>      import cStringIO as io
> @@ -30,13 +61,13 @@
>      util,
>  )
>
> -class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
> +class passwordmgr(pyhttppasswordmgrwithdefaultrealm):
>      def __init__(self, ui):
> -        urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
> +        pyhttppasswordmgrwithdefaultrealm.__init__(self)
>          self.ui = ui
>
>      def find_user_password(self, realm, authuri):
> -        authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
> +        authinfo = pyhttppasswordmgrwithdefaultrealm.find_user_password(
>              self, realm, authuri)
>          user, passwd = authinfo
>          if user and passwd:
> @@ -76,10 +107,10 @@
>          self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
>
>      def find_stored_password(self, authuri):
> -        return urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
> +        return pyhttppasswordmgrwithdefaultrealm.find_user_password(
>              self, None, authuri)
>
> -class proxyhandler(urllib2.ProxyHandler):
> +class proxyhandler(pyproxyhandler):
>      def __init__(self, ui):
>          proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
>          # XXX proxyauthinfo = None
> @@ -125,7 +156,7 @@
>                  except OSError:
>                      pass
>
> -        urllib2.ProxyHandler.__init__(self, proxies)
> +        pyproxyhandler.__init__(self, proxies)
>          self.ui = ui
>
>      def proxy_open(self, req, proxy, type_):
> @@ -138,7 +169,7 @@
>              if e.startswith('.') and host.endswith(e[1:]):
>                  return None
>
> -        return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
> +        return pyproxyhandler.proxy_open(self, req, proxy, type_)
>
>  def _gen_sendfile(orgsend):
>      def _sendfile(self, data):
> @@ -152,7 +183,6 @@
>              orgsend(self, data)
>      return _sendfile
>
> -has_https = util.safehasattr(urllib2, 'HTTPSHandler')
>  if has_https:
>      try:
>          _create_connection = socket.create_connection
> @@ -361,10 +391,10 @@
>                  **sslutil.sslkwargs(self.ui, host))
>              sslutil.validator(self.ui, host)(self.sock)
>
> -    class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
> +    class httpshandler(keepalive.KeepAliveHandler, pyhttpshandler):
>          def __init__(self, ui):
>              keepalive.KeepAliveHandler.__init__(self)
> -            urllib2.HTTPSHandler.__init__(self)
> +            pyhttpshandler.__init__(self)
>              self.ui = ui
>              self.pwmgr = passwordmgr(self.ui)
>
> @@ -407,9 +437,9 @@
>              conn.ui = self.ui
>              return conn
>
> -class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
> +class httpdigestauthhandler(pyhttpdigestauthhandler):
>      def __init__(self, *args, **kwargs):
> -        urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
> +        pyhttpdigestauthhandler.__init__(self, *args, **kwargs)
>          self.retried_req = None
>
>      def reset_retry_count(self):
> @@ -423,13 +453,13 @@
>          if req is not self.retried_req:
>              self.retried_req = req
>              self.retried = 0
> -        return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
> +        return pyhttpdigestauthhandler.http_error_auth_reqed(
>                      self, auth_header, host, req, headers)
>
> -class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
> +class httpbasicauthhandler(pyhttpbasicauthhandler):
>      def __init__(self, *args, **kwargs):
>          self.auth = None
> -        urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
> +        pyhttpbasicauthhandler.__init__(self, *args, **kwargs)
>          self.retried_req = None
>
>      def http_request(self, request):
> @@ -455,7 +485,7 @@
>          if req is not self.retried_req:
>              self.retried_req = req
>              self.retried = 0
> -        return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
> +        return pyhttpbasicauthhandler.http_error_auth_reqed(
>                          self, auth_header, host, req, headers)
>
>      def retry_http_basic_auth(self, host, req, realm):
> @@ -498,7 +528,7 @@
>      handlers.extend((httpbasicauthhandler(passmgr),
>                       httpdigestauthhandler(passmgr)))
>      handlers.extend([h(ui, passmgr) for h in handlerfuncs])
> -    opener = urllib2.build_opener(*handlers)
> +    opener = build_opener(*handlers)
>
>      # 1.0 here is the _protocol_ version
>      opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
> @@ -512,6 +542,6 @@
>          url_, authinfo = u.authinfo()
>      else:
>          path = util.normpath(os.path.abspath(url_))
> -        url_ = 'file://' + urllib.pathname2url(path)
> +        url_ = 'file://' + pathname2url(path)
>          authinfo = None
>      return opener(ui, authinfo).open(url_, data)
> diff --git a/mercurial/util.py b/mercurial/util.py
> --- a/mercurial/util.py
> +++ b/mercurial/util.py
> @@ -34,9 +34,15 @@
>  import textwrap
>  import time
>  import traceback
> -import urllib
>  import zlib
>
> +try:
> +    import urllib
> +    quote = urllib.quote
> +except AttributeError:
> +    import urllib.request
> +    quote = urllib.request.quote
> +
>  from . import (
>      encoding,
>      error,
> @@ -2368,30 +2374,30 @@
>              if hasdriveletter(self.path):
>                  s += '/'
>          if self.user:
> -            s += urllib.quote(self.user, safe=self._safechars)
> +            s += quote(self.user, safe=self._safechars)
>          if self.passwd:
> -            s += ':' + urllib.quote(self.passwd, safe=self._safechars)
> +            s += ':' + 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)
> +                s += quote(self.host)
>              else:
>                  s += self.host
>          if self.port:
> -            s += ':' + urllib.quote(self.port)
> +            s += ':' + quote(self.port)
>          if self.host:
>              s += '/'
>          if self.path:
>              # TODO: similar to the query string, we should not unescape the
>              # path when we store it, the path might contain '%2f' = '/',
>              # which we should *not* escape.
> -            s += urllib.quote(self.path, safe=self._safepchars)
> +            s += quote(self.path, safe=self._safepchars)
>          if self.query:
>              # we store the query in escaped form.
>              s += '?' + self.query
>          if self.fragment is not None:
> -            s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
> +            s += '#' + quote(self.fragment, safe=self._safepchars)
>          return s
>
>      def authinfo(self):
> diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
> --- a/mercurial/wireproto.py
> +++ b/mercurial/wireproto.py
> @@ -11,7 +11,15 @@
>  import os
>  import sys
>  import tempfile
> -import urllib
> +
> +try:
> +    import urllib
> +    quote = urllib.quote
> +    unquote = urllib.unquote
> +except AttributeError:
> +    import urllib.request
> +    quote = urllib.request.quote
> +    unquote = urllib.request.unquote
>
>  from .i18n import _
>  from .node import (
> @@ -287,7 +295,7 @@
>              branchmap = {}
>              for branchpart in d.splitlines():
>                  branchname, branchheads = branchpart.split(' ', 1)
> -                branchname = encoding.tolocal(urllib.unquote(branchname))
> +                branchname = encoding.tolocal(unquote(branchname))
>                  branchheads = decodelist(branchheads)
>                  branchmap[branchname] = branchheads
>              yield branchmap
> @@ -632,7 +640,7 @@
>      branchmap = repo.branchmap()
>      heads = []
>      for branch, nodes in branchmap.iteritems():
> -        branchname = urllib.quote(encoding.fromlocal(branch))
> +        branchname = quote(encoding.fromlocal(branch))
>          branchnodes = encodelist(nodes)
>          heads.append('%s %s' % (branchname, branchnodes))
>      return '\n'.join(heads)
> @@ -684,7 +692,7 @@
>              caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
>      if repo.ui.configbool('experimental', 'bundle2-advertise', True):
>          capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
> -        caps.append('bundle2=' + urllib.quote(capsblob))
> +        caps.append('bundle2=' + quote(capsblob))
>      caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
>      caps.append(
>          'httpheader=%d' % repo.ui.configint('server', 'maxhttpheaderlen', 1024))
> diff --git a/tests/test-check-py3-compat.t b/tests/test-check-py3-compat.t
> --- a/tests/test-check-py3-compat.t
> +++ b/tests/test-check-py3-compat.t
> @@ -165,8 +165,8 @@
>    hgext/largefiles/lfutil.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
>    hgext/largefiles/localstore.py: error importing module: <ImportError> No module named 'lfutil' (line *) (glob)
>    hgext/largefiles/overrides.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
> -  hgext/largefiles/proto.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
> -  hgext/largefiles/remotestore.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
> +  hgext/largefiles/proto.py: error importing: <ImportError> No module named 'httplib' (error at httppeer.py:*) (glob)
> +  hgext/largefiles/remotestore.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
>    hgext/largefiles/reposetup.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
>    hgext/largefiles/uisetup.py: error importing module: <SyntaxError> invalid syntax (archival.py, line *) (line *) (glob)
>    hgext/largefiles/wirestore.py: error importing module: <ImportError> No module named 'lfutil' (line *) (glob)
> @@ -189,7 +189,6 @@
>    mercurial/branchmap.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
>    mercurial/bundle*.py: invalid syntax: invalid syntax (<unknown>, line *) (glob)
>    mercurial/bundlerepo.py: error importing module: <SyntaxError> invalid syntax (bundle*.py, line *) (line *) (glob)
> -  mercurial/byterange.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
>    mercurial/changegroup.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
>    mercurial/changelog.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
>    mercurial/cmdutil.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
> @@ -203,7 +202,7 @@
>    mercurial/dirstate.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
>    mercurial/discovery.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
>    mercurial/dispatch.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
> -  mercurial/exchange.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
> +  mercurial/exchange.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
>    mercurial/extensions.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
>    mercurial/filelog.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
>    mercurial/filemerge.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
> @@ -223,7 +222,7 @@
>    mercurial/hgweb/wsgicgi.py: error importing module: <SystemError> Parent module 'mercurial.hgweb' not loaded, cannot perform relative import (line *) (glob)
>    mercurial/hook.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
>    mercurial/httpclient/_readers.py: error importing module: <ImportError> No module named 'httplib' (line *) (glob)
> -  mercurial/httpconnection.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
> +  mercurial/httpconnection.py: error importing: <ImportError> No module named 'httplib' (error at __init__.py:*) (glob)
>    mercurial/httppeer.py: error importing module: <ImportError> No module named 'httplib' (line *) (glob)
>    mercurial/keepalive.py: error importing module: <ImportError> No module named 'httplib' (line *) (glob)
>    mercurial/localrepo.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
> @@ -254,7 +253,7 @@
>    mercurial/sshpeer.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
>    mercurial/sshserver.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
>    mercurial/sslutil.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
> -  mercurial/statichttprepo.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
> +  mercurial/statichttprepo.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
>    mercurial/store.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
>    mercurial/streamclone.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
>    mercurial/subrepo.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
> diff --git a/tests/test-hgweb-auth.py b/tests/test-hgweb-auth.py
> --- a/tests/test-hgweb-auth.py
> +++ b/tests/test-hgweb-auth.py
> @@ -1,5 +1,12 @@
> +try:
> +    import urllib2
> +    httppasswordmgrwithdefaultrealm = urllib2.HTTPPasswordMgrWithDefaultRealm
> +except AttributeError:
> +    import urllib.request
> +    httppasswordmgrwithdefaultrealm = \
> +        urllib.request.HTTPPasswordMgrWithDefaultRealm
> +
>  from mercurial import demandimport; demandimport.enable()
> -import urllib2
>  from mercurial import ui, util
>  from mercurial import url
>  from mercurial.error import Abort
> @@ -99,7 +106,7 @@
>
>  def testauthinfo(fullurl, authurl):
>      print 'URIs:', fullurl, authurl
> -    pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
> +    pm = httppasswordmgrwithdefaultrealm()
>      pm.add_password(*util.url(fullurl).authinfo()[1])
>      print pm.find_user_password('test', authurl)
>
>
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Pierre-Yves David - March 30, 2016, 5:14 a.m.
On 03/29/2016 09:24 PM, timeless wrote:
> There's a minor bug in this commit, if it's queued, could someone unqueue it?
>
> request.urlencode should be parse.urlencode

I've pushed the fixed version. Any idea of why the test did not caught that?
Gregory Szorc - March 30, 2016, 6:40 a.m.
On Tue, Mar 29, 2016 at 10:14 PM, Pierre-Yves David <
pierre-yves.david@ens-lyon.org> wrote:

>
>
> On 03/29/2016 09:24 PM, timeless wrote:
>
>> There's a minor bug in this commit, if it's queued, could someone unqueue
>> it?
>>
>> request.urlencode should be parse.urlencode
>>
>
> I've pushed the fixed version. Any idea of why the test did not caught
> that?


Because 90% of this code doesn't run under Python 3 yet. It was probably
found during a later part when the module loading was actually able to get
to that line.
timeless - March 30, 2016, 8:33 a.m.
Precisely.

On Wed, Mar 30, 2016 at 2:40 AM, Gregory Szorc <gregory.szorc@gmail.com> wrote:
> On Tue, Mar 29, 2016 at 10:14 PM, Pierre-Yves David
> <pierre-yves.david@ens-lyon.org> wrote:
>>
>>
>>
>> On 03/29/2016 09:24 PM, timeless wrote:
>>>
>>> There's a minor bug in this commit, if it's queued, could someone unqueue
>>> it?
>>>
>>> request.urlencode should be parse.urlencode
>>
>>
>> I've pushed the fixed version. Any idea of why the test did not caught
>> that?
>
>
> Because 90% of this code doesn't run under Python 3 yet. It was probably
> found during a later part when the module loading was actually able to get
> to that line.
>

Patch

diff --git a/hgext/acl.py b/hgext/acl.py
--- a/hgext/acl.py
+++ b/hgext/acl.py
@@ -194,7 +194,13 @@ 
 from __future__ import absolute_import
 
 import getpass
-import urllib
+
+try:
+    import urllib
+    unquote = urllib.unquote
+except AttributeError:
+    import urllib.request
+    unquote = urllib.request.unquote
 
 from mercurial.i18n import _
 from mercurial import (
@@ -287,7 +293,7 @@ 
     if source == 'serve' and 'url' in kwargs:
         url = kwargs['url'].split(':')
         if url[0] == 'remote' and url[1].startswith('http'):
-            user = urllib.unquote(url[3])
+            user = unquote(url[3])
 
     if user is None:
         user = getpass.getuser()
diff --git a/hgext/convert/subversion.py b/hgext/convert/subversion.py
--- a/hgext/convert/subversion.py
+++ b/hgext/convert/subversion.py
@@ -9,10 +9,25 @@ 
 import re
 import sys
 import tempfile
-import urllib
-import urllib2
 import xml.dom.minidom
 
+try:
+    import urllib2
+    import urllib
+    httperror = urllib2.HTTPError
+    build_opener = urllib2.build_opener
+    quote = urllib.quote
+    unquote = urllib.unquote
+    url2pathname = urllib.url2pathname
+except ImportError:
+    import urllib.request
+    import urllib.error
+    httperror = urllib.error.HTTPError
+    build_opener = urllib.request.build_opener
+    quote = urllib.request.quote
+    unquote = urllib.request.unquote
+    url2pathname = urllib.request.url2pathname
+
 from mercurial import (
     encoding,
     error,
@@ -87,7 +102,7 @@ 
         mod = '/' + parts[1]
     return uuid, mod, revnum
 
-def quote(s):
+def svnquote(s):
     # As of svn 1.7, many svn calls expect "canonical" paths. In
     # theory, we should call svn.core.*canonicalize() on all paths
     # before passing them to the API.  Instead, we assume the base url
@@ -95,7 +110,7 @@ 
     # so we can extend it safely with new components. The "safe"
     # characters were taken from the "svn_uri__char_validity" table in
     # libsvn_subr/path.c.
-    return urllib.quote(s, "!$&'()*+,-./:=@_~")
+    return quote(s, "!$&'()*+,-./:=@_~")
 
 def geturl(path):
     try:
@@ -110,7 +125,7 @@ 
         # Module URL is later compared with the repository URL returned
         # by svn API, which is UTF-8.
         path = encoding.tolocal(path)
-        path = 'file://%s' % quote(path)
+        path = 'file://%s' % svnquote(path)
     return svn.core.svn_path_canonicalize(path)
 
 def optrev(number):
@@ -237,7 +252,7 @@ 
         opener = urllib2.build_opener()
         rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path))
         data = rsp.read()
-    except urllib2.HTTPError as inst:
+    except httperror as inst:
         if inst.code != 404:
             # Except for 404 we cannot know for sure this is not an svn repo
             ui.warn(_('svn: cannot probe remote repository, assume it could '
@@ -261,7 +276,7 @@ 
             if (os.name == 'nt' and path[:1] == '/' and path[1:2].isalpha()
                 and path[2:6].lower() == '%3a/'):
                 path = path[:2] + ':/' + path[6:]
-            path = urllib.url2pathname(path)
+            path = url2pathname(path)
     except ValueError:
         proto = 'file'
         path = os.path.abspath(url)
@@ -331,7 +346,7 @@ 
             self.baseurl = svn.ra.get_repos_root(self.ra)
             # Module is either empty or a repository path starting with
             # a slash and not ending with a slash.
-            self.module = urllib.unquote(self.url[len(self.baseurl):])
+            self.module = unquote(self.url[len(self.baseurl):])
             self.prevmodule = None
             self.rootmodule = self.module
             self.commits = {}
@@ -395,7 +410,7 @@ 
 
     def exists(self, path, optrev):
         try:
-            svn.client.ls(self.url.rstrip('/') + '/' + quote(path),
+            svn.client.ls(self.url.rstrip('/') + '/' + svnquote(path),
                                  optrev, False, self.ctx)
             return True
         except svn.core.SubversionException:
@@ -447,7 +462,7 @@ 
         # Check if branches bring a few more heads to the list
         if branches:
             rpath = self.url.strip('/')
-            branchnames = svn.client.ls(rpath + '/' + quote(branches),
+            branchnames = svn.client.ls(rpath + '/' + svnquote(branches),
                                         rev, False, self.ctx)
             for branch in sorted(branchnames):
                 module = '%s/%s/%s' % (oldmodule, branches, branch)
@@ -481,7 +496,7 @@ 
         if full or not parents:
             # Perform a full checkout on roots
             uuid, module, revnum = revsplit(rev)
-            entries = svn.client.ls(self.baseurl + quote(module),
+            entries = svn.client.ls(self.baseurl + svnquote(module),
                                     optrev(revnum), True, self.ctx)
             files = [n for n, e in entries.iteritems()
                      if e.kind == svn.core.svn_node_file]
@@ -729,7 +744,7 @@ 
         """Reparent the svn transport and return the previous parent."""
         if self.prevmodule == module:
             return module
-        svnurl = self.baseurl + quote(module)
+        svnurl = self.baseurl + svnquote(module)
         prevmodule = self.prevmodule
         if prevmodule is None:
             prevmodule = ''
@@ -1012,7 +1027,7 @@ 
         """Enumerate all files in path at revnum, recursively."""
         path = path.strip('/')
         pool = svn.core.Pool()
-        rpath = '/'.join([self.baseurl, quote(path)]).strip('/')
+        rpath = '/'.join([self.baseurl, svnquote(path)]).strip('/')
         entries = svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool)
         if path:
             path += '/'
diff --git a/hgext/largefiles/proto.py b/hgext/largefiles/proto.py
--- a/hgext/largefiles/proto.py
+++ b/hgext/largefiles/proto.py
@@ -4,9 +4,15 @@ 
 # GNU General Public License version 2 or any later version.
 
 import os
-import urllib2
 import re
 
+try:
+    import urllib2
+    httperror = urllib2.HTTPError
+except ImportError:
+    import urllib.error
+    httperror = urllib.error.HTTPError
+
 from mercurial import error, httppeer, util, wireproto
 from mercurial.i18n import _
 
@@ -140,7 +146,7 @@ 
             yield result, f
             try:
                 yield int(f.value)
-            except (ValueError, urllib2.HTTPError):
+            except (ValueError, httperror):
                 # If the server returns anything but an integer followed by a
                 # newline, newline, it's not speaking our language; if we get
                 # an HTTP error, we can't be sure the largefile is present;
diff --git a/hgext/largefiles/remotestore.py b/hgext/largefiles/remotestore.py
--- a/hgext/largefiles/remotestore.py
+++ b/hgext/largefiles/remotestore.py
@@ -6,7 +6,14 @@ 
 
 '''remote largefile store; the base class for wirestore'''
 
-import urllib2
+try:
+    import urllib2
+    httperror = urllib2.HTTPError
+    urlerror = urllib2.URLError
+except ImportError:
+    import urllib.error
+    httperror = urllib.error.HTTPError
+    urlerror = urllib.error.URLError
 
 from mercurial import util, wireproto, error
 from mercurial.i18n import _
@@ -49,11 +56,11 @@ 
     def _getfile(self, tmpfile, filename, hash):
         try:
             chunks = self._get(hash)
-        except urllib2.HTTPError as e:
+        except httperror as e:
             # 401s get converted to error.Aborts; everything else is fine being
             # turned into a StoreError
             raise basestore.StoreError(filename, hash, self.url, str(e))
-        except urllib2.URLError as e:
+        except urlerror as e:
             # This usually indicates a connection problem, so don't
             # keep trying with the other files... they will probably
             # all fail too.
diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -152,7 +152,15 @@ 
 import string
 import struct
 import sys
-import urllib
+
+try:
+    import urllib
+    quote = urllib.quote
+    unquote = urllib.unquote
+except AttributeError:
+    import urllib.request
+    quote = urllib.request.quote
+    unquote = urllib.request.unquote
 
 from .i18n import _
 from . import (
@@ -467,8 +475,8 @@ 
     chunks = []
     for ca in sorted(caps):
         vals = caps[ca]
-        ca = urllib.quote(ca)
-        vals = [urllib.quote(v) for v in vals]
+        ca = quote(ca)
+        vals = [quote(v) for v in vals]
         if vals:
             ca = "%s=%s" % (ca, ','.join(vals))
         chunks.append(ca)
@@ -570,9 +578,9 @@ 
         """return a encoded version of all stream parameters"""
         blocks = []
         for par, value in self._params:
-            par = urllib.quote(par)
+            par = quote(par)
             if value is not None:
-                value = urllib.quote(value)
+                value = quote(value)
                 par = '%s=%s' % (par, value)
             blocks.append(par)
         return ' '.join(blocks)
diff --git a/mercurial/byterange.py b/mercurial/byterange.py
--- a/mercurial/byterange.py
+++ b/mercurial/byterange.py
@@ -26,22 +26,45 @@ 
 import re
 import socket
 import stat
-import urllib
-import urllib2
 
-addclosehook = urllib.addclosehook
-addinfourl = urllib.addinfourl
-splitattr = urllib.splitattr
-splitpasswd = urllib.splitpasswd
-splitport = urllib.splitport
-splituser = urllib.splituser
-unquote = urllib.unquote
+try:
+    import urllib2
+    import urllib
+    basehandler = urllib2.BaseHandler
+    filehandler = urllib2.FileHandler
+    ftphandler = urllib2.FTPHandler
+    ftpwrapper = urllib.ftpwrapper
+    urlerror = urllib2.URLError
+    addclosehook = urllib.addclosehook
+    addinfourl = urllib.addinfourl
+    splitattr = urllib.splitattr
+    splitpasswd = urllib.splitpasswd
+    splitport = urllib.splitport
+    splituser = urllib.splituser
+    unquote = urllib.unquote
+    url2pathname = urllib.url2pathname
+except ImportError:
+    import urllib.request
+    import urllib.error
+    basehandler = urllib.request.BaseHandler
+    filehandler = urllib.request.FileHandler
+    ftphandler = urllib.request.FTPHandler
+    ftpwrapper = urllib.request.ftpwrapper
+    urlerror = urllib.error.URLError
+    addclosehook = urllib.request.addclosehook
+    addinfourl = urllib.request.addinfourl
+    splitattr = urllib.request.splitattr
+    splitpasswd = urllib.request.splitpasswd
+    splitport = urllib.request.splitport
+    splituser = urllib.request.splituser
+    unquote = urllib.request.unquote
+    url2pathname = urllib.request.url2pathname
 
 class RangeError(IOError):
     """Error raised when an unsatisfiable range is requested."""
     pass
 
-class HTTPRangeHandler(urllib2.BaseHandler):
+class HTTPRangeHandler(basehandler):
     """Handler that enables HTTP Range headers.
 
     This was extremely simple. The Range header is a HTTP feature to
@@ -67,7 +90,7 @@ 
 
     def http_error_206(self, req, fp, code, msg, hdrs):
         # 206 Partial Content Response
-        r = urllib.addinfourl(fp, hdrs, req.get_full_url())
+        r = addinfourl(fp, hdrs, req.get_full_url())
         r.code = code
         r.msg = msg
         return r
@@ -204,7 +227,7 @@ 
                 raise RangeError('Requested Range Not Satisfiable')
             pos += bufsize
 
-class FileRangeHandler(urllib2.FileHandler):
+class FileRangeHandler(filehandler):
     """FileHandler subclass that adds Range support.
     This class handles Range headers exactly like an HTTP
     server would.
@@ -212,15 +235,15 @@ 
     def open_local_file(self, req):
         host = req.get_host()
         file = req.get_selector()
-        localfile = urllib.url2pathname(file)
+        localfile = url2pathname(file)
         stats = os.stat(localfile)
         size = stats[stat.ST_SIZE]
         modified = email.Utils.formatdate(stats[stat.ST_MTIME])
         mtype = mimetypes.guess_type(file)[0]
         if host:
-            host, port = urllib.splitport(host)
+            host, port = splitport(host)
             if port or socket.gethostbyname(host) not in self.get_names():
-                raise urllib2.URLError('file not on local host')
+                raise urlerror('file not on local host')
         fo = open(localfile,'rb')
         brange = req.headers.get('Range', None)
         brange = range_header_to_tuple(brange)
@@ -236,7 +259,7 @@ 
         headers = email.message_from_string(
             'Content-Type: %s\nContent-Length: %d\nLast-Modified: %s\n' %
             (mtype or 'text/plain', size, modified))
-        return urllib.addinfourl(fo, headers, 'file:'+file)
+        return addinfourl(fo, headers, 'file:'+file)
 
 
 # FTP Range Support
@@ -246,7 +269,7 @@ 
 # follows:
 # -- range support modifications start/end here
 
-class FTPRangeHandler(urllib2.FTPHandler):
+class FTPRangeHandler(ftphandler):
     def ftp_open(self, req):
         host = req.get_host()
         if not host:
@@ -270,7 +293,7 @@ 
         try:
             host = socket.gethostbyname(host)
         except socket.error as msg:
-            raise urllib2.URLError(msg)
+            raise urlerror(msg)
         path, attrs = splitattr(req.get_selector())
         dirs = path.split('/')
         dirs = map(unquote, dirs)
@@ -331,10 +354,10 @@ 
             raise IOError('ftp error', msg)
 
     def connect_ftp(self, user, passwd, host, port, dirs):
-        fw = ftpwrapper(user, passwd, host, port, dirs)
+        fw = ftpwrapperrest(user, passwd, host, port, dirs)
         return fw
 
-class ftpwrapper(urllib.ftpwrapper):
+class ftpwrapperrest(ftpwrapper):
     # range support note:
     # this ftpwrapper code is copied directly from
     # urllib. The only enhancement is to add the rest
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -8,8 +8,20 @@ 
 from __future__ import absolute_import
 
 import errno
-import urllib
-import urllib2
+
+try:
+    import urllib2
+    import urllib
+    httperror = urllib2.HTTPError
+    urlerror = urllib2.URLError
+    quote = urllib.quote
+    unquote = urllib.unquote
+except ImportError:
+    import urllib.error
+    httperror = urllib.error.HTTPError
+    urlerror = urllib.error.URLError
+    quote = urllib.request.quote
+    unquote = urllib.request.unquote
 
 from .i18n import _
 from .node import (
@@ -97,8 +109,8 @@ 
                       'missing "=" in parameter: %s') % p)
 
             key, value = p.split('=', 1)
-            key = urllib.unquote(key)
-            value = urllib.unquote(value)
+            key = unquote(key)
+            value = unquote(value)
             params[key] = value
 
         return version, params
@@ -236,7 +248,7 @@ 
     elif isinstance(b, streamclone.streamcloneapplier):
         requirements = streamclone.readbundle1header(fh)[2]
         params = 'requirements=%s' % ','.join(sorted(requirements))
-        return 'none-packed1;%s' % urllib.quote(params)
+        return 'none-packed1;%s' % quote(params)
     else:
         raise error.Abort(_('unknown bundle type: %s') % b)
 
@@ -1469,7 +1481,7 @@ 
     """return a set with appropriate options to use bundle20 during getbundle"""
     caps = set(['HG20'])
     capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
-    caps.add('bundle2=' + urllib.quote(capsblob))
+    caps.add('bundle2=' + quote(capsblob))
     return caps
 
 # List of names of steps to perform for a bundle2 for getbundle, order matters.
@@ -1535,7 +1547,7 @@ 
     b2caps = {}
     for bcaps in bundlecaps:
         if bcaps.startswith('bundle2='):
-            blob = urllib.unquote(bcaps[len('bundle2='):])
+            blob = unquote(bcaps[len('bundle2='):])
             b2caps.update(bundle2.decodecaps(blob))
     bundler = bundle2.bundle20(repo.ui, b2caps)
 
@@ -1804,8 +1816,8 @@ 
         attrs = {'URL': fields[0]}
         for rawattr in fields[1:]:
             key, value = rawattr.split('=', 1)
-            key = urllib.unquote(key)
-            value = urllib.unquote(value)
+            key = unquote(key)
+            value = unquote(value)
             attrs[key] = value
 
             # Parse BUNDLESPEC into components. This makes client-side
diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py
--- a/mercurial/hgweb/protocol.py
+++ b/mercurial/hgweb/protocol.py
@@ -8,7 +8,6 @@ 
 from __future__ import absolute_import
 
 import cgi
-import urllib
 import zlib
 
 try:
@@ -16,6 +15,13 @@ 
 except ImportError:
     import io
 
+try:
+    import urllib
+    quote = urllib.quote
+except AttributeError:
+    import urllib.request
+    quote = urllib.request.quote
+
 from .common import (
     HTTP_OK,
 )
@@ -86,8 +92,8 @@ 
     def _client(self):
         return 'remote:%s:%s:%s' % (
             self.req.env.get('wsgi.url_scheme') or 'http',
-            urllib.quote(self.req.env.get('REMOTE_HOST', '')),
-            urllib.quote(self.req.env.get('REMOTE_USER', '')))
+            quote(self.req.env.get('REMOTE_HOST', '')),
+            quote(self.req.env.get('REMOTE_USER', '')))
 
 def iscmd(cmd):
     return cmd in wireproto.commands
diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -15,7 +15,13 @@ 
 import socket
 import sys
 import traceback
-import urllib
+
+try:
+    import urllib
+    unquote = urllib.unquote
+except AttributeError:
+    import urllib.request
+    unquote = urllib.request.unquote
 
 from ..i18n import _
 
@@ -38,7 +44,7 @@ 
         path, query = uri.split('?', 1)
     else:
         path, query = uri, ''
-    return urllib.unquote(path), query
+    return unquote(path), query
 
 class _error_logger(object):
     def __init__(self, handler):
diff --git a/mercurial/httpconnection.py b/mercurial/httpconnection.py
--- a/mercurial/httpconnection.py
+++ b/mercurial/httpconnection.py
@@ -13,8 +13,23 @@ 
 import logging
 import os
 import socket
-import urllib
-import urllib2
+
+try:
+    import urllib2
+    import urllib
+    httphandler = urllib2.HTTPHandler
+    httpshandler = urllib2.HTTPSHandler
+    abstracthttphandler = urllib2.AbstractHTTPHandler
+    addinfourl = urllib.addinfourl
+    urlerror = urllib2.URLError
+except ImportError:
+    import urllib.request
+    import urllib.error
+    httphandler = urllib.request.HTTPHandler
+    httpshandler = urllib.request.HTTPSHandler
+    abstracthttphandler = urllib.request.AbstractHTTPHandler
+    addinfourl = urllib.request.addinfourl
+    urlerror = urllib.error.URLError
 
 from .i18n import _
 from . import (
@@ -123,10 +138,10 @@ 
 # Subclass BOTH of these because otherwise urllib2 "helpfully"
 # reinserts them since it notices we don't include any subclasses of
 # them.
-class http2handler(urllib2.HTTPHandler, urllib2.HTTPSHandler):
+class http2handler(httphandler, httpshandler):
     def __init__(self, ui, pwmgr):
         global _configuredlogging
-        urllib2.AbstractHTTPHandler.__init__(self)
+        abstracthttphandler.__init__(self)
         self.ui = ui
         self.pwmgr = pwmgr
         self._connections = {}
@@ -187,7 +202,7 @@ 
             proxy = None
 
         if not host:
-            raise urllib2.URLError('no host given')
+            raise urlerror('no host given')
 
         connkey = use_ssl, host, proxy
         allconns = self._connections.get(connkey, [])
@@ -217,13 +232,13 @@ 
             h.request(req.get_method(), path, req.data, headers)
             r = h.getresponse()
         except socket.error as err: # XXX what error?
-            raise urllib2.URLError(err)
+            raise urlerror(err)
 
         # Pick apart the HTTPResponse object to get the addinfourl
         # object initialized properly.
         r.recv = r.read
 
-        resp = urllib.addinfourl(r, r.headers, req.get_full_url())
+        resp = addinfourl(r, r.headers, req.get_full_url())
         resp.code = r.status
         resp.msg = r.reason
         return resp
diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -13,10 +13,21 @@ 
 import os
 import socket
 import tempfile
-import urllib
-import urllib2
 import zlib
 
+try:
+    import urllib2
+    import urllib
+    request = urllib2.Request
+    httperror = urllib2.HTTPError
+    urlencode = urllib.urlencode
+except ImportError:
+    import urllib.request
+    import urllib.error
+    request = urllib.request.Request
+    httperror = urllib.error.HTTPError
+    urlencode = urllib.request.urlencode
+
 from .i18n import _
 from .node import nullid
 from . import (
@@ -59,7 +70,7 @@ 
         self.ui.debug('using %s\n' % self._url)
 
         self.urlopener = url.opener(ui, authinfo)
-        self.requestbuilder = urllib2.Request
+        self.requestbuilder = request
 
     def __del__(self):
         if self.urlopener:
@@ -105,7 +116,7 @@ 
         # object rather than a basestring
         canmungedata = not data or isinstance(data, basestring)
         if postargsok and canmungedata:
-            strargs = urllib.urlencode(sorted(args.items()))
+            strargs = urlencode(sorted(args.items()))
             if strargs:
                 if not data:
                     data = strargs
@@ -119,7 +130,7 @@ 
                     headersize = int(httpheader.split(',', 1)[0])
             if headersize > 0:
                 # The headers can typically carry more data than the URL.
-                encargs = urllib.urlencode(sorted(args.items()))
+                encargs = urlencode(sorted(args.items()))
                 headerfmt = 'X-HgArg-%s'
                 contentlen = headersize - len(headerfmt % '000' + ': \r\n')
                 headernum = 0
@@ -132,7 +143,7 @@ 
                 headers['Vary'] = ','.join(varyheaders)
             else:
                 q += sorted(args.items())
-        qs = '?%s' % urllib.urlencode(q)
+        qs = '?%s' % urlencode(q)
         cu = "%s%s" % (self._url, qs)
         size = 0
         if util.safehasattr(data, 'length'):
@@ -150,7 +161,7 @@ 
             req.add_unredirected_header('Content-Length', '%d' % size)
         try:
             resp = self.urlopener.open(req)
-        except urllib2.HTTPError as inst:
+        except httperror as inst:
             if inst.code == 401:
                 raise error.Abort(_('authorization failed'))
             raise
diff --git a/mercurial/keepalive.py b/mercurial/keepalive.py
--- a/mercurial/keepalive.py
+++ b/mercurial/keepalive.py
@@ -25,13 +25,23 @@ 
 
 """An HTTP handler for urllib2 that supports HTTP 1.1 and keepalive.
 
->>> import urllib2
+>>> try:
+...     import urllib2
+...     build_opener = urllib2.build_opener
+...     install_opener = urllib2.install_opener
+...     urlopen = urllib2.urlopen
+... except ImportError:
+...     import urllib.request
+...     build_opener = urllib.request.build_opener
+...     install_opener = urllib.request.install_opener
+...     urlopen = urllib.request.urlopen
+
 >>> from keepalive import HTTPHandler
 >>> keepalive_handler = HTTPHandler()
->>> opener = urllib2.build_opener(keepalive_handler)
->>> urllib2.install_opener(opener)
+>>> opener = build_opener(keepalive_handler)
+>>> install_opener(opener)
 >>>
->>> fo = urllib2.urlopen('http://www.python.org')
+>>> fo = urlopen('http://www.python.org')
 
 If a connection to a given host is requested, and all of the existing
 connections are still in use, another connection will be opened.  If
@@ -114,7 +124,22 @@ 
 import socket
 import sys
 import thread
-import urllib2
+
+try:
+    import urllib2
+    build_opener = urllib2.build_opener
+    install_opener = urllib2.install_opener
+    urlopen = urllib2.urlopen
+    httphandler = urllib2.HTTPHandler
+    urlerror = urllib2.URLError
+except ImportError:
+    import urllib.request
+    import urllib.error
+    build_opener = urllib.request.build_opener
+    install_opener = urllib.request.install_opener
+    urlopen = urllib.request.urlopen
+    httphandler = urllib.request.HTTPHandler
+    urlerror = urllib.error.URLError
 
 DEBUG = None
 
@@ -227,7 +252,7 @@ 
     def do_open(self, http_class, req):
         host = req.get_host()
         if not host:
-            raise urllib2.URLError('no host given')
+            raise urlerror('no host given')
 
         try:
             h = self._cm.get_ready_conn(host)
@@ -254,7 +279,7 @@ 
                 self._start_transaction(h, req)
                 r = h.getresponse()
         except (socket.error, httplib.HTTPException) as err:
-            raise urllib2.URLError(err)
+            raise urlerror(err)
 
         # if not a persistent connection, don't try to reuse it
         if r.will_close:
@@ -346,14 +371,14 @@ 
             else:
                 h.putrequest('GET', req.get_selector(), **skipheaders)
         except socket.error as err:
-            raise urllib2.URLError(err)
+            raise urlerror(err)
         for k, v in headers.items():
             h.putheader(k, v)
         h.endheaders()
         if req.has_data():
             h.send(data)
 
-class HTTPHandler(KeepAliveHandler, urllib2.HTTPHandler):
+class HTTPHandler(KeepAliveHandler, httphandler):
     pass
 
 class HTTPResponse(httplib.HTTPResponse):
@@ -593,14 +618,14 @@ 
     global HANDLE_ERRORS
     orig = HANDLE_ERRORS
     keepalive_handler = HTTPHandler()
-    opener = urllib2.build_opener(keepalive_handler)
-    urllib2.install_opener(opener)
+    opener = build_opener(keepalive_handler)
+    install_opener(opener)
     pos = {0: 'off', 1: 'on'}
     for i in (0, 1):
         print("  fancy error handling %s (HANDLE_ERRORS = %i)" % (pos[i], i))
         HANDLE_ERRORS = i
         try:
-            fo = urllib2.urlopen(url)
+            fo = urlopen(url)
             fo.read()
             fo.close()
             try:
@@ -623,25 +648,25 @@ 
     format = '%25s: %s'
 
     # first fetch the file with the normal http handler
-    opener = urllib2.build_opener()
-    urllib2.install_opener(opener)
-    fo = urllib2.urlopen(url)
+    opener = build_opener()
+    install_opener(opener)
+    fo = urlopen(url)
     foo = fo.read()
     fo.close()
     m = md5(foo)
     print(format % ('normal urllib', m.hexdigest()))
 
     # now install the keepalive handler and try again
-    opener = urllib2.build_opener(HTTPHandler())
-    urllib2.install_opener(opener)
+    opener = build_opener(HTTPHandler())
+    install_opener(opener)
 
-    fo = urllib2.urlopen(url)
+    fo = urlopen(url)
     foo = fo.read()
     fo.close()
     m = md5(foo)
     print(format % ('keepalive read', m.hexdigest()))
 
-    fo = urllib2.urlopen(url)
+    fo = urlopen(url)
     foo = ''
     while True:
         f = fo.readline()
@@ -657,15 +682,15 @@ 
 
     sys.stdout.write('  first using the normal urllib handlers')
     # first use normal opener
-    opener = urllib2.build_opener()
-    urllib2.install_opener(opener)
+    opener = build_opener()
+    install_opener(opener)
     t1 = fetch(N, url)
     print('  TIME: %.3f s' % t1)
 
     sys.stdout.write('  now using the keepalive handler       ')
     # now install the keepalive handler and try again
-    opener = urllib2.build_opener(HTTPHandler())
-    urllib2.install_opener(opener)
+    opener = build_opener(HTTPHandler())
+    install_opener(opener)
     t2 = fetch(N, url)
     print('  TIME: %.3f s' % t2)
     print('  improvement factor: %.2f' % (t1 / t2))
@@ -677,7 +702,7 @@ 
     for i in range(N):
         if delay and i > 0:
             time.sleep(delay)
-        fo = urllib2.urlopen(url)
+        fo = urlopen(url)
         foo = fo.read()
         fo.close()
         lens.append(len(foo))
@@ -700,7 +725,7 @@ 
         info = warning = error = debug
     DEBUG = FakeLogger()
     print("  fetching the file to establish a connection")
-    fo = urllib2.urlopen(url)
+    fo = urlopen(url)
     data1 = fo.read()
     fo.close()
 
@@ -714,7 +739,7 @@ 
     sys.stderr.write('\r')
 
     print("  fetching the file a second time")
-    fo = urllib2.urlopen(url)
+    fo = urlopen(url)
     data2 = fo.read()
     fo.close()
 
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -12,9 +12,15 @@ 
 import os
 import random
 import time
-import urllib
 import weakref
 
+try:
+    import urllib
+    quote = urllib.quote
+except AttributeError:
+    import urllib.request
+    quote = urllib.request.quote
+
 from .i18n import _
 from .node import (
     hex,
@@ -366,7 +372,7 @@ 
         if self.ui.configbool('experimental', 'bundle2-advertise', True):
             caps = set(caps)
             capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
-            caps.add('bundle2=' + urllib.quote(capsblob))
+            caps.add('bundle2=' + quote(capsblob))
         return caps
 
     def _applyopenerreqs(self):
diff --git a/mercurial/statichttprepo.py b/mercurial/statichttprepo.py
--- a/mercurial/statichttprepo.py
+++ b/mercurial/statichttprepo.py
@@ -11,8 +11,21 @@ 
 
 import errno
 import os
-import urllib
-import urllib2
+
+try:
+    import urllib2
+    import urllib
+    request = urllib2.Request
+    httperror = urllib2.HTTPError
+    quote = urllib.quote
+    urlerror = urllib2.URLError
+except ImportError:
+    import urllib.request
+    import urllib.error
+    request = urllib.request.Request
+    httperror = urllib.error.HTTPError
+    quote = urllib.request.quote
+    urlerror = urllib.error.URLError
 
 from .i18n import _
 from . import (
@@ -45,7 +58,7 @@ 
     def seek(self, pos):
         self.pos = pos
     def read(self, bytes=None):
-        req = urllib2.Request(self.url)
+        req = request(self.url)
         end = ''
         if bytes:
             end = self.pos + bytes - 1
@@ -56,10 +69,10 @@ 
             f = self.opener.open(req)
             data = f.read()
             code = f.code
-        except urllib2.HTTPError as inst:
+        except httperror as inst:
             num = inst.code == 404 and errno.ENOENT or None
             raise IOError(num, inst)
-        except urllib2.URLError as inst:
+        except urlerror as inst:
             raise IOError(None, inst.reason[1])
 
         if code == 200:
@@ -92,7 +105,7 @@ 
         def __call__(self, path, mode='r', *args, **kw):
             if mode not in ('r', 'rb'):
                 raise IOError('Permission denied')
-            f = "/".join((self.base, urllib.quote(path)))
+            f = "/".join((self.base, quote(path)))
             return httprangereader(f, urlopener)
 
         def join(self, path):
diff --git a/mercurial/templatefilters.py b/mercurial/templatefilters.py
--- a/mercurial/templatefilters.py
+++ b/mercurial/templatefilters.py
@@ -11,7 +11,13 @@ 
 import os
 import re
 import time
-import urllib
+
+try:
+    import urllib
+    quote = urllib.quote
+except AttributeError:
+    import urllib.request
+    quote = urllib.request.quote
 
 from . import (
     encoding,
@@ -269,7 +275,7 @@ 
     Forward slashes are escaped twice to prevent web servers from prematurely
     unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
     """
-    return urllib.quote(text, safe='/@').replace('/', '%252F')
+    return quote(text, safe='/@').replace('/', '%252F')
 
 def rfc3339date(text):
     """:rfc3339date: Date. Returns a date using the Internet date format
@@ -342,7 +348,7 @@ 
     """:urlescape: Any text. Escapes all "special" characters. For example,
     "foo bar" becomes "foo%20bar".
     """
-    return urllib.quote(text)
+    return quote(text)
 
 def userfilter(text):
     """:user: Any text. Returns a short representation of a user name or email
diff --git a/mercurial/url.py b/mercurial/url.py
--- a/mercurial/url.py
+++ b/mercurial/url.py
@@ -13,8 +13,39 @@ 
 import httplib
 import os
 import socket
-import urllib
-import urllib2
+
+try:
+    import urllib2
+    import urllib
+    build_opener = urllib2.build_opener
+    pathname2url = urllib.pathname2url
+    pyhttpbasicauthhandler = urllib2.HTTPBasicAuthHandler
+    pyhttpdigestauthhandler = urllib2.HTTPDigestAuthHandler
+    pyhttppasswordmgrwithdefaultrealm = urllib2.HTTPPasswordMgrWithDefaultRealm
+    pyproxyhandler = urllib2.ProxyHandler
+
+    try:
+        pyhttpshandler = urllib2.HTTPSHandler
+        has_https = True
+    except AttributeError:
+        has_https = False
+
+except ImportError:
+    import urllib.request
+    import urllib.error
+    build_opener = urllib.request.build_opener
+    pathname2url = urllib.request.pathname2url
+    pyhttpbasicauthhandler = urllib.request.HTTPBasicAuthHandler
+    pyhttpdigestauthhandler = urllib.request.HTTPDigestAuthHandler
+    pyhttppasswordmgrwithdefaultrealm = \
+        urllib.request.HTTPPasswordMgrWithDefaultRealm
+    pyproxyhandler = urllib.request.ProxyHandler
+
+    try:
+        pyhttpshandler = urllib.request.HTTPSHandler
+        has_https = True
+    except AttributeError:
+        has_https = False
 
 try:
     import cStringIO as io
@@ -30,13 +61,13 @@ 
     util,
 )
 
-class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
+class passwordmgr(pyhttppasswordmgrwithdefaultrealm):
     def __init__(self, ui):
-        urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
+        pyhttppasswordmgrwithdefaultrealm.__init__(self)
         self.ui = ui
 
     def find_user_password(self, realm, authuri):
-        authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
+        authinfo = pyhttppasswordmgrwithdefaultrealm.find_user_password(
             self, realm, authuri)
         user, passwd = authinfo
         if user and passwd:
@@ -76,10 +107,10 @@ 
         self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
 
     def find_stored_password(self, authuri):
-        return urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
+        return pyhttppasswordmgrwithdefaultrealm.find_user_password(
             self, None, authuri)
 
-class proxyhandler(urllib2.ProxyHandler):
+class proxyhandler(pyproxyhandler):
     def __init__(self, ui):
         proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
         # XXX proxyauthinfo = None
@@ -125,7 +156,7 @@ 
                 except OSError:
                     pass
 
-        urllib2.ProxyHandler.__init__(self, proxies)
+        pyproxyhandler.__init__(self, proxies)
         self.ui = ui
 
     def proxy_open(self, req, proxy, type_):
@@ -138,7 +169,7 @@ 
             if e.startswith('.') and host.endswith(e[1:]):
                 return None
 
-        return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
+        return pyproxyhandler.proxy_open(self, req, proxy, type_)
 
 def _gen_sendfile(orgsend):
     def _sendfile(self, data):
@@ -152,7 +183,6 @@ 
             orgsend(self, data)
     return _sendfile
 
-has_https = util.safehasattr(urllib2, 'HTTPSHandler')
 if has_https:
     try:
         _create_connection = socket.create_connection
@@ -361,10 +391,10 @@ 
                 **sslutil.sslkwargs(self.ui, host))
             sslutil.validator(self.ui, host)(self.sock)
 
-    class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
+    class httpshandler(keepalive.KeepAliveHandler, pyhttpshandler):
         def __init__(self, ui):
             keepalive.KeepAliveHandler.__init__(self)
-            urllib2.HTTPSHandler.__init__(self)
+            pyhttpshandler.__init__(self)
             self.ui = ui
             self.pwmgr = passwordmgr(self.ui)
 
@@ -407,9 +437,9 @@ 
             conn.ui = self.ui
             return conn
 
-class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
+class httpdigestauthhandler(pyhttpdigestauthhandler):
     def __init__(self, *args, **kwargs):
-        urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
+        pyhttpdigestauthhandler.__init__(self, *args, **kwargs)
         self.retried_req = None
 
     def reset_retry_count(self):
@@ -423,13 +453,13 @@ 
         if req is not self.retried_req:
             self.retried_req = req
             self.retried = 0
-        return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
+        return pyhttpdigestauthhandler.http_error_auth_reqed(
                     self, auth_header, host, req, headers)
 
-class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
+class httpbasicauthhandler(pyhttpbasicauthhandler):
     def __init__(self, *args, **kwargs):
         self.auth = None
-        urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
+        pyhttpbasicauthhandler.__init__(self, *args, **kwargs)
         self.retried_req = None
 
     def http_request(self, request):
@@ -455,7 +485,7 @@ 
         if req is not self.retried_req:
             self.retried_req = req
             self.retried = 0
-        return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
+        return pyhttpbasicauthhandler.http_error_auth_reqed(
                         self, auth_header, host, req, headers)
 
     def retry_http_basic_auth(self, host, req, realm):
@@ -498,7 +528,7 @@ 
     handlers.extend((httpbasicauthhandler(passmgr),
                      httpdigestauthhandler(passmgr)))
     handlers.extend([h(ui, passmgr) for h in handlerfuncs])
-    opener = urllib2.build_opener(*handlers)
+    opener = build_opener(*handlers)
 
     # 1.0 here is the _protocol_ version
     opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
@@ -512,6 +542,6 @@ 
         url_, authinfo = u.authinfo()
     else:
         path = util.normpath(os.path.abspath(url_))
-        url_ = 'file://' + urllib.pathname2url(path)
+        url_ = 'file://' + pathname2url(path)
         authinfo = None
     return opener(ui, authinfo).open(url_, data)
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -34,9 +34,15 @@ 
 import textwrap
 import time
 import traceback
-import urllib
 import zlib
 
+try:
+    import urllib
+    quote = urllib.quote
+except AttributeError:
+    import urllib.request
+    quote = urllib.request.quote
+
 from . import (
     encoding,
     error,
@@ -2368,30 +2374,30 @@ 
             if hasdriveletter(self.path):
                 s += '/'
         if self.user:
-            s += urllib.quote(self.user, safe=self._safechars)
+            s += quote(self.user, safe=self._safechars)
         if self.passwd:
-            s += ':' + urllib.quote(self.passwd, safe=self._safechars)
+            s += ':' + 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)
+                s += quote(self.host)
             else:
                 s += self.host
         if self.port:
-            s += ':' + urllib.quote(self.port)
+            s += ':' + quote(self.port)
         if self.host:
             s += '/'
         if self.path:
             # TODO: similar to the query string, we should not unescape the
             # path when we store it, the path might contain '%2f' = '/',
             # which we should *not* escape.
-            s += urllib.quote(self.path, safe=self._safepchars)
+            s += quote(self.path, safe=self._safepchars)
         if self.query:
             # we store the query in escaped form.
             s += '?' + self.query
         if self.fragment is not None:
-            s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
+            s += '#' + quote(self.fragment, safe=self._safepchars)
         return s
 
     def authinfo(self):
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -11,7 +11,15 @@ 
 import os
 import sys
 import tempfile
-import urllib
+
+try:
+    import urllib
+    quote = urllib.quote
+    unquote = urllib.unquote
+except AttributeError:
+    import urllib.request
+    quote = urllib.request.quote
+    unquote = urllib.request.unquote
 
 from .i18n import _
 from .node import (
@@ -287,7 +295,7 @@ 
             branchmap = {}
             for branchpart in d.splitlines():
                 branchname, branchheads = branchpart.split(' ', 1)
-                branchname = encoding.tolocal(urllib.unquote(branchname))
+                branchname = encoding.tolocal(unquote(branchname))
                 branchheads = decodelist(branchheads)
                 branchmap[branchname] = branchheads
             yield branchmap
@@ -632,7 +640,7 @@ 
     branchmap = repo.branchmap()
     heads = []
     for branch, nodes in branchmap.iteritems():
-        branchname = urllib.quote(encoding.fromlocal(branch))
+        branchname = quote(encoding.fromlocal(branch))
         branchnodes = encodelist(nodes)
         heads.append('%s %s' % (branchname, branchnodes))
     return '\n'.join(heads)
@@ -684,7 +692,7 @@ 
             caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
     if repo.ui.configbool('experimental', 'bundle2-advertise', True):
         capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
-        caps.append('bundle2=' + urllib.quote(capsblob))
+        caps.append('bundle2=' + quote(capsblob))
     caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
     caps.append(
         'httpheader=%d' % repo.ui.configint('server', 'maxhttpheaderlen', 1024))
diff --git a/tests/test-check-py3-compat.t b/tests/test-check-py3-compat.t
--- a/tests/test-check-py3-compat.t
+++ b/tests/test-check-py3-compat.t
@@ -165,8 +165,8 @@ 
   hgext/largefiles/lfutil.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   hgext/largefiles/localstore.py: error importing module: <ImportError> No module named 'lfutil' (line *) (glob)
   hgext/largefiles/overrides.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
-  hgext/largefiles/proto.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
-  hgext/largefiles/remotestore.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
+  hgext/largefiles/proto.py: error importing: <ImportError> No module named 'httplib' (error at httppeer.py:*) (glob)
+  hgext/largefiles/remotestore.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   hgext/largefiles/reposetup.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   hgext/largefiles/uisetup.py: error importing module: <SyntaxError> invalid syntax (archival.py, line *) (line *) (glob)
   hgext/largefiles/wirestore.py: error importing module: <ImportError> No module named 'lfutil' (line *) (glob)
@@ -189,7 +189,6 @@ 
   mercurial/branchmap.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/bundle*.py: invalid syntax: invalid syntax (<unknown>, line *) (glob)
   mercurial/bundlerepo.py: error importing module: <SyntaxError> invalid syntax (bundle*.py, line *) (line *) (glob)
-  mercurial/byterange.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
   mercurial/changegroup.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/changelog.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/cmdutil.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
@@ -203,7 +202,7 @@ 
   mercurial/dirstate.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/discovery.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/dispatch.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
-  mercurial/exchange.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
+  mercurial/exchange.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/extensions.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/filelog.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/filemerge.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
@@ -223,7 +222,7 @@ 
   mercurial/hgweb/wsgicgi.py: error importing module: <SystemError> Parent module 'mercurial.hgweb' not loaded, cannot perform relative import (line *) (glob)
   mercurial/hook.py: error importing: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (error at i18n.py:*) (glob)
   mercurial/httpclient/_readers.py: error importing module: <ImportError> No module named 'httplib' (line *) (glob)
-  mercurial/httpconnection.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
+  mercurial/httpconnection.py: error importing: <ImportError> No module named 'httplib' (error at __init__.py:*) (glob)
   mercurial/httppeer.py: error importing module: <ImportError> No module named 'httplib' (line *) (glob)
   mercurial/keepalive.py: error importing module: <ImportError> No module named 'httplib' (line *) (glob)
   mercurial/localrepo.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
@@ -254,7 +253,7 @@ 
   mercurial/sshpeer.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
   mercurial/sshserver.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
   mercurial/sslutil.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
-  mercurial/statichttprepo.py: error importing module: <ImportError> No module named 'urllib2' (line *) (glob)
+  mercurial/statichttprepo.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
   mercurial/store.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
   mercurial/streamclone.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
   mercurial/subrepo.py: error importing module: <AttributeError> 'NullTranslations' object has no attribute 'ugettext' (line *) (glob)
diff --git a/tests/test-hgweb-auth.py b/tests/test-hgweb-auth.py
--- a/tests/test-hgweb-auth.py
+++ b/tests/test-hgweb-auth.py
@@ -1,5 +1,12 @@ 
+try:
+    import urllib2
+    httppasswordmgrwithdefaultrealm = urllib2.HTTPPasswordMgrWithDefaultRealm
+except AttributeError:
+    import urllib.request
+    httppasswordmgrwithdefaultrealm = \
+        urllib.request.HTTPPasswordMgrWithDefaultRealm
+
 from mercurial import demandimport; demandimport.enable()
-import urllib2
 from mercurial import ui, util
 from mercurial import url
 from mercurial.error import Abort
@@ -99,7 +106,7 @@ 
 
 def testauthinfo(fullurl, authurl):
     print 'URIs:', fullurl, authurl
-    pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
+    pm = httppasswordmgrwithdefaultrealm()
     pm.add_password(*util.url(fullurl).authinfo()[1])
     print pm.find_user_password('test', authurl)