← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~tushar5526/lp-codeimport:port-to-focal-py3.8 into lp-codeimport:focal

 

Tushar Gupta has proposed merging ~tushar5526/lp-codeimport:port-to-focal-py3.8 into lp-codeimport:focal.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~tushar5526/lp-codeimport/+git/lp-codeimport/+merge/488050
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~tushar5526/lp-codeimport:port-to-focal-py3.8 into lp-codeimport:focal.
diff --git a/Makefile b/Makefile
index ff06eb8..67c4c22 100644
--- a/Makefile
+++ b/Makefile
@@ -1,14 +1,14 @@
 # This file modified from Zope3/Makefile
 # Licensed under the ZPL, (c) Zope Corporation and contributors.
 
-PYTHON:=python2.7
+PYTHON:=python3.8
 
 WD:=$(shell pwd)
 PY=$(WD)/bin/py
 PYTHONPATH:=$(WD)/lib:${PYTHONPATH}
 VERBOSITY=-vv
 
-DEPENDENCY_REPO ?= lp:~launchpad/lp-codeimport/+git/dependencies
+DEPENDENCY_REPO ?= https://git.launchpad.net/~launchpad/lp-codeimport/+git/dependencies
 DEPENDENCY_DIR ?= $(WD)/dependencies
 
 # virtualenv and pip fail if setlocale fails, so force a valid locale.
@@ -30,8 +30,6 @@ SITE_PACKAGES := \
 
 TESTOPTS=
 
-SHHH=utilities/shhh.py
-
 LPCONFIG?=development
 
 VERSION_INFO = version-info.py
@@ -120,8 +118,8 @@ requirements/combined.txt: \
 # afterwards.
 build_wheels: $(PIP_BIN) requirements/combined.txt
 	$(RM) -r wheelhouse wheels
-	$(SHHH) $(PIP) wheel -r requirements/setup.txt -w wheels
-	$(SHHH) $(PIP) wheel \
+	$(PIP) wheel -r requirements/setup.txt -w wheels
+	$(PIP) wheel \
 		-c requirements/setup.txt -c requirements/combined.txt \
 		-w wheels .
 	$(RM) wheels/lp_codeimport-[0-9]*.whl
@@ -143,8 +141,8 @@ $(PY): $(DEPENDENCY_DIR) requirements/combined.txt setup.py
 		--extra-search-dir=$(WD)/wheels/ \
 		env
 	ln -sfn env/bin bin
-	$(SHHH) $(PIP) install -r requirements/setup.txt
-	$(SHHH) LPCONFIG=$(LPCONFIG) $(PIP) \
+	$(PIP) install -r requirements/setup.txt
+	LPCONFIG=$(LPCONFIG) $(PIP) \
 		install \
 		-c requirements/setup.txt -c requirements/combined.txt -e . \
 		|| { code=$$?; rm -f $@; exit $$code; }
diff --git a/_pythonpath.py b/_pythonpath.py
index 616dcda..0f2d3b8 100644
--- a/_pythonpath.py
+++ b/_pythonpath.py
@@ -4,75 +4,65 @@
 # This file works if the Python has been started with -S, or if bin/py
 # has been used.
 
-import imp
 import os.path
 import sys
-
+from importlib.util import find_spec
 
 # Get path to this file.
-if __name__ == '__main__':
+if __name__ == "__main__":
     filename = __file__
 else:
     # If this is an imported module, we want the location of the .py
     # file, not the .pyc, because the .py file may have been symlinked.
-    filename = imp.find_module(__name__)[1]
+    filename = find_spec(__name__).origin
 # Get the full, non-symbolic-link directory for this file.  This is the
 # project root.
 top = os.path.dirname(os.path.abspath(os.path.realpath(filename)))
 
-env = os.path.join(top, 'env')
-python_version_path = os.path.join(env, 'python_version')
-
-# If the current Python major/minor version doesn't match the virtualenv,
-# then re-exec.  This makes it practical to experiment with switching
-# between Python 2 and 3 without having to update the #! lines of all our
-# scripts to match.
-python_version = '%s.%s' % sys.version_info[:2]
-with open(python_version_path) as python_version_file:
-    env_python_version = python_version_file.readline().strip()
-if python_version != env_python_version:
-    env_python = os.path.join(env, 'bin', 'python')
-    os.execl(env_python, env_python, *sys.argv)
+env = os.path.join(top, "env")
+python_version = "%s.%s" % sys.version_info[:2]
+stdlib_dir = os.path.join(env, "lib", "python%s" % python_version)
 
-stdlib_dir = os.path.join(env, 'lib', 'python%s' % python_version)
 
-if ('site' in sys.modules and
-    not sys.modules['site'].__file__.startswith(
-        os.path.join(stdlib_dir, 'site.py'))):
-    # We have the wrong site.py, so our paths are not set up correctly.
-    # We blow up, with a hopefully helpful error message.
+if "site" in sys.modules and "lp_sitecustomize" not in sys.modules:
+    # Site initialization has been run but lp_sitecustomize was not loaded,
+    # so something is set up incorrectly.  We blow up, with a hopefully
+    # helpful error message.
     raise RuntimeError(
-        'The wrong site.py is imported (%r imported, %r expected). '
-        'Scripts should usually be '
+        "Python was invoked incorrectly.  Scripts should usually be "
         "started with Launchpad's bin/py, or with a Python invoked with "
-        'the -S flag.' % (
-        sys.modules['site'].__file__, os.path.join(stdlib_dir, 'site.py')))
+        "the -S flag."
+    )
 
 # Ensure that the virtualenv's standard library directory is in sys.path;
 # activate_this will not put it there.
 if stdlib_dir not in sys.path and (stdlib_dir + os.sep) not in sys.path:
     sys.path.insert(0, stdlib_dir)
 
-if not sys.executable.startswith(top + os.sep) or 'site' not in sys.modules:
+if not sys.executable.startswith(top + os.sep) or "site" not in sys.modules:
     # Activate the virtualenv.  Avoid importing lp_sitecustomize here, as
     # activate_this imports site before it's finished setting up sys.path.
-    orig_disable_sitecustomize = os.environ.get('LP_DISABLE_SITECUSTOMIZE')
-    os.environ['LP_DISABLE_SITECUSTOMIZE'] = '1'
+    orig_disable_sitecustomize = os.environ.get("LP_DISABLE_SITECUSTOMIZE")
+    os.environ["LP_DISABLE_SITECUSTOMIZE"] = "1"
     # This is a bit like env/bin/activate_this.py, but to help namespace
     # packages work properly we change sys.prefix before importing site
     # rather than after.
     sys.real_prefix = sys.prefix
     sys.prefix = env
-    os.environ['PATH'] = (
-        os.path.join(env, 'bin') + os.pathsep + os.environ.get('PATH', ''))
+    os.environ["PATH"] = (
+        os.path.join(env, "bin") + os.pathsep + os.environ.get("PATH", "")
+    )
+    os.environ["VIRTUAL_ENV"] = env
     site_packages = os.path.join(
-        env, 'lib', 'python%s' % python_version, 'site-packages')
+        env, "lib", "python%s" % python_version, "site-packages"
+    )
     import site
+
     site.addsitedir(site_packages)
     if orig_disable_sitecustomize is not None:
-        os.environ['LP_DISABLE_SITECUSTOMIZE'] = orig_disable_sitecustomize
+        os.environ["LP_DISABLE_SITECUSTOMIZE"] = orig_disable_sitecustomize
     else:
-        del os.environ['LP_DISABLE_SITECUSTOMIZE']
+        del os.environ["LP_DISABLE_SITECUSTOMIZE"]
 
 # Move all our own directories to the front of the path.
 new_sys_path = []
@@ -83,7 +73,8 @@ for item in list(sys.path):
 sys.path[:0] = new_sys_path
 
 # Initialise the Launchpad environment.
-if 'LP_DISABLE_SITECUSTOMIZE' not in os.environ:
-    if 'lp_sitecustomize' not in sys.modules:
+if "LP_DISABLE_SITECUSTOMIZE" not in os.environ:
+    if "lp_sitecustomize" not in sys.modules:
         import lp_sitecustomize
+
         lp_sitecustomize.main()
diff --git a/configs/unique-from-test-workermonitor-py-line775-100007/codeimport-lazr.conf b/configs/unique-from-test-workermonitor-py-line775-100007/codeimport-lazr.conf
new file mode 100644
index 0000000..04c8569
--- /dev/null
+++ b/configs/unique-from-test-workermonitor-py-line775-100007/codeimport-lazr.conf
@@ -0,0 +1,5 @@
+[meta]
+extends: ../testrunner/codeimport-lazr.conf
+
+[codeimportdispatcher]
+codeimportscheduler_url: http://localhost:44547/
diff --git a/configs/unique-from-test-workermonitor-py-line775-100007/test-process-lazr.conf b/configs/unique-from-test-workermonitor-py-line775-100007/test-process-lazr.conf
new file mode 100644
index 0000000..7a22de3
--- /dev/null
+++ b/configs/unique-from-test-workermonitor-py-line775-100007/test-process-lazr.conf
@@ -0,0 +1,5 @@
+# This is an empty conf file used strictly to verify that the process_name
+# controls the conf file that is loaded.
+
+[meta]
+extends: codeimport-lazr.conf
diff --git a/cronscripts/code-import-dispatcher.py b/cronscripts/code-import-dispatcher.py
index 2810356..d9a339b 100755
--- a/cronscripts/code-import-dispatcher.py
+++ b/cronscripts/code-import-dispatcher.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python2 -S
+#!/usr/bin/python3 -S
 #
 # Copyright 2009 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
diff --git a/lib/devscripts/sourcecode.py b/lib/devscripts/sourcecode.py
index 0ab7650..c40b6d8 100644
--- a/lib/devscripts/sourcecode.py
+++ b/lib/devscripts/sourcecode.py
@@ -41,21 +41,21 @@ try:
     from breezy.upgrade import upgrade
     from breezy.workingtree import WorkingTree
 except ImportError:
-    from bzrlib import ui
-    from bzrlib.branch import Branch
-    from bzrlib.errors import (
+    from breezy import ui
+    from breezy.branch import Branch
+    from breezy.errors import (
         BzrError,
         IncompatibleRepositories,
         NotBranchError,
         )
-    from bzrlib.plugin import load_plugins
-    from bzrlib.revisionspec import RevisionSpec
-    from bzrlib.trace import (
+    from breezy.plugin import load_plugins
+    from breezy.revisionspec import RevisionSpec
+    from breezy.trace import (
         enable_default_logging,
         report_exception,
         )
-    from bzrlib.upgrade import upgrade
-    from bzrlib.workingtree import WorkingTree
+    from breezy.upgrade import upgrade
+    from breezy.workingtree import WorkingTree
 
 from devscripts import get_launchpad_root
 
diff --git a/lib/devscripts/tests/test_sourcecode.py b/lib/devscripts/tests/test_sourcecode.py
index 368ea0d..1899dfa 100644
--- a/lib/devscripts/tests/test_sourcecode.py
+++ b/lib/devscripts/tests/test_sourcecode.py
@@ -19,12 +19,12 @@ import unittest
 
 try:
     from breezy.bzr.bzrdir import BzrDir
-    from breezy.tests import TestCase
+    from breezy import TestCase
     from breezy.transport import get_transport
 except ImportError:
-    from bzrlib.bzrdir import BzrDir
-    from bzrlib.tests import TestCase
-    from bzrlib.transport import get_transport
+    from breezy import BzrDir
+    from breezy import TestCase
+    from breezy.transport import get_transport
 
 import six
 
diff --git a/lib/lp/app/tests/test_versioninfo.py b/lib/lp/app/tests/test_versioninfo.py
index 7db9b35..fcbf231 100644
--- a/lib/lp/app/tests/test_versioninfo.py
+++ b/lib/lp/app/tests/test_versioninfo.py
@@ -19,6 +19,6 @@ class TestVersionInfo(unittest.TestCase):
         args = [os.path.join(TREE_ROOT, "bin/py"), "-c",
                 "from lp.app.versioninfo import revision;"
                 "print(revision)"]
-        process = subprocess.Popen(args, cwd='/tmp', stdout=subprocess.PIPE)
-        (output, errors) = process.communicate(None)
+        process = subprocess.Popen(args, cwd='/tmp', stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
+        output, errors = process.communicate()
         self.assertEqual(revision, output.rstrip("\n"))
diff --git a/lib/lp/codehosting/__init__.py b/lib/lp/codehosting/__init__.py
index ad12914..37f06f6 100644
--- a/lib/lp/codehosting/__init__.py
+++ b/lib/lp/codehosting/__init__.py
@@ -27,7 +27,7 @@ from lp.services.config import config
 
 
 if six.PY2:
-    from bzrlib.plugin import load_plugins as bzr_load_plugins
+    from breezy.plugin import load_plugins as bzr_load_plugins
     # This import is needed so that bzr's logger gets registered.
     import bzrlib.trace  # noqa: F401
 
diff --git a/lib/lp/codehosting/codeimport/tests/servers.py b/lib/lp/codehosting/codeimport/tests/servers.py
index f5f444b..86e8edf 100644
--- a/lib/lp/codehosting/codeimport/tests/servers.py
+++ b/lib/lp/codehosting/codeimport/tests/servers.py
@@ -31,16 +31,16 @@ import threading
 import time
 from wsgiref.simple_server import make_server
 
-from bzrlib.branch import Branch
-from bzrlib.branchbuilder import BranchBuilder
-from bzrlib.bzrdir import BzrDir
-from bzrlib.tests.test_server import (
-    ReadonlySmartTCPServer_for_testing,
+from breezy.branch import Branch
+from breezy.branchbuilder import BranchBuilder
+from breezy.bzr.bzrdir import BzrDir
+from breezy.tests.test_server import (
     TestServer,
+    ReadonlySmartTCPServer_for_testing,
     )
-from bzrlib.tests.treeshape import build_tree_contents
-from bzrlib.transport import Server
-from bzrlib.urlutils import (
+from breezy.tests.treeshape import build_tree_contents
+from breezy.transport import Server
+from breezy.urlutils import (
     escape,
     join as urljoin,
     )
@@ -368,14 +368,14 @@ class GitServer(Server):
         self.createRepository(repository_path, bare=self._use_server)
         repo = GitRepo(repository_path)
         blobs = [
-            (Blob.from_string(contents), filename) for (filename, contents)
+            (Blob.from_string(contents.encode("utf-8")), filename.encode("utf-8")) for (filename, contents)
             in tree_contents]
         repo.object_store.add_objects(blobs)
         root_id = dulwich.index.commit_tree(repo.object_store, [
             (filename, b.id, stat.S_IFREG | 0o644)
             for (b, filename) in blobs])
-        repo.do_commit(committer='Joe Foo <joe@xxxxxxx>',
-            message=u'<The commit message>', tree=root_id)
+        repo.do_commit(committer=b'Joe Foo <joe@xxxxxxx>',
+            message=b'<The commit message>', tree=root_id)
 
 
 class BzrServer(Server):
diff --git a/lib/lp/codehosting/codeimport/tests/test_foreigntree.py b/lib/lp/codehosting/codeimport/tests/test_foreigntree.py
index ff378ff..8f9480a 100644
--- a/lib/lp/codehosting/codeimport/tests/test_foreigntree.py
+++ b/lib/lp/codehosting/codeimport/tests/test_foreigntree.py
@@ -14,7 +14,7 @@ __metaclass__ = type
 import os
 import time
 
-from bzrlib.tests import TestCaseWithTransport
+from breezy.tests import TestCaseWithTransport
 import CVS
 
 from lp.codehosting.codeimport.foreigntree import CVSWorkingTree
diff --git a/lib/lp/codehosting/codeimport/tests/test_uifactory.py b/lib/lp/codehosting/codeimport/tests/test_uifactory.py
index 4873bb3..eef9218 100644
--- a/lib/lp/codehosting/codeimport/tests/test_uifactory.py
+++ b/lib/lp/codehosting/codeimport/tests/test_uifactory.py
@@ -146,9 +146,9 @@ class TestLoggingUIFactory(TestCase):
 
     def test_show_warning_unicode(self):
         factory = self.makeLoggingUIFactory()
-        factory.show_warning(u"Peach\xeas")
+        factory.show_warning("Peach\xeas")
         self.assertEqual(
-            "WARNING Peach\xc3\xaas\n", self.logger.getLogBuffer())
+            "WARNING Peachês\n", self.logger.getLogBuffer())
 
     def test_user_warning(self):
         factory = self.makeLoggingUIFactory()
diff --git a/lib/lp/codehosting/codeimport/tests/test_worker.py b/lib/lp/codehosting/codeimport/tests/test_worker.py
index 46ff8da..0a1c83c 100644
--- a/lib/lp/codehosting/codeimport/tests/test_worker.py
+++ b/lib/lp/codehosting/codeimport/tests/test_worker.py
@@ -19,38 +19,38 @@ import tempfile
 import time
 from uuid import uuid4
 
-from bzrlib import (
+from breezy import (
     trace,
     urlutils,
     )
-from bzrlib.branch import (
+from breezy.bzr.branch import (
     Branch,
     BranchReferenceFormat,
     )
-from bzrlib.branchbuilder import BranchBuilder
-from bzrlib.bzrdir import (
+from breezy.branchbuilder import BranchBuilder
+from breezy.bzr.bzrdir import (
     BzrDir,
     BzrDirFormat,
-    format_registry,
     )
-from bzrlib.errors import NoSuchFile
-from bzrlib.tests import (
+from breezy.branch import format_registry
+from breezy.errors import NoSuchFile
+from breezy.tests import (
     http_utils,
     TestCaseWithTransport,
     )
-from bzrlib.transport import (
+from breezy.transport import (
     get_transport,
     get_transport_from_url,
     )
-from bzrlib.url_policy_open import (
+from breezy.url_policy_open import (
     AcceptAnythingPolicy,
     BadUrl,
     BranchOpener,
     BranchOpenPolicy,
     )
-from bzrlib.url_policy_open import \
+from breezy.url_policy_open import \
     _BlacklistPolicy as DenylistPolicy  # wokeignore:rule=blacklist
-from bzrlib.urlutils import (
+from breezy.urlutils import (
     join as urljoin,
     local_path_from_url,
     )
@@ -1178,7 +1178,7 @@ class TestGitImport(WorkerTest, TestActualImportMixin,
         paths to database connections, which happily returns the connection
         that corresponds to a path that no longer exists.
         """
-        from bzrlib.plugins.git.cache import mapdbs
+        from breezy.plugins.git.cache import mapdbs
         mapdbs().clear()
 
     def makeImportWorker(self, source_details, opener_policy):
@@ -1258,7 +1258,7 @@ class TestBzrSvnImport(WorkerTest, SubversionImportHelpers,
     def test_pushBazaarBranch_saves_bzr_svn_cache(self):
         # BzrSvnImportWorker.pushBazaarBranch saves a tarball of the bzr-svn
         # cache in the worker's ImportDataStore.
-        from bzrlib.plugins.svn.cache import get_cache
+        from breezy.plugins.svn.cache import get_cache
         worker = self.makeImportWorker(self.makeSourceDetails(
             'trunk', [('README', b'Original contents')]),
             opener_policy=AcceptAnythingPolicy())
@@ -1280,7 +1280,7 @@ class TestBzrSvnImport(WorkerTest, SubversionImportHelpers,
         # BzrSvnImportWorker.getBazaarBranch fetches the tarball of the
         # bzr-svn cache from the worker's ImportDataStore and expands it
         # into the appropriate cache directory.
-        from bzrlib.plugins.svn.cache import get_cache
+        from breezy.plugins.svn.cache import get_cache
         worker = self.makeImportWorker(self.makeSourceDetails(
             'trunk', [('README', b'Original contents')]),
             opener_policy=AcceptAnythingPolicy())
diff --git a/lib/lp/codehosting/codeimport/tests/test_workermonitor.py b/lib/lp/codehosting/codeimport/tests/test_workermonitor.py
index 56b3112..e84ec9e 100644
--- a/lib/lp/codehosting/codeimport/tests/test_workermonitor.py
+++ b/lib/lp/codehosting/codeimport/tests/test_workermonitor.py
@@ -14,12 +14,13 @@ __metaclass__ = type
 import io
 import os
 import shutil
+import six
 import subprocess
 import tempfile
 from textwrap import dedent
 
-from bzrlib.branch import Branch
-from bzrlib.tests import TestCaseInTempDir
+from breezy.branch import Branch
+from breezy.tests import TestCaseInTempDir
 from dulwich.repo import Repo as GitRepo
 from fixtures import MockPatchObject
 import oops_twisted
@@ -80,7 +81,6 @@ from lp.services.webapp import errorlog
 from lp.testing import TestCase
 from lp.xmlrpc.faults import NoSuchCodeImportJob
 
-
 class TestWorkerMonitorProtocol(ProcessTestsMixin, TestCase):
 
     class StubWorkerMonitor:
@@ -89,7 +89,9 @@ class TestWorkerMonitorProtocol(ProcessTestsMixin, TestCase):
             self.calls = []
 
         def updateHeartbeat(self, tail):
-            self.calls.append(('updateHeartbeat', tail))
+            # updateHeartbeat implementation in lib/lp/codehosting/codeimport/workermonitor.py 
+            # converts tail of "bytes" type to string
+            self.calls.append(('updateHeartbeat', six.ensure_text(tail, errors='replace')))
 
     def setUp(self):
         self.worker_monitor = self.StubWorkerMonitor()
@@ -149,12 +151,12 @@ class TestWorkerMonitorProtocol(ProcessTestsMixin, TestCase):
         # outReceived updates the tail of the log, currently and arbitrarily
         # defined to be the last 5 lines of the log.
         lines = ['line %d' % number for number in range(1, 7)]
-        self.protocol.outReceived('\n'.join(lines[:3]) + '\n')
+        self.protocol.outReceived(('\n'.join(lines[:3]) + '\n').encode("utf-8"))
         self.assertEqual(
-            self.protocol._tail, 'line 1\nline 2\nline 3\n')
-        self.protocol.outReceived('\n'.join(lines[3:]) + '\n')
+            self.protocol._tail, b'line 1\nline 2\nline 3\n')
+        self.protocol.outReceived(('\n'.join(lines[3:]) + '\n').encode("utf-8"))
         self.assertEqual(
-            self.protocol._tail, 'line 3\nline 4\nline 5\nline 6\n')
+            self.protocol._tail, b'line 3\nline 4\nline 5\nline 6\n')
 
 
 class FakeCodeImportScheduler(xmlrpc.XMLRPC, object):
@@ -219,13 +221,13 @@ class TestWorkerMonitorUnit(FakeCodeImportSchedulerMixin, TestCase):
         self.scheduler, scheduler_url = self.makeFakeCodeImportScheduler(
             {job_id: job_data})
         return CodeImportWorkerMonitor(
-            job_id, BufferLogger(), xmlrpc.Proxy(scheduler_url), "anything")
+            job_id, BufferLogger(), xmlrpc.Proxy(scheduler_url.encode("UTF-8")), "anything")
 
     def makeWorkerMonitorWithoutJob(self, fault=None):
         self.scheduler, scheduler_url = self.makeFakeCodeImportScheduler(
             {}, fault)
         return CodeImportWorkerMonitor(
-            1, BufferLogger(), xmlrpc.Proxy(scheduler_url), None)
+            1, BufferLogger(), xmlrpc.Proxy(scheduler_url.encode("UTF-8")), None)
 
     def test_getWorkerArguments(self):
         # getWorkerArguments returns a deferred that fires with the
@@ -503,7 +505,7 @@ class TestWorkerMonitorRunNoProcess(FakeCodeImportSchedulerMixin, TestCase):
             job_data = {}
         _, scheduler_url = self.makeFakeCodeImportScheduler(job_data)
         return self.WorkerMonitor(
-            1, BufferLogger(), xmlrpc.Proxy(scheduler_url), "anything",
+            1, BufferLogger(), xmlrpc.Proxy(scheduler_url.encode("UTF-8")), "anything",
             process_deferred)
 
     def assertFinishJobCalledWithStatus(self, ignored, worker_monitor,
@@ -809,11 +811,11 @@ class TestWorkerMonitorIntegration(FakeCodeImportSchedulerMixin,
         job_id, job_data = self.getStartedJobForImport(
             self.makeGitCodeImport(target_rcs_type='git'))
         source_repo = GitRepo(os.path.join(self.repo_path, "source"))
-        commit = source_repo.refs["refs/heads/master"]
-        source_repo.refs["refs/heads/one"] = commit
-        source_repo.refs["refs/heads/two"] = commit
-        source_repo.refs.set_symbolic_ref("HEAD", "refs/heads/one")
-        del source_repo.refs["refs/heads/master"]
+        commit = source_repo.refs[b"refs/heads/master"]
+        source_repo.refs[b"refs/heads/one"] = commit
+        source_repo.refs[b"refs/heads/two"] = commit
+        source_repo.refs.set_symbolic_ref(b"HEAD", b"refs/heads/one")
+        del source_repo.refs[b"refs/heads/master"]
         target_repo_path = os.path.join(
             self.target_store, job_data['arguments'][0])
         self.target_git_server.makeRepo(
diff --git a/lib/lp/codehosting/codeimport/uifactory.py b/lib/lp/codehosting/codeimport/uifactory.py
index 1ac52c2..163f5c8 100644
--- a/lib/lp/codehosting/codeimport/uifactory.py
+++ b/lib/lp/codehosting/codeimport/uifactory.py
@@ -16,8 +16,8 @@ __all__ = ['LoggingUIFactory']
 import sys
 import time
 
-from bzrlib.ui import NoninteractiveUIFactory
-from bzrlib.ui.text import TextProgressView
+from breezy.ui import NoninteractiveUIFactory
+from breezy.ui.text import TextProgressView
 import six
 
 
@@ -52,7 +52,7 @@ class LoggingUIFactory(NoninteractiveUIFactory):
             "%s", self.format_user_warning(warning_id, message_args))
 
     def show_warning(self, msg):
-        self.logger.warning("%s", six.ensure_binary(msg))
+        self.logger.warning("%s", msg)
 
     def get_username(self, prompt, **kwargs):
         return None
@@ -149,7 +149,7 @@ class LoggingTextProgressView(TextProgressView):
         # We just report the amount of data transferred.
         return '%s bytes transferred' % self._bytes_since_update
 
-    # What's below is copied and pasted from bzrlib.ui.text.TextProgressView
+    # What's below is copied and pasted from breezy.ui.text.TextProgressView
     # and changed to (a) get its notion of time from self.time_source (which
     # can be replaced by a deterministic time source in tests) rather than
     # time.time and (b) respect the _update_repaint_frequency,
diff --git a/lib/lp/codehosting/codeimport/worker.py b/lib/lp/codehosting/codeimport/worker.py
index 0afc073..b0603f7 100644
--- a/lib/lp/codehosting/codeimport/worker.py
+++ b/lib/lp/codehosting/codeimport/worker.py
@@ -8,6 +8,8 @@ from __future__ import (
     print_function,
     )
 
+import breezy.plugins
+
 
 __metaclass__ = type
 __all__ = [
@@ -37,34 +39,37 @@ import subprocess
 # line below this comment.
 import lp.codehosting  # noqa: F401  # isort: split
 
-from bzrlib.branch import (
+from breezy.branch import (
     Branch,
     InterBranch,
     )
-from bzrlib.bzrdir import (
+from breezy.bzr.bzrdir import (
     BzrDir,
     BzrDirFormat,
     )
-from bzrlib.errors import (
+
+from breezy.errors import (
     ConnectionError,
-    InvalidEntryName,
     NoRepositoryPresent,
     NoSuchFile,
     NotBranchError,
     TooManyRedirections,
     )
-from bzrlib.transport import (
+
+from breezy.bzr.inventory import InvalidEntryName
+
+from breezy.transport import (
     get_transport_from_path,
     get_transport_from_url,
     )
-import bzrlib.ui
-from bzrlib.upgrade import upgrade
-from bzrlib.url_policy_open import (
+import breezy.ui
+from breezy.upgrade import upgrade
+from breezy.url_policy_open import (
     BadUrl,
     BranchOpener,
     BranchOpenPolicy,
     )
-from bzrlib.urlutils import (
+from breezy.urlutils import (
     join as urljoin,
     local_path_from_url,
     )
@@ -831,7 +836,7 @@ class GitImportWorker(PullingImportWorker):
 
     @property
     def unsupported_feature_exceptions(self):
-        from bzrlib.plugins.git.fetch import SubmodulesRequireSubtrees
+        from breezy.plugins.git.fetch import SubmodulesRequireSubtrees
         return [
             InvalidEntryName,
             SubmodulesRequireSubtrees,
@@ -844,7 +849,7 @@ class GitImportWorker(PullingImportWorker):
     @property
     def probers(self):
         """See `PullingImportWorker.probers`."""
-        from bzrlib.plugins.git import (
+        from breezy.plugins.git import (
             LocalGitProber,
             RemoteGitProber,
             )
@@ -905,7 +910,7 @@ class BzrSvnImportWorker(PullingImportWorker):
 
     @property
     def unsupported_feature_exceptions(self):
-        from bzrlib.plugins.svn.errors import InvalidFileName
+        from breezy.plugins.svn.errors import InvalidFileName
         return [
             InvalidEntryName,
             InvalidFileName,
@@ -913,7 +918,7 @@ class BzrSvnImportWorker(PullingImportWorker):
 
     @property
     def broken_remote_exceptions(self):
-        from bzrlib.plugins.svn.errors import IncompleteRepositoryHistory
+        from breezy.plugins.svn.errors import IncompleteRepositoryHistory
         return [IncompleteRepositoryHistory]
 
     def getRevisionLimit(self):
@@ -923,7 +928,7 @@ class BzrSvnImportWorker(PullingImportWorker):
     @property
     def probers(self):
         """See `PullingImportWorker.probers`."""
-        from bzrlib.plugins.svn import SvnRemoteProber
+        from breezy.plugins.svn import SvnRemoteProber
         return [SvnRemoteProber]
 
     def getBazaarBranch(self):
@@ -933,7 +938,7 @@ class BzrSvnImportWorker(PullingImportWorker):
         cache from the import data store and put it where bzr-svn will find
         it.
         """
-        from bzrlib.plugins.svn.cache import create_cache_dir
+        from breezy.plugins.svn.cache import create_cache_dir
         branch = super(BzrSvnImportWorker, self).getBazaarBranch()
         local_name = 'svn-cache.tar.gz'
         if self.import_data_store.fetch(local_name):
@@ -946,7 +951,7 @@ class BzrSvnImportWorker(PullingImportWorker):
         In addition to the superclass' behaviour, we store bzr-svn's cache
         directory in the import data store.
         """
-        from bzrlib.plugins.svn.cache import get_cache
+        from breezy.plugins.svn.cache import get_cache
         non_trivial = super(BzrSvnImportWorker, self).pushBazaarBranch(
             bazaar_branch)
         if remote_branch is not None:
@@ -981,7 +986,7 @@ class BzrImportWorker(PullingImportWorker):
     @property
     def probers(self):
         """See `PullingImportWorker.probers`."""
-        from bzrlib.bzrdir import (
+        from breezy import (
             BzrProber,
             RemoteBzrProber,
             )
diff --git a/lib/lp/codehosting/tests/helpers.py b/lib/lp/codehosting/tests/helpers.py
index 7173e74..b083875 100644
--- a/lib/lp/codehosting/tests/helpers.py
+++ b/lib/lp/codehosting/tests/helpers.py
@@ -21,8 +21,8 @@ def create_branch_with_one_revision(branch_dir, format=None):
     """Create a test Bazaar branch at the given directory."""
     # XXX cjwatson 2019-06-13: This still uses bzrlib until such time as the
     # code import workers are ported to Breezy.
-    from bzrlib.bzrdir import BzrDir
-    from bzrlib.errors import FileExists
+    from breezy import BzrDir
+    from breezy.errors import FileExists
     if not os.path.exists(branch_dir):
         os.makedirs(branch_dir)
     try:
diff --git a/requirements/lp-codeimport.txt b/requirements/lp-codeimport.txt
index d8f99da..8f3c399 100644
--- a/requirements/lp-codeimport.txt
+++ b/requirements/lp-codeimport.txt
@@ -10,6 +10,7 @@ appdirs==1.4.3
 asn1crypto==0.23.0
 attrs==19.1.0
 Automat==0.6.0
+breezy==3.2.0
 bson==0.5.9
 # lp:~launchpad/bzr/lp
 bzr==2.6.0.lp.4
@@ -20,7 +21,7 @@ cryptography==2.7
 Cython==0.29.17
 defusedxml==0.6.0
 distro==1.4.0
-dulwich==0.18.6
+dulwich==0.19.16
 enum34==1.1.6
 httplib2==0.8
 hyperlink==18.0.0
@@ -61,7 +62,7 @@ setuptools-git==1.2
 setuptools-scm==3.4.3
 six==1.15.0
 subprocess32==3.2.6
-subvertpy==0.9.1
+subvertpy==0.11.0
 testresources==0.2.7
 testscenarios==0.4
 timeline==0.0.7
@@ -70,7 +71,7 @@ Twisted==19.2.1
 unittest2==1.1.0+lp1
 urllib3==1.25.11
 vine==1.1.4
-virtualenv-tools3==2.0.0
+virtualenv-tools3==3.1.1
 wadllib==1.3.2
 # lp:~launchpad-committers/zope.testrunner:launchpad
 zope.testrunner==5.2+lp1
diff --git a/scripts/code-import-worker-monitor.py b/scripts/code-import-worker-monitor.py
index ba87951..ef612e0 100755
--- a/scripts/code-import-worker-monitor.py
+++ b/scripts/code-import-worker-monitor.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python2 -S
+#!/usr/bin/python3 -S
 #
 # Copyright 2009 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
diff --git a/scripts/code-import-worker.py b/scripts/code-import-worker.py
index b968b50..f20f4b1 100755
--- a/scripts/code-import-worker.py
+++ b/scripts/code-import-worker.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python2 -S
+#!/usr/bin/python3 -S
 #
 # Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
@@ -21,8 +21,8 @@ from optparse import OptionParser
 import os
 import sys
 
-from bzrlib.transport import get_transport
-from bzrlib.url_policy_open import AcceptAnythingPolicy
+from breezy.transport import get_transport
+from breezy.url_policy_open import AcceptAnythingPolicy
 
 from lp.codehosting.codeimport.worker import (
     BzrImportWorker,
@@ -54,7 +54,7 @@ def force_bzr_to_use_urllib():
     prevents a significant number of import branchs from updating.  Also see
     https://bugs.launchpad.net/bzr/+bug/516222.
     """
-    from bzrlib.transport import register_lazy_transport
+    from breezy.transport import register_lazy_transport
     register_lazy_transport('http://', 'bzrlib.transport.http._urllib',
                             'HttpTransport_urllib')
     register_lazy_transport('https://', 'bzrlib.transport.http._urllib',
diff --git a/scripts/update-version-info.sh b/scripts/update-version-info.sh
index 54e777e..b888d48 100755
--- a/scripts/update-version-info.sh
+++ b/scripts/update-version-info.sh
@@ -22,7 +22,7 @@ branch_nick="$(git rev-parse --abbrev-ref HEAD | sed "s/'/\\\\'/g")"
 revision_id="$(git rev-parse HEAD)"
 date="$(git show -s --format=%ci HEAD)"
 cat > $newfile <<EOF
-#! /usr/bin/env python
+#! /usr/bin/env python3
 
 from __future__ import print_function
 
@@ -36,7 +36,7 @@ if __name__ == '__main__':
     print('revision id: %(revision_id)s' % version_info)
 EOF
 
-revision_id=$(python $newfile | sed -n 's/^revision id: //p')
+revision_id=$(python3 $newfile | sed -n 's/^revision id: //p')
 if ! [ -f version-info.py ]; then
     echo "Creating version-info.py at revision $revision_id"
     mv ${newfile} version-info.py
diff --git a/setup.py b/setup.py
index 26bbcc4..94403a8 100644
--- a/setup.py
+++ b/setup.py
@@ -102,11 +102,20 @@ class lp_develop(develop):
                 os.execv(sys.executable, [sys.executable] + sys.argv[1:])
                 """)
             self.write_script("py", py_header + py_script_text)
-
+            
+            # Install site customizations for this virtualenv.  In principle
+            # we just want to install sitecustomize and have site load it,
+            # but this doesn't work with virtualenv 20.x
+            # (https://github.com/pypa/virtualenv/issues/1703).  Note that
+            # depending on the resolution of
+            # https://bugs.python.org/issue33944 we may need to change this
+            # again in future.
             env_top = os.path.join(os.path.dirname(__file__), "env")
-            stdlib_dir = get_python_lib(standard_lib=True, prefix=env_top)
+            site_packages_dir = get_python_lib(prefix=env_top)
             orig_sitecustomize = self._get_orig_sitecustomize()
-            sitecustomize_path = os.path.join(stdlib_dir, "sitecustomize.py")
+            sitecustomize_path = os.path.join(
+                site_packages_dir, "_sitecustomize.py"
+            )
             with open(sitecustomize_path, "w") as sitecustomize_file:
                 sitecustomize_file.write(dedent("""\
                     import os
@@ -120,19 +129,20 @@ class lp_develop(develop):
                 if orig_sitecustomize:
                     sitecustomize_file.write(orig_sitecustomize)
 
+            # Awkward naming; this needs to come lexicographically after any
+            # other .pth files.
+            sitecustomize_pth_path = os.path.join(
+                site_packages_dir, "zzz_run_venv_sitecustomize.pth"
+            )
+            with open(sitecustomize_pth_path, "w") as sitecustomize_pth_file:
+                sitecustomize_pth_file.write("import _sitecustomize\n")
+
             # Write out the build-time value of LPCONFIG so that it can be
             # used by scripts as the default instance name.
             instance_name_path = os.path.join(env_top, "instance_name")
             with open(instance_name_path, "w") as instance_name_file:
                 print(os.environ["LPCONFIG"], file=instance_name_file)
 
-            # Write out the build-time Python major/minor version so that
-            # scripts run with /usr/bin/python2 know whether they need to
-            # re-exec.
-            python_version_path = os.path.join(env_top, "python_version")
-            with open(python_version_path, "w") as python_version_file:
-                print("%s.%s" % sys.version_info[:2], file=python_version_file)
-
 
 __version__ = '0.1'
 
@@ -151,6 +161,7 @@ setup(
         # XXX cjwatson 2020-08-07: This should eventually be removed
         # entirely, but we need to retain it until codeimport has been
         # ported to Breezy.
+        'breezy',
         'bzr; python_version < "3"',
         'contextlib2; python_version < "3.3"',
         'defusedxml',
@@ -179,7 +190,7 @@ setup(
         'six',
         # XXX cjwatson 2020-08-07: Temporarily dropped on Python 3 until
         # codeimport can be ported to Breezy.
-        'subvertpy; python_version < "3"',
+        'subvertpy',
         'testscenarios',
         'testtools',
         'timeline',
diff --git a/system-dependencies.txt b/system-dependencies.txt
index e6b4ea2..c0e0699 100644
--- a/system-dependencies.txt
+++ b/system-dependencies.txt
@@ -1,14 +1,14 @@
 build-essential
-bzr
 cvs
 git
 libffi-dev
 libssl-dev
 libsvn-dev
-python-dev
-python-pkg-resources
-python-sqlite
-python-tdb
+python3-dev
+python3-pkg-resources
+sqlite3
+python3-tdb
 rabbitmq-server
 subversion
 virtualenv
+python3-subvertpy
diff --git a/utilities/link-system-packages.py b/utilities/link-system-packages.py
index 7ec75f1..03c0e9c 100755
--- a/utilities/link-system-packages.py
+++ b/utilities/link-system-packages.py
@@ -1,4 +1,4 @@
-#! /usr/bin/python2
+#! /usr/bin/python3
 
 # Copyright 2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
diff --git a/utilities/lsconf.py b/utilities/lsconf.py
index 9f20c50..25d0eea 100755
--- a/utilities/lsconf.py
+++ b/utilities/lsconf.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python2 -S
+#!/usr/bin/python3 -S
 #
 # Copyright 2009 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
diff --git a/utilities/make-requirements.py b/utilities/make-requirements.py
index 1f784e0..6e150bc 100755
--- a/utilities/make-requirements.py
+++ b/utilities/make-requirements.py
@@ -1,4 +1,4 @@
-#! /usr/bin/python2
+#! /usr/bin/python3
 
 # Copyright 2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
diff --git a/utilities/relocate-virtualenv b/utilities/relocate-virtualenv
index 80e1136..9536beb 100755
--- a/utilities/relocate-virtualenv
+++ b/utilities/relocate-virtualenv
@@ -14,7 +14,9 @@ fi
 
 # virtualenv-tools does most of the hard work.  We must explicitly invoke it
 # with the virtualenv's Python, as its #! line is probably wrong.
-"$1/bin/python" "$1/bin/virtualenv-tools" --update-path=auto "$1"
+
+LC_ALL=C.UTF-8 \
+    "$1/bin/python" "$1/bin/virtualenv-tools" --update-path=auto "$1"
 
 # Fix up a few things that virtualenv-tools doesn't handle.
 top="$(readlink -f "$(dirname "$0")/..")"
diff --git a/utilities/shhh.py b/utilities/shhh.py
index 63ce9ed..8612a4b 100755
--- a/utilities/shhh.py
+++ b/utilities/shhh.py
@@ -1,4 +1,4 @@
-#! /usr/bin/python2 -S
+#! /usr/bin/python3 -S
 #
 # Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
diff --git a/utilities/update-copyright b/utilities/update-copyright
index 3d86539..64668d9 100755
--- a/utilities/update-copyright
+++ b/utilities/update-copyright
@@ -1,4 +1,4 @@
-#!/usr/bin/python2
+#!/usr/bin/python3
 #
 # Copyright 2010-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
diff --git a/utilities/update-sourcecode b/utilities/update-sourcecode
index df0dac7..2607166 100755
--- a/utilities/update-sourcecode
+++ b/utilities/update-sourcecode
@@ -1,4 +1,4 @@
-#!/usr/bin/python2 -u
+#!/usr/bin/python3 -u
 #
 # Copyright 2009 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).

Follow ups