← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:remove-codeimport into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:remove-codeimport into launchpad:master with ~cjwatson/launchpad:move-instrument-method-helpers as a prerequisite.

Commit message:
Remove codeimport, which is now in a separate tree

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/395802

This now lives in https://git.launchpad.net/lp-codeimport.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:remove-codeimport into launchpad:master.
diff --git a/README b/README
index 9682c4c..d09ef4f 100644
--- a/README
+++ b/README
@@ -89,8 +89,8 @@ You can spend years hacking on Launchpad full-time and not know what all of
 the files in the top-level directory are for.  However, here's a guide to some
 of the ones that come up from time to time.
 
-  bzrplugins/
-    Bazaar plugins used in running Launchpad.
+  brzplugins/
+    Breezy plugins used in running Launchpad.
 
   sourcecode/
     A directory into which we symlink branches of some of Launchpad's
diff --git a/bzrplugins/git b/bzrplugins/git
deleted file mode 120000
index cf14f27..0000000
--- a/bzrplugins/git
+++ /dev/null
@@ -1 +0,0 @@
-../sourcecode/bzr-git
\ No newline at end of file
diff --git a/bzrplugins/svn b/bzrplugins/svn
deleted file mode 120000
index 15208fc..0000000
--- a/bzrplugins/svn
+++ /dev/null
@@ -1 +0,0 @@
-../sourcecode/bzr-svn
\ No newline at end of file
diff --git a/cronscripts/code-import-dispatcher.py b/cronscripts/code-import-dispatcher.py
deleted file mode 100755
index 1225de7..0000000
--- a/cronscripts/code-import-dispatcher.py
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/python2 -S
-#
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Look for and dispatch code import jobs as needed."""
-
-import _pythonpath
-
-from six.moves.xmlrpc_client import ServerProxy
-
-from lp.codehosting.codeimport.dispatcher import CodeImportDispatcher
-from lp.services.config import config
-from lp.services.scripts.base import LaunchpadScript
-from lp.services.webapp.errorlog import globalErrorUtility
-
-
-class CodeImportDispatcherScript(LaunchpadScript):
-
-    def add_my_options(self):
-        self.parser.add_option(
-            "--max-jobs", dest="max_jobs", type=int,
-            default=config.codeimportdispatcher.max_jobs_per_machine,
-            help="The maximum number of jobs to run on this machine.")
-
-    def run(self, use_web_security=False, isolation=None):
-        """See `LaunchpadScript.run`.
-
-        We override to avoid all of the setting up all of the component
-        architecture and connecting to the database.
-        """
-        self.main()
-
-    def main(self):
-        globalErrorUtility.configure('codeimportdispatcher')
-
-        dispatcher = CodeImportDispatcher(self.logger, self.options.max_jobs)
-        dispatcher.findAndDispatchJobs(
-            ServerProxy(config.codeimportdispatcher.codeimportscheduler_url))
-
-
-if __name__ == '__main__':
-    script = CodeImportDispatcherScript("codeimportdispatcher")
-    script.lock_and_run()
-
diff --git a/lib/lp/codehosting/__init__.py b/lib/lp/codehosting/__init__.py
index f41a997..0731704 100644
--- a/lib/lp/codehosting/__init__.py
+++ b/lib/lp/codehosting/__init__.py
@@ -3,8 +3,8 @@
 
 """Launchpad code-hosting system.
 
-NOTE: Importing this package will load any system Bazaar plugins, as well as
-all plugins in the bzrplugins/ directory underneath the rocketfuel checkout.
+NOTE: Importing this package will load any system Breezy plugins, as well as
+all plugins in the brzplugins/ directory underneath the rocketfuel checkout.
 """
 
 from __future__ import absolute_import, print_function
@@ -12,7 +12,6 @@ from __future__ import absolute_import, print_function
 __metaclass__ = type
 __all__ = [
     'get_brz_path',
-    'get_BZR_PLUGIN_PATH_for_subprocess',
     ]
 
 
@@ -25,39 +24,16 @@ from breezy.library_state import BzrLibraryState as BrzLibraryState
 from breezy.plugin import load_plugins as brz_load_plugins
 # This import is needed so that brz's logger gets registered.
 import breezy.trace
-import six
 from zope.security import checker
 
 from lp.services.config import config
 
-if six.PY2:
-    from bzrlib.plugin import load_plugins as bzr_load_plugins
-    # This import is needed so that bzr's logger gets registered.
-    import bzrlib.trace
-
 
 def get_brz_path():
     """Find the path to the copy of Breezy for this rocketfuel instance"""
     return os.path.join(config.root, 'bin', 'brz')
 
 
-def _get_bzr_plugins_path():
-    """Find the path to the Bazaar plugins for this rocketfuel instance."""
-    return os.path.join(config.root, 'bzrplugins')
-
-
-def get_BZR_PLUGIN_PATH_for_subprocess():
-    """Calculate the appropriate value for the BZR_PLUGIN_PATH environment.
-
-    The '-site' token tells bzrlib not to include the 'site specific plugins
-    directory' (which is usually something like
-    /usr/lib/pythonX.Y/dist-packages/bzrlib/plugins/) in the plugin search
-    path, which would be inappropriate for Launchpad, which may be using a bzr
-    egg of an incompatible version.
-    """
-    return ":".join((_get_bzr_plugins_path(), "-site"))
-
-
 def _get_brz_plugins_path():
     """Find the path to the Breezy plugins for this rocketfuel instance."""
     return os.path.join(config.root, 'brzplugins')
@@ -83,9 +59,6 @@ if breezy._global_state is None:
     brz_state._start()
 
 
-# XXX cjwatson 2019-06-13: Remove BZR_PLUGIN_PATH and supporting code once
-# all of Launchpad has been ported to Breezy.
-os.environ['BZR_PLUGIN_PATH'] = get_BZR_PLUGIN_PATH_for_subprocess()
 os.environ['BRZ_PLUGIN_PATH'] = get_BRZ_PLUGIN_PATH_for_subprocess()
 
 # Disable some Breezy plugins that are likely to cause trouble if used on
@@ -100,8 +73,6 @@ os.environ['BRZ_DISABLE_PLUGINS'] = ':'.join([
 
 # We want to have full access to Launchpad's Breezy plugins throughout the
 # codehosting package.
-if six.PY2:
-    bzr_load_plugins([_get_bzr_plugins_path()])
 brz_load_plugins()
 
 
diff --git a/lib/lp/codehosting/codeimport/__init__.py b/lib/lp/codehosting/codeimport/__init__.py
deleted file mode 100644
index 094e67f..0000000
--- a/lib/lp/codehosting/codeimport/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-# Make this directory a Python package
diff --git a/lib/lp/codehosting/codeimport/dispatcher.py b/lib/lp/codehosting/codeimport/dispatcher.py
deleted file mode 100644
index 2ab170f..0000000
--- a/lib/lp/codehosting/codeimport/dispatcher.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""The code import dispatcher.
-
-The code import dispatcher is responsible for checking if any code
-imports need to be processed and launching child processes to handle
-them.
-"""
-
-from __future__ import absolute_import, print_function
-
-__metaclass__ = type
-__all__ = [
-    'CodeImportDispatcher',
-    ]
-
-import os
-import socket
-import subprocess
-import time
-
-from lp.services.config import config
-
-
-class CodeImportDispatcher:
-    """A CodeImportDispatcher kicks off the processing of a job if needed.
-
-    The entry point is `findAndDispatchJob`.
-
-    :ivar txn: A transaction manager.
-    :ivar logger: A `Logger` object.
-    """
-
-    worker_script = os.path.join(
-        config.root, 'scripts', 'code-import-worker-monitor.py')
-
-    def __init__(self, logger, worker_limit, _sleep=time.sleep):
-        """Initialize an instance.
-
-        :param logger: A `Logger` object.
-        """
-        self.logger = logger
-        self.worker_limit = worker_limit
-        self._sleep = _sleep
-
-    def getHostname(self):
-        """Return the hostname of this machine.
-
-        This usually calls `socket.gethostname` but it can be
-        overridden by the config for tests and developer machines.
-        """
-        if config.codeimportdispatcher.forced_hostname:
-            return config.codeimportdispatcher.forced_hostname
-        else:
-            return socket.gethostname()
-
-    def dispatchJob(self, job_id):
-        """Start the processing of job `job_id`."""
-        # Just launch the process and forget about it.
-        log_file = os.path.join(
-            config.codeimportdispatcher.worker_log_dir,
-            'code-import-worker-%d.log' % (job_id,))
-        # Return the Popen object to make testing easier.
-        interpreter = "%s/bin/py" % config.root
-        return subprocess.Popen(
-            [interpreter, self.worker_script, str(job_id), '-vv',
-             '--log-file', log_file])
-
-    def findAndDispatchJob(self, scheduler_client):
-        """Check for and dispatch a job if necessary.
-
-        :return: A boolean, true if a job was found and dispatched.
-        """
-        job_id = scheduler_client.getJobForMachine(
-            self.getHostname(), self.worker_limit)
-
-        if job_id == 0:
-            self.logger.info("No jobs pending.")
-            return False
-
-        self.logger.info("Dispatching job %d." % job_id)
-
-        self.dispatchJob(job_id)
-        return True
-
-    def _getSleepInterval(self):
-        """How long to sleep for until asking for a new job.
-
-        The basic idea is to wait longer if the machine is more heavily
-        loaded, so that less loaded slaves get a chance to grab some jobs.
-
-        We assume worker_limit will be roughly the number of CPUs in the
-        machine, so load/worker_limit is roughly how loaded the machine is.
-        """
-        return 5 * os.getloadavg()[0] / self.worker_limit
-
-    def findAndDispatchJobs(self, scheduler_client):
-        """Call findAndDispatchJob until no job is found."""
-        while True:
-            found = self.findAndDispatchJob(scheduler_client)
-            if not found:
-                break
-            self._sleep(self._getSleepInterval())
diff --git a/lib/lp/codehosting/codeimport/foreigntree.py b/lib/lp/codehosting/codeimport/foreigntree.py
deleted file mode 100644
index e0fbe43..0000000
--- a/lib/lp/codehosting/codeimport/foreigntree.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Support for CVS branches."""
-
-from __future__ import absolute_import, print_function
-
-__metaclass__ = type
-__all__ = ['CVSWorkingTree']
-
-import os
-
-import CVS
-import six
-
-
-class CVSWorkingTree:
-    """Represents a CVS working tree."""
-
-    def __init__(self, cvs_root, cvs_module, local_path):
-        """Construct a CVSWorkingTree.
-
-        :param cvs_root: The root of the CVS repository.
-        :param cvs_module: The module in the CVS repository.
-        :param local_path: The local path to check the working tree out to.
-        """
-        self.root = six.ensure_str(cvs_root)
-        self.module = six.ensure_str(cvs_module)
-        self.local_path = os.path.abspath(local_path)
-
-    def checkout(self):
-        repository = CVS.Repository(self.root, None)
-        repository.get(self.module, self.local_path)
-
-    def commit(self):
-        tree = CVS.tree(self.local_path)
-        tree.commit(log='Log message')
-
-    def update(self):
-        tree = CVS.tree(self.local_path)
-        tree.update()
diff --git a/lib/lp/codehosting/codeimport/tarball.py b/lib/lp/codehosting/codeimport/tarball.py
deleted file mode 100644
index edabfbb..0000000
--- a/lib/lp/codehosting/codeimport/tarball.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# Copyright 2009-2019 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Create and extract tarballs."""
-
-from __future__ import absolute_import, print_function
-
-__metaclass__ = type
-__all__ = ['create_tarball', 'extract_tarball', 'TarError']
-
-
-import os
-import subprocess
-
-
-class TarError(Exception):
-    """Raised when the `tar` command has failed for some reason."""
-
-    _format = 'tar exited with status %(status)d'
-
-    def __init__(self, status):
-        Exception.__init__(self, self._format % {'status': status})
-
-
-class NotADirectory(Exception):
-
-    _format = '%(path)s is not a directory.'
-
-    def __init__(self, path):
-        Exception.__init__(self, self._format % {'path': path})
-
-
-def _check_tar_retcode(retcode):
-    if retcode != 0:
-        raise TarError(retcode)
-
-
-def create_tarball(directory, tarball_name, filenames=None):
-    """Create a tarball of `directory` called `tarball_name`.
-
-    This creates a tarball of `directory` from its parent directory. This
-    means that when untarred, it will create a new directory with the same
-    name as `directory`. If `filenames` is not None, then the tarball will
-    be limited to that list of directory entries under `directory`.
-
-    Basically, this is the standard way of making tarballs.
-    """
-    if not os.path.isdir(directory):
-        raise NotADirectory(directory)
-    if filenames is None:
-        filenames = ['.']
-    retcode = subprocess.call(
-        ['tar', '-C', directory, '-czf', tarball_name] + filenames)
-    _check_tar_retcode(retcode)
-
-
-def extract_tarball(tarball_name, directory):
-    """Extract contents of a tarball.
-
-    Changes to `directory` and extracts the tarball at `tarball_name`.
-    """
-    if not os.path.isdir(directory):
-        raise NotADirectory(directory)
-    retcode = subprocess.call(['tar', 'xzf', tarball_name, '-C', directory])
-    _check_tar_retcode(retcode)
diff --git a/lib/lp/codehosting/codeimport/tests/__init__.py b/lib/lp/codehosting/codeimport/tests/__init__.py
deleted file mode 100644
index 094e67f..0000000
--- a/lib/lp/codehosting/codeimport/tests/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-# Make this directory a Python package
diff --git a/lib/lp/codehosting/codeimport/tests/servers.py b/lib/lp/codehosting/codeimport/tests/servers.py
deleted file mode 100644
index e3c3ae2..0000000
--- a/lib/lp/codehosting/codeimport/tests/servers.py
+++ /dev/null
@@ -1,427 +0,0 @@
-# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Server classes that know how to create various kinds of foreign archive."""
-
-from __future__ import absolute_import, print_function
-
-__all__ = [
-    'BzrServer',
-    'CVSServer',
-    'GitServer',
-    'SubversionServer',
-    ]
-
-__metaclass__ = type
-
-import errno
-import io
-import os
-import re
-import shutil
-import signal
-import stat
-import subprocess
-import tempfile
-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,
-    TestServer,
-    )
-from bzrlib.tests.treeshape import build_tree_contents
-from bzrlib.transport import Server
-from bzrlib.urlutils import (
-    escape,
-    join as urljoin,
-    )
-import CVS
-from dulwich.errors import NotGitRepository
-import dulwich.index
-from dulwich.objects import Blob
-from dulwich.repo import Repo as GitRepo
-from dulwich.server import (
-    Backend,
-    Handler,
-    )
-from dulwich.web import (
-    GunzipFilter,
-    handle_service_request,
-    HTTPGitApplication,
-    LimitedInputFilter,
-    WSGIRequestHandlerLogger,
-    WSGIServerLogger,
-    )
-import six
-from subvertpy import SubversionException
-import subvertpy.ra
-import subvertpy.repos
-
-from lp.services.log.logger import BufferLogger
-
-
-def local_path_to_url(local_path):
-    """Return a file:// URL to `local_path`.
-
-    This implementation is unusual in that it returns a file://localhost/ URL.
-    This is to work around the valid_vcs_details constraint on CodeImport.
-    """
-    return u'file://localhost' + escape(
-        os.path.normpath(os.path.abspath(local_path)))
-
-
-def run_in_temporary_directory(function):
-    """Decorate `function` to be run in a temporary directory.
-
-    Creates a new temporary directory and changes to it for the duration of
-    `function`.
-    """
-
-    def decorated(*args, **kwargs):
-        old_cwd = os.getcwd()
-        new_dir = tempfile.mkdtemp()
-        os.chdir(new_dir)
-        try:
-            return function(*args, **kwargs)
-        finally:
-            os.chdir(old_cwd)
-            shutil.rmtree(new_dir)
-
-    decorated.__name__ = function.__name__
-    decorated.__doc__ = function.__doc__
-    return decorated
-
-
-class SubversionServer(Server):
-    """A controller for an Subversion repository, used for testing."""
-
-    def __init__(self, repository_path, use_svn_serve=False):
-        super(SubversionServer, self).__init__()
-        self.repository_path = os.path.abspath(repository_path)
-        self._use_svn_serve = use_svn_serve
-
-    def _get_ra(self, url):
-        return subvertpy.ra.RemoteAccess(url,
-            auth=subvertpy.ra.Auth([subvertpy.ra.get_username_provider()]))
-
-    def createRepository(self, path):
-        """Create a Subversion repository at `path`."""
-        subvertpy.repos.create(path)
-
-    def get_url(self):
-        """Return a URL to the Subversion repository."""
-        if self._use_svn_serve:
-            return u'svn://localhost/'
-        else:
-            return local_path_to_url(self.repository_path)
-
-    def start_server(self):
-        super(SubversionServer, self).start_server()
-        self.createRepository(self.repository_path)
-        if self._use_svn_serve:
-            conf_path = os.path.join(
-                self.repository_path, 'conf/svnserve.conf')
-            with open(conf_path, 'w') as conf_file:
-                conf_file.write('[general]\nanon-access = write\n')
-            self._svnserve = subprocess.Popen(
-                ['svnserve', '--daemon', '--foreground', '--threads',
-                 '--root', self.repository_path])
-            delay = 0.1
-            for i in range(10):
-                try:
-                    try:
-                        self._get_ra(self.get_url())
-                    except OSError as e:
-                        # Subversion < 1.9 just produces OSError.
-                        if e.errno == errno.ECONNREFUSED:
-                            time.sleep(delay)
-                            delay *= 1.5
-                            continue
-                        raise
-                    except SubversionException as e:
-                        # Subversion >= 1.9 turns the raw error into a
-                        # SubversionException.  The code is
-                        # SVN_ERR_RA_CANNOT_CREATE_SESSION, which is not yet
-                        # in subvertpy.
-                        if e.args[1] == 170013:
-                            time.sleep(delay)
-                            delay *= 1.5
-                            continue
-                        raise
-                    else:
-                        break
-                except Exception as e:
-                    self._kill_svnserve()
-                    raise
-            else:
-                self._kill_svnserve()
-                raise AssertionError(
-                    "svnserve didn't start accepting connections")
-
-    def _kill_svnserve(self):
-        os.kill(self._svnserve.pid, signal.SIGINT)
-        self._svnserve.communicate()
-
-    def stop_server(self):
-        super(SubversionServer, self).stop_server()
-        if self._use_svn_serve:
-            self._kill_svnserve()
-
-    def makeBranch(self, branch_name, tree_contents):
-        """Create a branch on the Subversion server called `branch_name`.
-
-        :param branch_name: The name of the branch to create.
-        :param tree_contents: The contents of the module. This is a list of
-            tuples of (relative filename, file contents).
-        """
-        branch_url = self.makeDirectory(branch_name)
-        ra = self._get_ra(branch_url)
-        editor = ra.get_commit_editor({"svn:log": "Import"})
-        root = editor.open_root()
-        for filename, content in tree_contents:
-            f = root.add_file(filename)
-            try:
-                subvertpy.delta.send_stream(
-                    io.BytesIO(content), f.apply_textdelta())
-            finally:
-                f.close()
-        root.close()
-        editor.close()
-        return branch_url
-
-    def makeDirectory(self, directory_name, commit_message=None):
-        """Make a directory on the repository."""
-        if commit_message is None:
-            commit_message = 'Make %r' % (directory_name,)
-        ra = self._get_ra(self.get_url())
-        editor = ra.get_commit_editor({"svn:log": commit_message})
-        root = editor.open_root()
-        root.add_directory(directory_name).close()
-        root.close()
-        editor.close()
-        return urljoin(self.get_url(), directory_name)
-
-
-class CVSServer(Server):
-    """A CVS server for testing."""
-
-    def __init__(self, repository_path):
-        """Construct a `CVSServer`.
-
-        :param repository_path: The path to the directory that will contain
-            the CVS repository.
-        """
-        super(CVSServer, self).__init__()
-        self._repository_path = os.path.abspath(repository_path)
-
-    def createRepository(self, path):
-        """Create a CVS repository at `path`.
-
-        :param path: The local path to create a repository in.
-        :return: A CVS.Repository`.
-        """
-        return CVS.init(path, BufferLogger())
-
-    def getRoot(self):
-        """Return the CVS root for this server."""
-        return six.ensure_text(self._repository_path)
-
-    @run_in_temporary_directory
-    def makeModule(self, module_name, tree_contents):
-        """Create a module on the CVS server called `module_name`.
-
-        A 'module' in CVS roughly corresponds to a project.
-
-        :param module_name: The name of the module to create.
-        :param tree_contents: The contents of the module. This is a list of
-            tuples of (relative filename, file contents).
-        """
-        build_tree_contents(tree_contents)
-        self._repository.Import(
-            module=module_name, log="import", vendor="vendor",
-            release=['release'], dir='.')
-
-    def start_server(self):
-        # Initialize the repository.
-        super(CVSServer, self).start_server()
-        self._repository = self.createRepository(self._repository_path)
-
-
-class GitStoreBackend(Backend):
-    """A backend that looks up repositories under a store directory."""
-
-    def __init__(self, root):
-        self.root = root
-
-    def open_repository(self, path):
-        full_path = os.path.normpath(os.path.join(self.root, path.lstrip("/")))
-        if not full_path.startswith(self.root + "/"):
-            raise NotGitRepository("Repository %s not under store" % path)
-        return GitRepo(full_path)
-
-
-class TurnipSetSymbolicRefHandler(Handler):
-    """Dulwich protocol handler for setting a symbolic ref.
-
-    Transcribed from turnip.pack.git.PackBackendProtocol.
-    """
-
-    def __init__(self, backend, args, proto, http_req=None):
-        super(TurnipSetSymbolicRefHandler, self).__init__(
-            backend, proto, http_req=http_req)
-        self.repo = backend.open_repository(args[0])
-
-    def handle(self):
-        line = self.proto.read_pkt_line()
-        if line is None:
-            self.proto.write_pkt_line(b"ERR Invalid set-symbolic-ref-line\n")
-            return
-        name, target = line.split(b" ", 1)
-        if name != b"HEAD":
-            self.proto.write_pkt_line(
-                b'ERR Symbolic ref name must be "HEAD"\n')
-            return
-        if target.startswith(b"-"):
-            self.proto.write_pkt_line(
-                b'ERR Symbolic ref target may not start with "-"\n')
-            return
-        try:
-            self.repo.refs.set_symbolic_ref(name, target)
-        except Exception as e:
-            self.proto.write_pkt_line(b'ERR %s\n' % e)
-        else:
-            self.proto.write_pkt_line(b'ACK %s\n' % name)
-
-
-class HTTPGitServerThread(threading.Thread):
-    """Thread that runs an HTTP Git server."""
-
-    def __init__(self, backend, address, port=None):
-        super(HTTPGitServerThread, self).__init__()
-        self.name = "HTTP Git server on %s:%s" % (address, port)
-        app = HTTPGitApplication(
-            backend,
-            handlers={'turnip-set-symbolic-ref': TurnipSetSymbolicRefHandler})
-        app.services[('POST', re.compile('/turnip-set-symbolic-ref$'))] = (
-            handle_service_request)
-        app = GunzipFilter(LimitedInputFilter(app))
-        self.server = make_server(
-            address, port, app, handler_class=WSGIRequestHandlerLogger,
-            server_class=WSGIServerLogger)
-
-    def run(self):
-        self.server.serve_forever()
-
-    def get_address(self):
-        return self.server.server_address
-
-    def stop(self):
-        self.server.shutdown()
-
-
-class GitServer(Server):
-
-    def __init__(self, repository_store, use_server=False):
-        super(GitServer, self).__init__()
-        self.repository_store = repository_store
-        self._use_server = use_server
-
-    def get_url(self, repository_name):
-        """Return a URL to the Git repository."""
-        if self._use_server:
-            host, port = self._server.get_address()
-            return u'http://%s:%d/%s' % (host, port, repository_name)
-        else:
-            return local_path_to_url(
-                os.path.join(self.repository_store, repository_name))
-
-    def createRepository(self, path, bare=False):
-        if bare:
-            GitRepo.init_bare(path)
-        else:
-            GitRepo.init(path)
-
-    def start_server(self):
-        super(GitServer, self).start_server()
-        if self._use_server:
-            self._server = HTTPGitServerThread(
-                GitStoreBackend(self.repository_store), "localhost", 0)
-            self._server.start()
-
-    def stop_server(self):
-        super(GitServer, self).stop_server()
-        if self._use_server:
-            self._server.stop()
-
-    def makeRepo(self, repository_name, tree_contents):
-        repository_path = os.path.join(self.repository_store, repository_name)
-        os.makedirs(repository_path)
-        self.createRepository(repository_path, bare=self._use_server)
-        repo = GitRepo(repository_path)
-        blobs = [
-            (Blob.from_string(contents), filename) 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)
-
-
-class BzrServer(Server):
-
-    def __init__(self, repository_path, use_server=False):
-        super(BzrServer, self).__init__()
-        self.repository_path = repository_path
-        self._use_server = use_server
-
-    def createRepository(self, path):
-        BzrDir.create_branch_convenience(path)
-
-    def makeRepo(self, tree_contents):
-        branch = Branch.open(self.repository_path)
-        branch.get_config().set_user_option("create_signatures", "never")
-        builder = BranchBuilder(branch=branch)
-        actions = [('add', ('', 'tree-root', 'directory', None))]
-        actions += [
-            ('add', (path, path + '-id', 'file', content))
-            for (path, content) in tree_contents]
-        builder.build_snapshot(
-            None, None, actions, committer='Joe Foo <joe@xxxxxxx>',
-                message=u'<The commit message>')
-
-    def get_url(self):
-        if self._use_server:
-            return six.ensure_text(self._bzrserver.get_url())
-        else:
-            return local_path_to_url(self.repository_path)
-
-    def start_server(self):
-        super(BzrServer, self).start_server()
-        self.createRepository(self.repository_path)
-
-        class LocalURLServer(TestServer):
-            def __init__(self, repository_path):
-                self.repository_path = repository_path
-
-            def start_server(self):
-                pass
-
-            def get_url(self):
-                return local_path_to_url(self.repository_path)
-
-        if self._use_server:
-            self._bzrserver = ReadonlySmartTCPServer_for_testing()
-            self._bzrserver.start_server(
-                LocalURLServer(self.repository_path))
-
-    def stop_server(self):
-        super(BzrServer, self).stop_server()
-        if self._use_server:
-            self._bzrserver.stop_server()
diff --git a/lib/lp/codehosting/codeimport/tests/test_dispatcher.py b/lib/lp/codehosting/codeimport/tests/test_dispatcher.py
deleted file mode 100644
index a6dc82e..0000000
--- a/lib/lp/codehosting/codeimport/tests/test_dispatcher.py
+++ /dev/null
@@ -1,172 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Tests for the code import dispatcher."""
-
-from __future__ import absolute_import, print_function
-
-__metaclass__ = type
-
-
-from optparse import OptionParser
-import os
-import shutil
-import socket
-import tempfile
-
-from lp.codehosting.codeimport.dispatcher import CodeImportDispatcher
-from lp.services import scripts
-from lp.services.log.logger import BufferLogger
-from lp.testing import TestCase
-from lp.testing.layers import BaseLayer
-
-
-class StubSchedulerClient:
-    """A scheduler client that returns a pre-arranged answer."""
-
-    def __init__(self, ids_to_return):
-        self.ids_to_return = ids_to_return
-
-    def getJobForMachine(self, machine, limit):
-        return self.ids_to_return.pop(0)
-
-
-class MockSchedulerClient:
-    """A scheduler client that records calls to `getJobForMachine`."""
-
-    def __init__(self):
-        self.calls = []
-
-    def getJobForMachine(self, machine, limit):
-        self.calls.append((machine, limit))
-        return 0
-
-
-class TestCodeImportDispatcherUnit(TestCase):
-    """Unit tests for `CodeImportDispatcher`."""
-
-    layer = BaseLayer
-
-    def setUp(self):
-        TestCase.setUp(self)
-        self.pushConfig('codeimportdispatcher', forced_hostname='none')
-
-    def makeDispatcher(self, worker_limit=10, _sleep=lambda delay: None):
-        """Make a `CodeImportDispatcher`."""
-        return CodeImportDispatcher(
-            BufferLogger(), worker_limit, _sleep=_sleep)
-
-    def test_getHostname(self):
-        # By default, getHostname return the same as socket.gethostname()
-        dispatcher = self.makeDispatcher()
-        self.assertEqual(socket.gethostname(), dispatcher.getHostname())
-
-    def test_getHostnameOverride(self):
-        # getHostname can be overridden by the config for testing, however.
-        dispatcher = self.makeDispatcher()
-        self.pushConfig('codeimportdispatcher', forced_hostname='test-value')
-        self.assertEqual('test-value', dispatcher.getHostname())
-
-    def writePythonScript(self, script_path, script_body):
-        """Write out an executable Python script.
-
-        This method writes a script header and `script_body` (which should be
-        a list of lines of Python source) to `script_path` and makes the file
-        executable.
-        """
-        script = open(script_path, 'w')
-        for script_line in script_body:
-            script.write(script_line + '\n')
-
-    def filterOutLoggingOptions(self, arglist):
-        """Remove the standard logging options from a list of arguments."""
-
-        # Calling parser.parse_args as we do below is dangerous,
-        # as if a callback invokes parser.error the test suite
-        # terminates. This hack removes the dangerous argument manually.
-        arglist = [
-            arg for arg in arglist if not arg.startswith('--log-file=')]
-        while '--log-file' in arglist:
-            index = arglist.index('--log-file')
-            del arglist[index]  # Delete the argument
-            del arglist[index]  # And its parameter
-
-        parser = OptionParser()
-        scripts.logger_options(parser)
-        options, args = parser.parse_args(arglist)
-        return args
-
-    def test_dispatchJob(self):
-        # dispatchJob launches a process described by its
-        # worker_script attribute with a given job id as an argument.
-
-        # We create a script that writes its command line arguments to
-        # some a temporary file and examine that.
-        dispatcher = self.makeDispatcher()
-        tmpdir = tempfile.mkdtemp()
-        self.addCleanup(shutil.rmtree, tmpdir)
-        script_path = os.path.join(tmpdir, 'script.py')
-        output_path = os.path.join(tmpdir, 'output.txt')
-        self.writePythonScript(
-            script_path,
-            ['import sys',
-             'open(%r, "w").write(str(sys.argv[1:]))' % output_path])
-        dispatcher.worker_script = script_path
-        proc = dispatcher.dispatchJob(10)
-        proc.wait()
-        with open(output_path) as f:
-            arglist = self.filterOutLoggingOptions(eval(f.read()))
-        self.assertEqual(['10'], arglist)
-
-    def test_findAndDispatchJob_jobWaiting(self):
-        # If there is a job to dispatch, then we call dispatchJob with its id
-        # and the worker_limit supplied to the dispatcher.
-        calls = []
-        dispatcher = self.makeDispatcher()
-        dispatcher.dispatchJob = lambda job_id: calls.append(job_id)
-        found = dispatcher.findAndDispatchJob(StubSchedulerClient([10]))
-        self.assertEqual(([10], True), (calls, found))
-
-    def test_findAndDispatchJob_noJobWaiting(self):
-        # If there is no job to dispatch, then we just exit quietly.
-        calls = []
-        dispatcher = self.makeDispatcher()
-        dispatcher.dispatchJob = lambda job_id: calls.append(job_id)
-        found = dispatcher.findAndDispatchJob(StubSchedulerClient([0]))
-        self.assertEqual(([], False), (calls, found))
-
-    def test_findAndDispatchJob_calls_getJobForMachine_with_limit(self):
-        # findAndDispatchJob calls getJobForMachine on the scheduler client
-        # with the hostname and supplied worker limit.
-        worker_limit = self.factory.getUniqueInteger()
-        dispatcher = self.makeDispatcher(worker_limit)
-        scheduler_client = MockSchedulerClient()
-        dispatcher.findAndDispatchJob(scheduler_client)
-        self.assertEqual(
-            [(dispatcher.getHostname(), worker_limit)],
-            scheduler_client.calls)
-
-    def test_findAndDispatchJobs(self):
-        # findAndDispatchJobs calls getJobForMachine on the scheduler_client,
-        # dispatching jobs, until it indicates that there are no more jobs to
-        # dispatch.
-        calls = []
-        dispatcher = self.makeDispatcher()
-        dispatcher.dispatchJob = lambda job_id: calls.append(job_id)
-        dispatcher.findAndDispatchJobs(StubSchedulerClient([10, 9, 0]))
-        self.assertEqual([10, 9], calls)
-
-    def test_findAndDispatchJobs_sleeps(self):
-        # After finding a job, findAndDispatchJobs sleeps for an interval as
-        # returned by _getSleepInterval.
-        sleep_calls = []
-        interval = self.factory.getUniqueInteger()
-
-        def _sleep(delay):
-            sleep_calls.append(delay)
-
-        dispatcher = self.makeDispatcher(_sleep=_sleep)
-        dispatcher.dispatchJob = lambda job_id: None
-        dispatcher._getSleepInterval = lambda: interval
-        dispatcher.findAndDispatchJobs(StubSchedulerClient([10, 0]))
-        self.assertEqual([interval], sleep_calls)
diff --git a/lib/lp/codehosting/codeimport/tests/test_foreigntree.py b/lib/lp/codehosting/codeimport/tests/test_foreigntree.py
deleted file mode 100644
index 161f83b..0000000
--- a/lib/lp/codehosting/codeimport/tests/test_foreigntree.py
+++ /dev/null
@@ -1,117 +0,0 @@
-# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Tests for foreign branch support."""
-
-from __future__ import absolute_import, print_function
-
-__metaclass__ = type
-
-import os
-import time
-
-from bzrlib.tests import TestCaseWithTransport
-import CVS
-
-from lp.codehosting.codeimport.foreigntree import CVSWorkingTree
-from lp.codehosting.codeimport.tests.servers import CVSServer
-from lp.testing.layers import BaseLayer
-
-
-class TestCVSWorkingTree(TestCaseWithTransport):
-
-    layer = BaseLayer
-
-    def assertHasCheckout(self, cvs_working_tree):
-        """Assert that `cvs_working_tree` has a checkout of its CVS module."""
-        tree = CVS.tree(os.path.abspath(cvs_working_tree.local_path))
-        repository = tree.repository()
-        self.assertEqual(repository.root, cvs_working_tree.root)
-        self.assertEqual(tree.module().name(), cvs_working_tree.module)
-
-    def makeCVSWorkingTree(self, local_path):
-        """Make a CVS working tree for testing."""
-        return CVSWorkingTree(
-            self.cvs_server.getRoot(), self.module_name, local_path)
-
-    def setUp(self):
-        super(TestCVSWorkingTree, self).setUp()
-        self.cvs_server = CVSServer('repository_path')
-        self.cvs_server.start_server()
-        self.module_name = 'test_module'
-        self.cvs_server.makeModule(
-            self.module_name, [('README', 'Random content\n')])
-        self.addCleanup(self.cvs_server.stop_server)
-
-    def test_path(self):
-        # The local path is passed to the constructor and available as
-        # 'local_path'.
-        tree = CVSWorkingTree('root', 'module', 'path')
-        self.assertEqual(tree.local_path, os.path.abspath('path'))
-
-    def test_module(self):
-        # The module is passed to the constructor and available as 'module'.
-        tree = CVSWorkingTree('root', 'module', 'path')
-        self.assertEqual(tree.module, 'module')
-
-    def test_root(self):
-        # The root is passed to the constructor and available as 'root'.
-        tree = CVSWorkingTree('root', 'module', 'path')
-        self.assertEqual(tree.root, 'root')
-
-    def test_checkout(self):
-        # checkout() checks out an up-to-date working tree.
-        tree = self.makeCVSWorkingTree('working_tree')
-        tree.checkout()
-        self.assertHasCheckout(tree)
-
-    def test_commit(self):
-        # commit() makes local changes available to other checkouts.
-        tree = self.makeCVSWorkingTree('working_tree')
-        tree.checkout()
-
-        # If you write to a file in the same second as the previous commit,
-        # CVS will not think that it has changed.
-        time.sleep(1)
-
-        # Make a change.
-        new_content = 'Comfort ye\n'
-        readme = open(os.path.join(tree.local_path, 'README'), 'w')
-        readme.write(new_content)
-        readme.close()
-        self.assertFileEqual(new_content, 'working_tree/README')
-
-        # Commit the change.
-        tree.commit()
-
-        tree2 = self.makeCVSWorkingTree('working_tree2')
-        tree2.checkout()
-
-        self.assertFileEqual(new_content, 'working_tree2/README')
-
-    def test_update(self):
-        # update() fetches any changes to the branch from the remote branch.
-        # We test this by checking out the same branch twice, making
-        # modifications in one, then updating the other. If the modifications
-        # appear, then update() works.
-        tree = self.makeCVSWorkingTree('working_tree')
-        tree.checkout()
-
-        tree2 = self.makeCVSWorkingTree('working_tree2')
-        tree2.checkout()
-
-        # If you write to a file in the same second as the previous commit,
-        # CVS will not think that it has changed.
-        time.sleep(1)
-
-        # Make a change.
-        new_content = 'Comfort ye\n'
-        self.build_tree_contents([('working_tree/README', new_content)])
-
-        # Commit the change.
-        tree.commit()
-
-        # Update.
-        tree2.update()
-        readme_path = os.path.join(tree2.local_path, 'README')
-        self.assertFileEqual(new_content, readme_path)
diff --git a/lib/lp/codehosting/codeimport/tests/test_uifactory.py b/lib/lp/codehosting/codeimport/tests/test_uifactory.py
deleted file mode 100644
index 5c93900..0000000
--- a/lib/lp/codehosting/codeimport/tests/test_uifactory.py
+++ /dev/null
@@ -1,162 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Tests for `LoggingUIFactory`."""
-
-from __future__ import absolute_import, print_function
-
-__metaclass__ = type
-
-from lp.codehosting.codeimport.uifactory import LoggingUIFactory
-from lp.services.log.logger import BufferLogger
-from lp.testing import (
-    FakeTime,
-    TestCase,
-    )
-
-
-class TestLoggingUIFactory(TestCase):
-    """Tests for `LoggingUIFactory`."""
-
-    def setUp(self):
-        TestCase.setUp(self)
-        self.fake_time = FakeTime(12345)
-        self.logger = BufferLogger()
-
-    def makeLoggingUIFactory(self):
-        """Make a `LoggingUIFactory` with fake time and contained output."""
-        return LoggingUIFactory(
-            time_source=self.fake_time.now, logger=self.logger)
-
-    def test_first_progress_updates(self):
-        # The first call to progress generates some output.
-        factory = self.makeLoggingUIFactory()
-        bar = factory.nested_progress_bar()
-        bar.update("hi")
-        self.assertEqual('INFO hi\n', self.logger.getLogBuffer())
-
-    def test_second_rapid_progress_doesnt_update(self):
-        # The second of two progress calls that are less than the factory's
-        # interval apart does not generate output.
-        factory = self.makeLoggingUIFactory()
-        bar = factory.nested_progress_bar()
-        bar.update("hi")
-        self.fake_time.advance(factory.interval / 2)
-        bar.update("there")
-        self.assertEqual('INFO hi\n', self.logger.getLogBuffer())
-
-    def test_second_slow_progress_updates(self):
-        # The second of two progress calls that are more than the factory's
-        # interval apart does generate output.
-        factory = self.makeLoggingUIFactory()
-        bar = factory.nested_progress_bar()
-        bar.update("hi")
-        self.fake_time.advance(factory.interval * 2)
-        bar.update("there")
-        self.assertEqual(
-            'INFO hi\n'
-            'INFO there\n',
-            self.logger.getLogBuffer())
-
-    def test_first_progress_on_new_bar_updates(self):
-        # The first progress on a new progress task always generates output.
-        factory = self.makeLoggingUIFactory()
-        bar = factory.nested_progress_bar()
-        bar.update("hi")
-        self.fake_time.advance(factory.interval / 2)
-        bar2 = factory.nested_progress_bar()
-        bar2.update("there")
-        self.assertEqual(
-            'INFO hi\nINFO hi:there\n', self.logger.getLogBuffer())
-
-    def test_update_with_count_formats_nicely(self):
-        # When more details are passed to update, they are formatted nicely.
-        factory = self.makeLoggingUIFactory()
-        bar = factory.nested_progress_bar()
-        bar.update("hi", 1, 8)
-        self.assertEqual('INFO hi 1/8\n', self.logger.getLogBuffer())
-
-    def test_report_transport_activity_reports_bytes_since_last_update(self):
-        # If there is no call to _progress_updated for 'interval' seconds, the
-        # next call to report_transport_activity will report however many
-        # bytes have been transferred since the update.
-        factory = self.makeLoggingUIFactory()
-        bar = factory.nested_progress_bar()
-        bar.update("hi", 1, 10)
-        self.fake_time.advance(factory.interval / 2)
-        # The bytes in this call will not be reported:
-        factory.report_transport_activity(None, 1, 'read')
-        self.fake_time.advance(factory.interval)
-        bar.update("hi", 2, 10)
-        self.fake_time.advance(factory.interval / 2)
-        factory.report_transport_activity(None, 10, 'read')
-        self.fake_time.advance(factory.interval)
-        factory.report_transport_activity(None, 100, 'read')
-        self.fake_time.advance(factory.interval * 2)
-        # This call will cause output that does not include the transport
-        # activity info.
-        bar.update("hi", 3, 10)
-        self.assertEqual(
-            'INFO hi 1/10\n'
-            'INFO hi 2/10\n'
-            'INFO 110 bytes transferred | hi 2/10\n'
-            'INFO hi 3/10\n',
-            self.logger.getLogBuffer())
-
-    def test_note(self):
-        factory = self.makeLoggingUIFactory()
-        factory.note("Banja Luka")
-        self.assertEqual('INFO Banja Luka\n', self.logger.getLogBuffer())
-
-    def test_show_error(self):
-        factory = self.makeLoggingUIFactory()
-        factory.show_error("Exploding Peaches")
-        self.assertEqual(
-            "ERROR Exploding Peaches\n", self.logger.getLogBuffer())
-
-    def test_confirm_action(self):
-        factory = self.makeLoggingUIFactory()
-        self.assertTrue(factory.confirm_action(
-            "How are you %(when)s?", "wellness", {"when": "today"}))
-
-    def test_show_message(self):
-        factory = self.makeLoggingUIFactory()
-        factory.show_message("Peaches")
-        self.assertEqual("INFO Peaches\n", self.logger.getLogBuffer())
-
-    def test_get_username(self):
-        factory = self.makeLoggingUIFactory()
-        self.assertIs(
-            None, factory.get_username("Who are you %(when)s?", when="today"))
-
-    def test_get_password(self):
-        factory = self.makeLoggingUIFactory()
-        self.assertIs(
-            None,
-            factory.get_password("How is your %(drink)s", drink="coffee"))
-
-    def test_show_warning(self):
-        factory = self.makeLoggingUIFactory()
-        factory.show_warning("Peaches")
-        self.assertEqual("WARNING Peaches\n", self.logger.getLogBuffer())
-
-    def test_show_warning_unicode(self):
-        factory = self.makeLoggingUIFactory()
-        factory.show_warning(u"Peach\xeas")
-        self.assertEqual(
-            "WARNING Peach\xc3\xaas\n", self.logger.getLogBuffer())
-
-    def test_user_warning(self):
-        factory = self.makeLoggingUIFactory()
-        factory.show_user_warning('cross_format_fetch',
-            from_format="athing", to_format="anotherthing")
-        message = factory._user_warning_templates['cross_format_fetch'] % {
-            "from_format": "athing",
-            "to_format": "anotherthing",
-            }
-        self.assertEqual("WARNING %s\n" % message, self.logger.getLogBuffer())
-
-    def test_clear_term(self):
-        factory = self.makeLoggingUIFactory()
-        factory.clear_term()
-        self.assertEqual("", self.logger.getLogBuffer())
diff --git a/lib/lp/codehosting/codeimport/tests/test_worker.py b/lib/lp/codehosting/codeimport/tests/test_worker.py
deleted file mode 100644
index 50fa83c..0000000
--- a/lib/lp/codehosting/codeimport/tests/test_worker.py
+++ /dev/null
@@ -1,1550 +0,0 @@
-# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Tests for the code import worker."""
-
-from __future__ import absolute_import, print_function
-
-__metaclass__ = type
-
-import logging
-import os
-import shutil
-import subprocess
-import tempfile
-import time
-from uuid import uuid4
-
-from bzrlib import (
-    trace,
-    urlutils,
-    )
-from bzrlib.branch import (
-    Branch,
-    BranchReferenceFormat,
-    )
-from bzrlib.branchbuilder import BranchBuilder
-from bzrlib.bzrdir import (
-    BzrDir,
-    BzrDirFormat,
-    format_registry,
-    )
-from bzrlib.errors import NoSuchFile
-from bzrlib.tests import (
-    http_utils,
-    TestCaseWithTransport,
-    )
-from bzrlib.transport import (
-    get_transport,
-    get_transport_from_url,
-    )
-from bzrlib.url_policy_open import (
-    _BlacklistPolicy,
-    AcceptAnythingPolicy,
-    BadUrl,
-    BranchOpener,
-    BranchOpenPolicy,
-    )
-from bzrlib.urlutils import (
-    join as urljoin,
-    local_path_from_url,
-    )
-from CVS import (
-    Repository,
-    tree as CVSTree,
-    )
-from dulwich.repo import Repo as GitRepo
-from fixtures import FakeLogger
-from pymacaroons import Macaroon
-import scandir
-import six
-import subvertpy
-import subvertpy.client
-import subvertpy.ra
-from testtools.matchers import (
-    ContainsAll,
-    LessThan,
-    )
-
-import lp.codehosting
-from lp.codehosting.codeimport.tarball import (
-    create_tarball,
-    extract_tarball,
-    )
-from lp.codehosting.codeimport.tests.servers import (
-    BzrServer,
-    CVSServer,
-    GitServer,
-    SubversionServer,
-    )
-from lp.codehosting.codeimport.worker import (
-    BazaarBranchStore,
-    BzrImportWorker,
-    BzrSvnImportWorker,
-    CodeImportBranchOpenPolicy,
-    CodeImportSourceDetails,
-    CodeImportWorkerExitCode,
-    CSCVSImportWorker,
-    ForeignTreeStore,
-    get_default_bazaar_branch_store,
-    GitImportWorker,
-    GitToGitImportWorker,
-    ImportDataStore,
-    ToBzrImportWorker,
-    )
-from lp.codehosting.tests.helpers import create_branch_with_one_revision
-from lp.services.config import config
-from lp.services.log.logger import BufferLogger
-from lp.testing import TestCase
-from lp.testing.layers import BaseLayer
-
-
-class ForeignBranchPluginLayer(BaseLayer):
-    """Ensure only specific tests are run with foreign branch plugins loaded.
-    """
-
-    @classmethod
-    def setUp(cls):
-        pass
-
-    @classmethod
-    def tearDown(cls):
-        # Raise NotImplementedError to signal that this layer cannot be torn
-        # down.  This means that the test runner will run subsequent tests in
-        # a different process.
-        raise NotImplementedError
-
-    @classmethod
-    def testSetUp(cls):
-        pass
-
-    @classmethod
-    def testTearDown(cls):
-        pass
-
-
-default_format = BzrDirFormat.get_default_format()
-
-
-class WorkerTest(TestCaseWithTransport, TestCase):
-    """Base test case for things that test the code import worker.
-
-    Provides Bazaar testing features, access to Launchpad objects and
-    factories for some code import objects.
-    """
-
-    layer = ForeignBranchPluginLayer
-
-    def setUp(self):
-        super(WorkerTest, self).setUp()
-        self.disable_directory_isolation()
-        BranchOpener.install_hook()
-
-    def assertDirectoryTreesEqual(self, directory1, directory2):
-        """Assert that `directory1` has the same structure as `directory2`.
-
-        That is, assert that all of the files and directories beneath
-        `directory1` are laid out in the same way as `directory2`.
-        """
-        def list_files(directory):
-            for path, ignored, ignored in scandir.walk(directory):
-                yield path[len(directory):]
-        self.assertEqual(
-            sorted(list_files(directory1)), sorted(list_files(directory2)))
-
-    def makeTemporaryDirectory(self):
-        directory = tempfile.mkdtemp()
-        self.addCleanup(shutil.rmtree, directory)
-        return directory
-
-
-class TestBazaarBranchStore(WorkerTest):
-    """Tests for `BazaarBranchStore`."""
-
-    def setUp(self):
-        WorkerTest.setUp(self)
-        # XXX: JonathanLange 2010-12-24 bug=694140: Avoid spurious "No
-        # handlers for logger 'bzr'" messages.
-        trace._bzr_logger = logging.getLogger('bzr')
-        self.temp_dir = self.makeTemporaryDirectory()
-        self.arbitrary_branch_id = 10
-
-    def makeBranchStore(self):
-        return BazaarBranchStore(self.get_transport())
-
-    def test_defaultStore(self):
-        # The default store is at config.codeimport.bazaar_branch_store.
-        store = get_default_bazaar_branch_store()
-        self.assertEqual(
-            store.transport.base.rstrip('/'),
-            config.codeimport.bazaar_branch_store.rstrip('/'))
-
-    def test__getMirrorURL(self):
-        # _getMirrorURL returns a URL for the branch with the given id.
-        store = BazaarBranchStore(get_transport_from_url(
-            'sftp://storage.example/branches'))
-        self.assertEqual(
-            'sftp://storage.example/branches/000186a0',
-            store._getMirrorURL(100000))
-
-    def test__getMirrorURL_push(self):
-        # _getMirrorURL prefers bzr+ssh over sftp when constructing push
-        # URLs.
-        store = BazaarBranchStore(get_transport_from_url(
-            'sftp://storage.example/branches'))
-        self.assertEqual(
-            'bzr+ssh://storage.example/branches/000186a0',
-            store._getMirrorURL(100000, push=True))
-
-    def test_getNewBranch(self):
-        # If there's no Bazaar branch of this id, then pull creates a new
-        # Bazaar branch.
-        store = self.makeBranchStore()
-        bzr_branch = store.pull(
-            self.arbitrary_branch_id, self.temp_dir, default_format)
-        self.assertEqual(0, bzr_branch.revno())
-
-    def test_getNewBranch_without_tree(self):
-        # If pull() with needs_tree=False creates a new branch, it doesn't
-        # create a working tree.
-        store = self.makeBranchStore()
-        bzr_branch = store.pull(
-            self.arbitrary_branch_id, self.temp_dir, default_format, False)
-        self.assertFalse(bzr_branch.bzrdir.has_workingtree())
-
-    def test_getNewBranch_with_tree(self):
-        # If pull() with needs_tree=True creates a new branch, it creates a
-        # working tree.
-        store = self.makeBranchStore()
-        bzr_branch = store.pull(
-            self.arbitrary_branch_id, self.temp_dir, default_format, True)
-        self.assertTrue(bzr_branch.bzrdir.has_workingtree())
-
-    def test_pushBranchThenPull(self):
-        # After we've pushed up a branch to the store, we can then pull it
-        # from the store.
-        store = self.makeBranchStore()
-        tree = create_branch_with_one_revision('original')
-        store.push(self.arbitrary_branch_id, tree.branch, default_format)
-        new_branch = store.pull(
-            self.arbitrary_branch_id, self.temp_dir, default_format)
-        self.assertEqual(
-            tree.branch.last_revision(), new_branch.last_revision())
-
-    def test_pull_without_needs_tree_doesnt_create_tree(self):
-        # pull with needs_tree=False doesn't spend the time to create a
-        # working tree.
-        store = self.makeBranchStore()
-        tree = create_branch_with_one_revision('original')
-        store.push(self.arbitrary_branch_id, tree.branch, default_format)
-        new_branch = store.pull(
-            self.arbitrary_branch_id, self.temp_dir, default_format, False)
-        self.assertFalse(new_branch.bzrdir.has_workingtree())
-
-    def test_pull_needs_tree_creates_tree(self):
-        # pull with needs_tree=True creates a working tree.
-        store = self.makeBranchStore()
-        tree = create_branch_with_one_revision('original')
-        store.push(self.arbitrary_branch_id, tree.branch, default_format)
-        new_branch = store.pull(
-            self.arbitrary_branch_id, self.temp_dir, default_format, True)
-        self.assertTrue(new_branch.bzrdir.has_workingtree())
-
-    def test_pullUpgradesFormat(self):
-        # A branch should always be in the most up-to-date format before a
-        # pull is performed.
-        store = self.makeBranchStore()
-        target_url = store._getMirrorURL(self.arbitrary_branch_id)
-        knit_format = format_registry.get('knit')()
-        tree = create_branch_with_one_revision(target_url, format=knit_format)
-        self.assertNotEqual(
-            tree.bzrdir._format.repository_format.network_name(),
-            default_format.repository_format.network_name())
-
-        # The fetched branch is in the default format.
-        new_branch = store.pull(
-            self.arbitrary_branch_id, self.temp_dir, default_format)
-        # Make sure backup.bzr is removed, as it interferes with CSCVS.
-        self.assertEqual(os.listdir(self.temp_dir), [".bzr"])
-        self.assertEqual(new_branch.repository._format.network_name(),
-            default_format.repository_format.network_name())
-
-    def test_pushUpgradesFormat(self):
-        # A branch should always be in the most up-to-date format before a
-        # pull is performed.
-        store = self.makeBranchStore()
-        target_url = store._getMirrorURL(self.arbitrary_branch_id)
-        knit_format = format_registry.get('knit')()
-        create_branch_with_one_revision(target_url, format=knit_format)
-
-        # The fetched branch is in the default format.
-        new_branch = store.pull(
-            self.arbitrary_branch_id, self.temp_dir, default_format)
-        self.assertEqual(
-            default_format, new_branch.bzrdir._format)
-
-        # The remote branch is still in the old format at this point.
-        target_branch = Branch.open(target_url)
-        self.assertEqual(
-            knit_format.get_branch_format(),
-            target_branch._format)
-
-        store.push(self.arbitrary_branch_id, new_branch, default_format)
-
-        # The remote branch is now in the new format.
-        target_branch = Branch.open(target_url)
-        # Only .bzr is left behind. The scanner removes branches
-        # in which invalid directories (such as .bzr.retire.
-        # exist). (bug #798560)
-        self.assertEqual(
-            target_branch.user_transport.list_dir("."),
-            [".bzr"])
-        self.assertEqual(
-            default_format.get_branch_format(),
-            target_branch._format)
-        self.assertEqual(
-            target_branch.last_revision_info(),
-            new_branch.last_revision_info())
-
-    def test_pushTwiceThenPull(self):
-        # We can push up a branch to the store twice and then pull it from the
-        # store.
-        store = self.makeBranchStore()
-        tree = create_branch_with_one_revision('original')
-        store.push(self.arbitrary_branch_id, tree.branch, default_format)
-        store.push(self.arbitrary_branch_id, tree.branch, default_format)
-        new_branch = store.pull(
-            self.arbitrary_branch_id, self.temp_dir, default_format)
-        self.assertEqual(
-            tree.branch.last_revision(), new_branch.last_revision())
-
-    def test_push_divergant_branches(self):
-        # push() uses overwrite=True, so divergent branches (rebased) can be
-        # pushed.
-        store = self.makeBranchStore()
-        tree = create_branch_with_one_revision('original')
-        store.push(self.arbitrary_branch_id, tree.branch, default_format)
-        tree = create_branch_with_one_revision('divergant')
-        store.push(self.arbitrary_branch_id, tree.branch, default_format)
-
-    def fetchBranch(self, from_url, target_path):
-        """Pull a branch from `from_url` to `target_path`.
-
-        This uses the Bazaar API for pulling a branch, and is used to test
-        that `push` indeed pushes a branch to a specific location.
-
-        :return: The working tree of the branch.
-        """
-        bzr_dir = BzrDir.open(from_url)
-        bzr_dir.sprout(target_path)
-        return BzrDir.open(target_path).open_workingtree()
-
-    def test_makesDirectories(self):
-        # push() tries to create the base directory of the branch store if it
-        # doesn't already exist.
-        store = BazaarBranchStore(self.get_transport('doesntexist'))
-        tree = create_branch_with_one_revision('original')
-        store.push(self.arbitrary_branch_id, tree.branch, default_format)
-        self.assertIsDirectory('doesntexist', self.get_transport())
-
-    def test_storedLocation(self):
-        # push() puts the branch in a directory named after the branch ID on
-        # the BazaarBranchStore's transport.
-        store = self.makeBranchStore()
-        tree = create_branch_with_one_revision('original')
-        store.push(self.arbitrary_branch_id, tree.branch, default_format)
-        new_branch = self.fetchBranch(
-            urljoin(store.transport.base, '%08x' % self.arbitrary_branch_id),
-            'new_tree')
-        self.assertEqual(
-            tree.branch.last_revision(), new_branch.last_revision())
-
-    def test_sftpPrefix(self):
-        # Since branches are mirrored by importd via sftp, _getMirrorURL must
-        # support sftp urls. There was once a bug that made it incorrect with
-        # sftp.
-        sftp_prefix = 'sftp://example/base/'
-        store = BazaarBranchStore(get_transport(sftp_prefix))
-        self.assertEqual(
-            store._getMirrorURL(self.arbitrary_branch_id),
-            sftp_prefix + '%08x' % self.arbitrary_branch_id)
-
-    def test_sftpPrefixNoSlash(self):
-        # If the prefix has no trailing slash, one should be added. It's very
-        # easy to forget a trailing slash in the importd configuration.
-        sftp_prefix_noslash = 'sftp://example/base'
-        store = BazaarBranchStore(get_transport(sftp_prefix_noslash))
-        self.assertEqual(
-            store._getMirrorURL(self.arbitrary_branch_id),
-            sftp_prefix_noslash + '/' + '%08x' % self.arbitrary_branch_id)
-
-    def test_all_revisions_saved(self):
-        # All revisions in the branch's repo are transferred, not just those
-        # in the ancestry of the tip.
-        # Consider a branch with two heads in its repo:
-        #            revid
-        #           /     \
-        #       revid1   revid2 <- branch tip
-        # A naive push/pull would just store 'revid' and 'revid2' in the
-        # branch store -- we need to make sure all three revisions are stored
-        # and retrieved.
-        builder = self.make_branch_builder('tree')
-        revid = builder.build_snapshot(
-            None, None, [('add', ('', 'root-id', 'directory', ''))])
-        revid1 = builder.build_snapshot(None, [revid], [])
-        revid2 = builder.build_snapshot(None, [revid], [])
-        store = self.makeBranchStore()
-        store.push(
-            self.arbitrary_branch_id, builder.get_branch(), default_format)
-        retrieved_branch = store.pull(
-            self.arbitrary_branch_id, 'pulled', default_format)
-        self.assertEqual(
-            set([revid, revid1, revid2]),
-            set(retrieved_branch.repository.all_revision_ids()))
-
-    def test_pull_doesnt_bring_backup_directories(self):
-        # If the branch has been upgraded in the branch store, `pull` does not
-        # copy the backup.bzr directory to `target_path`, just the .bzr
-        # directory.
-        store = self.makeBranchStore()
-        tree = create_branch_with_one_revision('original')
-        store.push(self.arbitrary_branch_id, tree.branch, default_format)
-        t = get_transport(store._getMirrorURL(self.arbitrary_branch_id))
-        t.mkdir('backup.bzr')
-        retrieved_branch = store.pull(
-            self.arbitrary_branch_id, 'pulled', default_format,
-            needs_tree=False)
-        self.assertEqual(
-            ['.bzr'], retrieved_branch.bzrdir.root_transport.list_dir('.'))
-
-
-class TestImportDataStore(WorkerTest):
-    """Tests for `ImportDataStore`."""
-
-    def test_fetch_returnsFalseIfNotFound(self):
-        # If the requested file does not exist on the transport, fetch returns
-        # False.
-        filename = '%s.tar.gz' % (self.factory.getUniqueString(),)
-        source_details = self.factory.makeCodeImportSourceDetails()
-        store = ImportDataStore(self.get_transport(), source_details)
-        ret = store.fetch(filename)
-        self.assertFalse(ret)
-
-    def test_fetch_doesntCreateFileIfNotFound(self):
-        # If the requested file does not exist on the transport, no local file
-        # is created.
-        filename = '%s.tar.gz' % (self.factory.getUniqueString(),)
-        source_details = self.factory.makeCodeImportSourceDetails()
-        store = ImportDataStore(self.get_transport(), source_details)
-        store.fetch(filename)
-        self.assertFalse(os.path.exists(filename))
-
-    def test_fetch_returnsTrueIfFound(self):
-        # If the requested file exists on the transport, fetch returns True.
-        source_details = self.factory.makeCodeImportSourceDetails()
-        # That the remote name is like this is part of the interface of
-        # ImportDataStore.
-        remote_name = '%08x.tar.gz' % (source_details.target_id,)
-        local_name = '%s.tar.gz' % (self.factory.getUniqueString(),)
-        transport = self.get_transport()
-        transport.put_bytes(remote_name, b'')
-        store = ImportDataStore(transport, source_details)
-        ret = store.fetch(local_name)
-        self.assertTrue(ret)
-
-    def test_fetch_retrievesFileIfFound(self):
-        # If the requested file exists on the transport, fetch copies its
-        # content to the filename given to fetch.
-        source_details = self.factory.makeCodeImportSourceDetails()
-        # That the remote name is like this is part of the interface of
-        # ImportDataStore.
-        remote_name = '%08x.tar.gz' % (source_details.target_id,)
-        content = self.factory.getUniqueString()
-        transport = self.get_transport()
-        transport.put_bytes(remote_name, content)
-        store = ImportDataStore(transport, source_details)
-        local_name = '%s.tar.gz' % (self.factory.getUniqueString('tarball'),)
-        store.fetch(local_name)
-        self.assertEqual(content, open(local_name).read())
-
-    def test_fetch_with_dest_transport(self):
-        # The second, optional, argument to fetch is the transport in which to
-        # place the retrieved file.
-        source_details = self.factory.makeCodeImportSourceDetails()
-        # That the remote name is like this is part of the interface of
-        # ImportDataStore.
-        remote_name = '%08x.tar.gz' % (source_details.target_id,)
-        content = self.factory.getUniqueString()
-        transport = self.get_transport()
-        transport.put_bytes(remote_name, content)
-        store = ImportDataStore(transport, source_details)
-        local_prefix = self.factory.getUniqueString()
-        self.get_transport(local_prefix).ensure_base()
-        local_name = '%s.tar.gz' % (self.factory.getUniqueString(),)
-        store.fetch(local_name, self.get_transport(local_prefix))
-        self.assertEqual(
-            content, open(os.path.join(local_prefix, local_name)).read())
-
-    def test_put_copiesFileToTransport(self):
-        # Put copies the content of the passed filename to the remote
-        # transport.
-        local_name = '%s.tar.gz' % (self.factory.getUniqueString(),)
-        source_details = self.factory.makeCodeImportSourceDetails()
-        content = self.factory.getUniqueString()
-        get_transport('.').put_bytes(local_name, content)
-        transport = self.get_transport()
-        store = ImportDataStore(transport, source_details)
-        store.put(local_name)
-        # That the remote name is like this is part of the interface of
-        # ImportDataStore.
-        remote_name = '%08x.tar.gz' % (source_details.target_id,)
-        self.assertEqual(content, transport.get_bytes(remote_name))
-
-    def test_put_ensures_base(self):
-        # Put ensures that the directory pointed to by the transport exists.
-        local_name = '%s.tar.gz' % (self.factory.getUniqueString(),)
-        subdir_name = self.factory.getUniqueString()
-        source_details = self.factory.makeCodeImportSourceDetails()
-        get_transport('.').put_bytes(local_name, b'')
-        transport = self.get_transport()
-        store = ImportDataStore(transport.clone(subdir_name), source_details)
-        store.put(local_name)
-        self.assertTrue(transport.has(subdir_name))
-
-    def test_put_with_source_transport(self):
-        # The second, optional, argument to put is the transport from which to
-        # read the retrieved file.
-        local_prefix = self.factory.getUniqueString()
-        local_name = '%s.tar.gz' % (self.factory.getUniqueString(),)
-        source_details = self.factory.makeCodeImportSourceDetails()
-        content = self.factory.getUniqueString()
-        os.mkdir(local_prefix)
-        get_transport(local_prefix).put_bytes(local_name, content)
-        transport = self.get_transport()
-        store = ImportDataStore(transport, source_details)
-        store.put(local_name, self.get_transport(local_prefix))
-        # That the remote name is like this is part of the interface of
-        # ImportDataStore.
-        remote_name = '%08x.tar.gz' % (source_details.target_id,)
-        self.assertEqual(content, transport.get_bytes(remote_name))
-
-
-class MockForeignWorkingTree:
-    """Working tree that records calls to checkout and update."""
-
-    def __init__(self, local_path):
-        self.local_path = local_path
-        self.log = []
-
-    def checkout(self):
-        self.log.append('checkout')
-
-    def update(self):
-        self.log.append('update')
-
-
-class TestForeignTreeStore(WorkerTest):
-    """Tests for the `ForeignTreeStore` object."""
-
-    def assertCheckedOut(self, tree):
-        self.assertEqual(['checkout'], tree.log)
-
-    def assertUpdated(self, tree):
-        self.assertEqual(['update'], tree.log)
-
-    def setUp(self):
-        """Set up a code import for an SVN working tree."""
-        super(TestForeignTreeStore, self).setUp()
-        self.temp_dir = self.makeTemporaryDirectory()
-
-    def makeForeignTreeStore(self, source_details=None):
-        """Make a foreign tree store.
-
-        The store is in a different directory to the local working directory.
-        """
-        def _getForeignTree(target_path):
-            return MockForeignWorkingTree(target_path)
-        fake_it = False
-        if source_details is None:
-            fake_it = True
-            source_details = self.factory.makeCodeImportSourceDetails()
-        transport = self.get_transport('remote')
-        store = ForeignTreeStore(ImportDataStore(transport, source_details))
-        if fake_it:
-            store._getForeignTree = _getForeignTree
-        return store
-
-    def test_getForeignTreeCVS(self):
-        # _getForeignTree() returns a CVS working tree for CVS code imports.
-        source_details = self.factory.makeCodeImportSourceDetails(
-            rcstype='cvs')
-        store = self.makeForeignTreeStore(source_details)
-        working_tree = store._getForeignTree('path')
-        self.assertIsSameRealPath(working_tree.local_path, 'path')
-        self.assertEqual(working_tree.root, source_details.cvs_root)
-        self.assertEqual(working_tree.module, source_details.cvs_module)
-
-    def test_getNewWorkingTree(self):
-        # If the foreign tree store doesn't have an archive of the foreign
-        # tree, then fetching the tree actually pulls in from the original
-        # site.
-        store = self.makeForeignTreeStore()
-        tree = store.fetchFromSource(self.temp_dir)
-        self.assertCheckedOut(tree)
-
-    def test_archiveTree(self):
-        # Once we have a foreign working tree, we can archive it so that we
-        # can retrieve it more reliably in the future.
-        store = self.makeForeignTreeStore()
-        foreign_tree = store.fetchFromSource(self.temp_dir)
-        store.archive(foreign_tree)
-        transport = store.import_data_store._transport
-        source_details = store.import_data_store.source_details
-        self.assertTrue(
-            transport.has('%08x.tar.gz' % source_details.target_id),
-            "Couldn't find '%08x.tar.gz'" % source_details.target_id)
-
-    def test_fetchFromArchiveFailure(self):
-        # If a tree has not been archived yet, but we try to retrieve it from
-        # the archive, we get a NoSuchFile error.
-        store = self.makeForeignTreeStore()
-        self.assertRaises(
-            NoSuchFile,
-            store.fetchFromArchive, self.temp_dir)
-
-    def test_fetchFromArchive(self):
-        # After archiving a tree, we can retrieve it from the store -- the
-        # tarball gets downloaded and extracted.
-        store = self.makeForeignTreeStore()
-        foreign_tree = store.fetchFromSource(self.temp_dir)
-        store.archive(foreign_tree)
-        new_temp_dir = self.makeTemporaryDirectory()
-        foreign_tree2 = store.fetchFromArchive(new_temp_dir)
-        self.assertEqual(new_temp_dir, foreign_tree2.local_path)
-        self.assertDirectoryTreesEqual(self.temp_dir, new_temp_dir)
-
-    def test_fetchFromArchiveUpdates(self):
-        # The local working tree is updated with changes from the remote
-        # branch after it has been fetched from the archive.
-        store = self.makeForeignTreeStore()
-        foreign_tree = store.fetchFromSource(self.temp_dir)
-        store.archive(foreign_tree)
-        new_temp_dir = self.makeTemporaryDirectory()
-        foreign_tree2 = store.fetchFromArchive(new_temp_dir)
-        self.assertUpdated(foreign_tree2)
-
-
-class TestWorkerCore(WorkerTest):
-    """Tests for the core (VCS-independent) part of the code import worker."""
-
-    def setUp(self):
-        WorkerTest.setUp(self)
-        self.source_details = self.factory.makeCodeImportSourceDetails()
-
-    def makeBazaarBranchStore(self):
-        """Make a Bazaar branch store."""
-        return BazaarBranchStore(self.get_transport('bazaar_branches'))
-
-    def makeImportWorker(self):
-        """Make an ImportWorker."""
-        return ToBzrImportWorker(
-            self.source_details, self.get_transport('import_data'),
-            self.makeBazaarBranchStore(), logging.getLogger("silent"),
-            AcceptAnythingPolicy())
-
-    def test_construct(self):
-        # When we construct an ImportWorker, it has a CodeImportSourceDetails
-        # object.
-        worker = self.makeImportWorker()
-        self.assertEqual(self.source_details, worker.source_details)
-
-    def test_getBazaarWorkingBranchMakesEmptyBranch(self):
-        # getBazaarBranch returns a brand-new working tree for an initial
-        # import.
-        worker = self.makeImportWorker()
-        bzr_branch = worker.getBazaarBranch()
-        self.assertEqual(0, bzr_branch.revno())
-
-    def test_bazaarBranchLocation(self):
-        # getBazaarBranch makes the working tree under the current working
-        # directory.
-        worker = self.makeImportWorker()
-        bzr_branch = worker.getBazaarBranch()
-        self.assertIsSameRealPath(
-            os.path.abspath(worker.BZR_BRANCH_PATH),
-            os.path.abspath(local_path_from_url(bzr_branch.base)))
-
-
-class TestCSCVSWorker(WorkerTest):
-    """Tests for methods specific to CSCVSImportWorker."""
-
-    def setUp(self):
-        WorkerTest.setUp(self)
-        self.source_details = self.factory.makeCodeImportSourceDetails()
-
-    def makeImportWorker(self):
-        """Make a CSCVSImportWorker."""
-        return CSCVSImportWorker(
-            self.source_details, self.get_transport('import_data'), None,
-            logging.getLogger("silent"), opener_policy=AcceptAnythingPolicy())
-
-    def test_getForeignTree(self):
-        # getForeignTree returns an object that represents the 'foreign'
-        # branch (i.e. a CVS or Subversion branch).
-        worker = self.makeImportWorker()
-
-        def _getForeignTree(target_path):
-            return MockForeignWorkingTree(target_path)
-
-        worker.foreign_tree_store._getForeignTree = _getForeignTree
-        working_tree = worker.getForeignTree()
-        self.assertIsSameRealPath(
-            os.path.abspath(worker.FOREIGN_WORKING_TREE_PATH),
-            working_tree.local_path)
-
-
-class TestGitImportWorker(WorkerTest):
-    """Test for behaviour particular to `GitImportWorker`."""
-
-    def makeBazaarBranchStore(self):
-        """Make a Bazaar branch store."""
-        t = self.get_transport('bazaar_branches')
-        t.ensure_base()
-        return BazaarBranchStore(self.get_transport('bazaar_branches'))
-
-    def makeImportWorker(self):
-        """Make an GitImportWorker."""
-        source_details = self.factory.makeCodeImportSourceDetails()
-        return GitImportWorker(
-            source_details, self.get_transport('import_data'),
-            self.makeBazaarBranchStore(), logging.getLogger("silent"),
-            opener_policy=AcceptAnythingPolicy())
-
-    def test_pushBazaarBranch_saves_git_cache(self):
-        # GitImportWorker.pushBazaarBranch saves a tarball of the git cache
-        # from the tree's repository in the worker's ImportDataStore.
-        content = self.factory.getUniqueString()
-        branch = self.make_branch('.')
-        branch.repository._transport.mkdir('git')
-        branch.repository._transport.put_bytes('git/cache', content)
-        import_worker = self.makeImportWorker()
-        import_worker.pushBazaarBranch(branch)
-        import_worker.import_data_store.fetch('git-cache.tar.gz')
-        extract_tarball('git-cache.tar.gz', '.')
-        self.assertEqual(content, open('cache').read())
-
-    def test_getBazaarBranch_fetches_legacy_git_db(self):
-        # GitImportWorker.getBazaarBranch fetches the legacy git.db file, if
-        # present, from the worker's ImportDataStore into the tree's
-        # repository.
-        import_worker = self.makeImportWorker()
-        # Store the git.db file in the store.
-        content = self.factory.getUniqueString()
-        open('git.db', 'w').write(content)
-        import_worker.import_data_store.put('git.db')
-        # Make sure there's a Bazaar branch in the branch store.
-        branch = self.make_branch('branch')
-        ToBzrImportWorker.pushBazaarBranch(import_worker, branch)
-        # Finally, fetching the tree gets the git.db file too.
-        branch = import_worker.getBazaarBranch()
-        self.assertEqual(
-            content, branch.repository._transport.get('git.db').read())
-
-    def test_getBazaarBranch_fetches_git_cache(self):
-        # GitImportWorker.getBazaarBranch fetches the tarball of the git
-        # cache from the worker's ImportDataStore and expands it into the
-        # tree's repository.
-        import_worker = self.makeImportWorker()
-        # Store a tarred-up cache in the store.x
-        content = self.factory.getUniqueString()
-        os.mkdir('cache')
-        open('cache/git-cache', 'w').write(content)
-        create_tarball('cache', 'git-cache.tar.gz')
-        import_worker.import_data_store.put('git-cache.tar.gz')
-        # Make sure there's a Bazaar branch in the branch store.
-        branch = self.make_branch('branch')
-        ToBzrImportWorker.pushBazaarBranch(import_worker, branch)
-        # Finally, fetching the tree gets the git.db file too.
-        new_branch = import_worker.getBazaarBranch()
-        self.assertEqual(
-            content,
-            new_branch.repository._transport.get('git/git-cache').read())
-
-
-def clean_up_default_stores_for_import(target_id):
-    """Clean up the default branch and foreign tree stores for an import.
-
-    This checks for an existing branch and/or other import data corresponding
-    to the passed in import and deletes them if they are found.
-
-    If there are tarballs or branches in the default stores that might
-    conflict with working on our job, life gets very, very confusing.
-
-    :source_details: A `CodeImportSourceDetails` describing the import.
-    """
-    tree_transport = get_transport(config.codeimport.foreign_tree_store)
-    prefix = '%08x' % target_id
-    if tree_transport.has('.'):
-        for filename in tree_transport.list_dir('.'):
-            if filename.startswith(prefix):
-                tree_transport.delete(filename)
-    branchstore = get_default_bazaar_branch_store()
-    branch_name = '%08x' % target_id
-    if branchstore.transport.has(branch_name):
-        branchstore.transport.delete_tree(branch_name)
-
-
-class TestActualImportMixin:
-    """Mixin for tests that check the actual importing."""
-
-    def setUpImport(self):
-        """Set up the objects required for an import.
-
-        This means a BazaarBranchStore, CodeImport and a CodeImportJob.
-        """
-        self.bazaar_store = BazaarBranchStore(
-            self.get_transport('bazaar_store'))
-        self.foreign_commit_count = 0
-
-    def makeImportWorker(self, source_details, opener_policy):
-        """Make a new `ImportWorker`.
-
-        Override this in your subclass.
-        """
-        raise NotImplementedError(
-            "Override this with a VCS-specific implementation.")
-
-    def makeForeignCommit(self, source_details):
-        """Commit a revision to the repo described by `self.source_details`.
-
-        Increment `self.foreign_commit_count` as appropriate.
-
-        Override this in your subclass.
-        """
-        raise NotImplementedError(
-            "Override this with a VCS-specific implementation.")
-
-    def makeWorkerArguments(self, module_name, files, stacked_on_url=None):
-        """Make a list of worker arguments pointing to a real repository.
-
-        This should set `self.foreign_commit_count` to an appropriate value.
-
-        Override this in your subclass.
-        """
-        raise NotImplementedError(
-            "Override this with a VCS-specific implementation.")
-
-    def makeSourceDetails(self, module_name, files, stacked_on_url=None):
-        """Make a `CodeImportSourceDetails` pointing to a real repository."""
-        return CodeImportSourceDetails.fromArguments(self.makeWorkerArguments(
-            module_name, files, stacked_on_url=stacked_on_url))
-
-    def getStoredBazaarBranch(self, worker):
-        """Get the Bazaar branch 'worker' stored into its BazaarBranchStore.
-        """
-        branch_url = worker.bazaar_branch_store._getMirrorURL(
-            worker.source_details.target_id)
-        return Branch.open(branch_url)
-
-    def clearCaches(self):
-        """Clear any caches between worker runs, if necessary.
-
-        Override this in your subclass if you need it.
-        """
-
-    def test_exclude_hosts(self):
-        details = CodeImportSourceDetails.fromArguments(
-            self.makeWorkerArguments(
-                'trunk', [('README', b'Original contents')]) +
-            ['--exclude-host', 'bad.example.com',
-             '--exclude-host', 'worse.example.com'])
-        self.assertEqual(
-            ['bad.example.com', 'worse.example.com'], details.exclude_hosts)
-
-    def test_import(self):
-        # Running the worker on a branch that hasn't been imported yet imports
-        # the branch.
-        worker = self.makeImportWorker(self.makeSourceDetails(
-            'trunk', [('README', b'Original contents')]),
-            opener_policy=AcceptAnythingPolicy())
-        worker.run()
-        branch = self.getStoredBazaarBranch(worker)
-        self.assertEqual(self.foreign_commit_count, branch.revno())
-
-    def test_sync(self):
-        # Do an import.
-        worker = self.makeImportWorker(self.makeSourceDetails(
-            'trunk', [('README', b'Original contents')]),
-            opener_policy=AcceptAnythingPolicy())
-        worker.run()
-        branch = self.getStoredBazaarBranch(worker)
-        self.assertEqual(self.foreign_commit_count, branch.revno())
-
-        # Change the remote branch.
-        self.makeForeignCommit(worker.source_details)
-
-        # Run the same worker again.
-        self.clearCaches()
-        worker.run()
-
-        # Check that the new revisions are in the Bazaar branch.
-        branch = self.getStoredBazaarBranch(worker)
-        self.assertEqual(self.foreign_commit_count, branch.revno())
-
-    def test_import_script(self):
-        # Like test_import, but using the code-import-worker.py script
-        # to perform the import.
-        arguments = self.makeWorkerArguments(
-            'trunk', [('README', b'Original contents')])
-        source_details = CodeImportSourceDetails.fromArguments(arguments)
-
-        clean_up_default_stores_for_import(source_details.target_id)
-
-        script_path = os.path.join(
-            config.root, 'scripts', 'code-import-worker.py')
-        output = tempfile.TemporaryFile()
-        retcode = subprocess.call(
-            [script_path, '--access-policy=anything', '--'] + arguments,
-            stderr=output, stdout=output)
-        self.assertEqual(retcode, 0)
-
-        # It's important that the subprocess writes to stdout or stderr
-        # regularly to let the worker monitor know it's still alive.  That
-        # specifically is hard to test, but we can at least test that the
-        # process produced _some_ output.
-        output.seek(0, 2)
-        self.assertPositive(output.tell())
-
-        self.addCleanup(
-            lambda: clean_up_default_stores_for_import(
-                source_details.target_id))
-
-        tree_path = tempfile.mkdtemp()
-        self.addCleanup(lambda: shutil.rmtree(tree_path))
-
-        branch_url = get_default_bazaar_branch_store()._getMirrorURL(
-            source_details.target_id)
-        branch = Branch.open(branch_url)
-
-        self.assertEqual(self.foreign_commit_count, branch.revno())
-
-    def test_script_exit_codes(self):
-        # After a successful import that imports revisions, the worker exits
-        # with a code of CodeImportWorkerExitCode.SUCCESS.  After a successful
-        # import that does not import revisions, the worker exits with a code
-        # of CodeImportWorkerExitCode.SUCCESS_NOCHANGE.
-        arguments = self.makeWorkerArguments(
-            'trunk', [('README', b'Original contents')])
-        source_details = CodeImportSourceDetails.fromArguments(arguments)
-
-        clean_up_default_stores_for_import(source_details.target_id)
-
-        script_path = os.path.join(
-            config.root, 'scripts', 'code-import-worker.py')
-        output = tempfile.TemporaryFile()
-        retcode = subprocess.call(
-            [script_path, '--access-policy=anything', '--'] + arguments,
-            stderr=output, stdout=output)
-        self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS)
-        retcode = subprocess.call(
-            [script_path, '--access-policy=anything', '--'] + arguments,
-            stderr=output, stdout=output)
-        self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS_NOCHANGE)
-
-
-class CSCVSActualImportMixin(TestActualImportMixin):
-
-    def setUpImport(self):
-        """Set up the objects required for an import.
-
-        This sets up a ForeignTreeStore in addition to what
-        TestActualImportMixin.setUpImport does.
-        """
-        TestActualImportMixin.setUpImport(self)
-
-    def makeImportWorker(self, source_details, opener_policy):
-        """Make a new `ImportWorker`."""
-        return CSCVSImportWorker(
-            source_details, self.get_transport('foreign_store'),
-            self.bazaar_store, logging.getLogger(), opener_policy)
-
-
-class TestCVSImport(WorkerTest, CSCVSActualImportMixin):
-    """Tests for the worker importing and syncing a CVS module."""
-
-    def setUp(self):
-        super(TestCVSImport, self).setUp()
-        self.useFixture(FakeLogger())
-        self.setUpImport()
-
-    def makeForeignCommit(self, source_details):
-        # If you write to a file in the same second as the previous commit,
-        # CVS will not think that it has changed.
-        time.sleep(1)
-        repo = Repository(
-            six.ensure_str(source_details.cvs_root), BufferLogger())
-        repo.get(six.ensure_str(source_details.cvs_module), 'working_dir')
-        wt = CVSTree('working_dir')
-        self.build_tree_contents([('working_dir/README', 'New content')])
-        wt.commit(log='Log message')
-        self.foreign_commit_count += 1
-        shutil.rmtree('working_dir')
-
-    def makeWorkerArguments(self, module_name, files, stacked_on_url=None):
-        """Make CVS worker arguments pointing at a real CVS repo."""
-        cvs_server = CVSServer(self.makeTemporaryDirectory())
-        cvs_server.start_server()
-        self.addCleanup(cvs_server.stop_server)
-
-        cvs_server.makeModule('trunk', [('README', 'original\n')])
-
-        self.foreign_commit_count = 2
-
-        return [
-            str(self.factory.getUniqueInteger()), 'cvs', 'bzr',
-            cvs_server.getRoot(), '--cvs-module', 'trunk',
-            ]
-
-
-class SubversionImportHelpers:
-    """Implementations of `makeForeignCommit` and `makeSourceDetails` for svn.
-    """
-
-    def makeForeignCommit(self, source_details):
-        """Change the foreign tree."""
-        auth = subvertpy.ra.Auth([subvertpy.ra.get_username_provider()])
-        auth.set_parameter(subvertpy.AUTH_PARAM_DEFAULT_USERNAME, "lptest2")
-        client = subvertpy.client.Client(auth=auth)
-        client.checkout(source_details.url, 'working_tree', "HEAD")
-        file = open('working_tree/newfile', 'w')
-        file.write('No real content\n')
-        file.close()
-        client.add('working_tree/newfile')
-        client.log_msg_func = lambda c: 'Add a file'
-        (revnum, date, author) = client.commit(['working_tree'], recurse=True)
-        # CSCVS breaks on commits without an author, so make sure there
-        # is one.
-        self.assertIsNot(None, author)
-        self.foreign_commit_count += 1
-        shutil.rmtree('working_tree')
-
-    def makeWorkerArguments(self, branch_name, files, stacked_on_url=None):
-        """Make SVN worker arguments pointing at a real SVN repo."""
-        svn_server = SubversionServer(self.makeTemporaryDirectory())
-        svn_server.start_server()
-        self.addCleanup(svn_server.stop_server)
-
-        svn_branch_url = svn_server.makeBranch(branch_name, files)
-        svn_branch_url = svn_branch_url.replace('://localhost/', ':///')
-        self.foreign_commit_count = 2
-        arguments = [
-            str(self.factory.getUniqueInteger()), self.rcstype, 'bzr',
-            svn_branch_url,
-            ]
-        if stacked_on_url is not None:
-            arguments.extend(['--stacked-on', stacked_on_url])
-        return arguments
-
-
-class PullingImportWorkerTests:
-    """Tests for the PullingImportWorker subclasses."""
-
-    def createBranchReference(self):
-        """Create a pure branch reference that points to a branch.
-        """
-        branch = self.make_branch('branch')
-        t = get_transport(self.get_url('.'))
-        t.mkdir('reference')
-        a_bzrdir = BzrDir.create(self.get_url('reference'))
-        BranchReferenceFormat().initialize(a_bzrdir, target_branch=branch)
-        return a_bzrdir.root_transport.base, branch.base
-
-    def test_reject_branch_reference(self):
-        # URLs that point to other branch types than that expected by the
-        # import should be rejected.
-        args = {'rcstype': self.rcstype}
-        reference_url, target_url = self.createBranchReference()
-        if self.rcstype in ('git', 'bzr-svn'):
-            args['url'] = reference_url
-        else:
-            raise AssertionError("unexpected rcs_type %r" % self.rcstype)
-        source_details = self.factory.makeCodeImportSourceDetails(**args)
-        worker = self.makeImportWorker(source_details,
-            opener_policy=AcceptAnythingPolicy())
-        self.assertEqual(
-            CodeImportWorkerExitCode.FAILURE_INVALID, worker.run())
-
-    def test_invalid(self):
-        # If there is no branch in the target URL, exit with FAILURE_INVALID
-        worker = self.makeImportWorker(
-            self.factory.makeCodeImportSourceDetails(
-                rcstype=self.rcstype,
-                url="http://localhost/path/non/existant";),
-            opener_policy=AcceptAnythingPolicy())
-        self.assertEqual(
-            CodeImportWorkerExitCode.FAILURE_INVALID, worker.run())
-
-    def test_forbidden(self):
-        # If the branch specified is using an invalid scheme, exit with
-        # FAILURE_FORBIDDEN
-        worker = self.makeImportWorker(
-            self.factory.makeCodeImportSourceDetails(
-                rcstype=self.rcstype, url="file:///local/path"),
-            opener_policy=CodeImportBranchOpenPolicy("bzr", "bzr"))
-        self.assertEqual(
-            CodeImportWorkerExitCode.FAILURE_FORBIDDEN, worker.run())
-
-    def test_unsupported_feature(self):
-        # If there is no branch in the target URL, exit with FAILURE_INVALID
-        worker = self.makeImportWorker(self.makeSourceDetails(
-            'trunk', [('bzr\\doesnt\\support\\this', b'Original contents')]),
-            opener_policy=AcceptAnythingPolicy())
-        self.assertEqual(
-            CodeImportWorkerExitCode.FAILURE_UNSUPPORTED_FEATURE,
-            worker.run())
-
-    def test_partial(self):
-        # Only config.codeimport.revisions_import_limit will be imported
-        # in a given run.
-        worker = self.makeImportWorker(self.makeSourceDetails(
-            'trunk', [('README', b'Original contents')]),
-            opener_policy=AcceptAnythingPolicy())
-        self.makeForeignCommit(worker.source_details)
-        self.assertTrue(self.foreign_commit_count > 1)
-        import_limit = self.foreign_commit_count - 1
-        self.pushConfig(
-            'codeimport',
-            git_revisions_import_limit=import_limit,
-            svn_revisions_import_limit=import_limit)
-        self.assertEqual(
-            CodeImportWorkerExitCode.SUCCESS_PARTIAL, worker.run())
-        self.clearCaches()
-        self.assertEqual(
-            CodeImportWorkerExitCode.SUCCESS, worker.run())
-
-    def test_stacked(self):
-        stacked_on = self.make_branch('stacked-on')
-        source_details = self.makeSourceDetails(
-            'trunk', [('README', b'Original contents')],
-            stacked_on_url=stacked_on.base)
-        stacked_on.fetch(Branch.open(source_details.url))
-        base_rev_count = self.foreign_commit_count
-        # There should only be one revision there, the other
-        # one is in the stacked-on repository.
-        self.addCleanup(stacked_on.lock_read().unlock)
-        self.assertEqual(
-            base_rev_count,
-            len(stacked_on.repository.revisions.keys()))
-        worker = self.makeImportWorker(
-            source_details,
-            opener_policy=AcceptAnythingPolicy())
-        self.makeForeignCommit(source_details)
-        self.assertEqual(
-            CodeImportWorkerExitCode.SUCCESS, worker.run())
-        branch = self.getStoredBazaarBranch(worker)
-        self.assertEqual(
-            base_rev_count,
-            len(stacked_on.repository.revisions.keys()))
-        # There should only be one revision there, the other
-        # one is in the stacked-on repository.
-        self.addCleanup(branch.lock_read().unlock)
-        self.assertEqual(1,
-             len(branch.repository.revisions.without_fallbacks().keys()))
-        self.assertEqual(stacked_on.base, branch.get_stacked_on_url())
-
-
-class TestGitImport(WorkerTest, TestActualImportMixin,
-                    PullingImportWorkerTests):
-
-    rcstype = 'git'
-
-    def setUp(self):
-        super(TestGitImport, self).setUp()
-        self.setUpImport()
-
-    def tearDown(self):
-        self.clearCaches()
-        super(TestGitImport, self).tearDown()
-
-    def clearCaches(self):
-        """Clear bzr-git's cache of sqlite connections.
-
-        This is rather obscure: different test runs tend to re-use the same
-        paths on disk, which confuses bzr-git as it keeps a cache that maps
-        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
-        mapdbs().clear()
-
-    def makeImportWorker(self, source_details, opener_policy):
-        """Make a new `ImportWorker`."""
-        return GitImportWorker(
-            source_details, self.get_transport('import_data'),
-            self.bazaar_store, logging.getLogger(),
-            opener_policy=opener_policy)
-
-    def makeForeignCommit(self, source_details, message=None, ref="HEAD"):
-        """Change the foreign tree, generating exactly one commit."""
-        repo = GitRepo(local_path_from_url(source_details.url))
-        if message is None:
-            message = self.factory.getUniqueString()
-        repo.do_commit(message=message,
-            committer="Joe Random Hacker <joe@xxxxxxxxxxx>", ref=ref)
-        self.foreign_commit_count += 1
-
-    def makeWorkerArguments(self, branch_name, files, stacked_on_url=None):
-        """Make Git worker arguments pointing at a real Git repo."""
-        repository_store = self.makeTemporaryDirectory()
-        git_server = GitServer(repository_store)
-        git_server.start_server()
-        self.addCleanup(git_server.stop_server)
-
-        git_server.makeRepo('source', files)
-        self.foreign_commit_count = 1
-
-        arguments = [
-            str(self.factory.getUniqueInteger()), 'git', 'bzr',
-            git_server.get_url('source'),
-            ]
-        if stacked_on_url is not None:
-            arguments.extend(['--stacked-on', stacked_on_url])
-        return arguments
-
-    def test_non_master(self):
-        # non-master branches can be specified in the import URL.
-        source_details = self.makeSourceDetails(
-            'trunk', [('README', b'Original contents')])
-        self.makeForeignCommit(source_details, ref="refs/heads/other",
-            message="Message for other")
-        self.makeForeignCommit(source_details, ref="refs/heads/master",
-            message="Message for master")
-        source_details.url = urlutils.join_segment_parameters(
-                source_details.url, {"branch": "other"})
-        source_transport = get_transport_from_url(source_details.url)
-        self.assertEqual(
-            {"branch": "other"},
-            source_transport.get_segment_parameters())
-        worker = self.makeImportWorker(source_details,
-            opener_policy=AcceptAnythingPolicy())
-        self.assertTrue(self.foreign_commit_count > 1)
-        self.assertEqual(
-            CodeImportWorkerExitCode.SUCCESS, worker.run())
-        branch = worker.getBazaarBranch()
-        lastrev = branch.repository.get_revision(branch.last_revision())
-        self.assertEqual(lastrev.message, "Message for other")
-
-
-class TestBzrSvnImport(WorkerTest, SubversionImportHelpers,
-                       TestActualImportMixin, PullingImportWorkerTests):
-
-    rcstype = 'bzr-svn'
-
-    def setUp(self):
-        super(TestBzrSvnImport, self).setUp()
-        self.setUpImport()
-
-    def makeImportWorker(self, source_details, opener_policy):
-        """Make a new `ImportWorker`."""
-        return BzrSvnImportWorker(
-            source_details, self.get_transport('import_data'),
-            self.bazaar_store, logging.getLogger(),
-            opener_policy=opener_policy)
-
-    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
-        worker = self.makeImportWorker(self.makeSourceDetails(
-            'trunk', [('README', b'Original contents')]),
-            opener_policy=AcceptAnythingPolicy())
-        uuid = subvertpy.ra.RemoteAccess(worker.source_details.url).get_uuid()
-        cache_dir = get_cache(uuid).create_cache_dir()
-        cache_dir_contents = os.listdir(cache_dir)
-        self.assertNotEqual([], cache_dir_contents)
-        opener = BranchOpener(worker._opener_policy, worker.probers)
-        remote_branch = opener.open(six.ensure_str(worker.source_details.url))
-        worker.pushBazaarBranch(
-            self.make_branch('.'), remote_branch=remote_branch)
-        worker.import_data_store.fetch('svn-cache.tar.gz')
-        extract_tarball('svn-cache.tar.gz', '.')
-        self.assertContentEqual(cache_dir_contents, os.listdir(uuid))
-
-    def test_getBazaarBranch_fetches_bzr_svn_cache(self):
-        # 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
-        worker = self.makeImportWorker(self.makeSourceDetails(
-            'trunk', [('README', b'Original contents')]),
-            opener_policy=AcceptAnythingPolicy())
-        # Store a tarred-up cache in the store.
-        content = self.factory.getUniqueString()
-        uuid = str(uuid4())
-        os.makedirs(os.path.join('cache', uuid))
-        with open(os.path.join('cache', uuid, 'svn-cache'), 'w') as cache_file:
-            cache_file.write(content)
-        create_tarball('cache', 'svn-cache.tar.gz', filenames=[uuid])
-        worker.import_data_store.put('svn-cache.tar.gz')
-        # Make sure there's a Bazaar branch in the branch store.
-        branch = self.make_branch('branch')
-        ToBzrImportWorker.pushBazaarBranch(worker, branch)
-        # Finally, fetching the tree gets the cache too.
-        worker.getBazaarBranch()
-        cache_dir = get_cache(uuid).create_cache_dir()
-        with open(os.path.join(cache_dir, 'svn-cache')) as cache_file:
-            self.assertEqual(content, cache_file.read())
-
-
-class TestBzrImport(WorkerTest, TestActualImportMixin,
-                    PullingImportWorkerTests):
-
-    rcstype = 'bzr'
-
-    def setUp(self):
-        super(TestBzrImport, self).setUp()
-        self.setUpImport()
-
-    def makeImportWorker(self, source_details, opener_policy):
-        """Make a new `ImportWorker`."""
-        return BzrImportWorker(
-            source_details, self.get_transport('import_data'),
-            self.bazaar_store, logging.getLogger(), opener_policy)
-
-    def makeForeignCommit(self, source_details):
-        """Change the foreign tree, generating exactly one commit."""
-        branch = Branch.open(source_details.url)
-        builder = BranchBuilder(branch=branch)
-        builder.build_commit(message=self.factory.getUniqueString(),
-            committer="Joe Random Hacker <joe@xxxxxxxxxxx>")
-        self.foreign_commit_count += 1
-
-    def makeWorkerArguments(self, branch_name, files, stacked_on_url=None):
-        """Make Bzr worker arguments pointing at a real Bzr repo."""
-        repository_path = self.makeTemporaryDirectory()
-        bzr_server = BzrServer(repository_path)
-        bzr_server.start_server()
-        self.addCleanup(bzr_server.stop_server)
-
-        bzr_server.makeRepo(files)
-        self.foreign_commit_count = 1
-
-        arguments = [
-            str(self.factory.getUniqueInteger()), 'bzr', 'bzr',
-            bzr_server.get_url(),
-            ]
-        if stacked_on_url is not None:
-            arguments.extend(['--stacked-on', stacked_on_url])
-        return arguments
-
-    def test_partial(self):
-        self.skipTest(
-            "Partial fetching is not supported for native bzr branches "
-            "at the moment.")
-
-    def test_unsupported_feature(self):
-        self.skipTest("All Bazaar features are supported by Bazaar.")
-
-    def test_reject_branch_reference(self):
-        # Branch references are allowed in the BzrImporter, but their URL
-        # should be checked.
-        reference_url, target_url = self.createBranchReference()
-        source_details = self.factory.makeCodeImportSourceDetails(
-            url=reference_url, rcstype='bzr')
-        policy = _BlacklistPolicy(True, [target_url])
-        worker = self.makeImportWorker(source_details, opener_policy=policy)
-        self.assertEqual(
-            CodeImportWorkerExitCode.FAILURE_FORBIDDEN, worker.run())
-
-    def test_support_branch_reference(self):
-        # Branch references are allowed in the BzrImporter.
-        reference_url, target_url = self.createBranchReference()
-        target_branch = Branch.open(target_url)
-        builder = BranchBuilder(branch=target_branch)
-        builder.build_commit(message=self.factory.getUniqueString(),
-            committer="Some Random Hacker <jane@xxxxxxxxxxx>")
-        source_details = self.factory.makeCodeImportSourceDetails(
-            url=reference_url, rcstype='bzr')
-        worker = self.makeImportWorker(source_details,
-            opener_policy=AcceptAnythingPolicy())
-        self.assertEqual(
-            CodeImportWorkerExitCode.SUCCESS, worker.run())
-        branch = self.getStoredBazaarBranch(worker)
-        self.assertEqual(1, branch.revno())
-        self.assertEqual(
-            "Some Random Hacker <jane@xxxxxxxxxxx>",
-            branch.repository.get_revision(branch.last_revision()).committer)
-
-
-class TestGitToGitImportWorker(TestCase):
-
-    def makeWorkerArguments(self):
-        """Make Git worker arguments, pointing at a fake URL for now."""
-        return [
-            'git-unique-name', 'git', 'git',
-            self.factory.getUniqueURL(scheme='git'),
-            '--macaroon', Macaroon().serialize(),
-            ]
-
-    def test_throttleProgress(self):
-        source_details = CodeImportSourceDetails.fromArguments(
-            self.makeWorkerArguments())
-        logger = BufferLogger()
-        worker = GitToGitImportWorker(
-            source_details, logger, AcceptAnythingPolicy())
-        read_fd, write_fd = os.pipe()
-        pid = os.fork()
-        if pid == 0:  # child
-            os.close(read_fd)
-            with os.fdopen(write_fd, "wb") as write:
-                write.write(b"Starting\n")
-                for i in range(50):
-                    time.sleep(0.1)
-                    write.write(("%d ...\r" % i).encode("UTF-8"))
-                    if (i % 10) == 9:
-                        write.write(
-                            ("Interval %d\n" % (i // 10)).encode("UTF-8"))
-                write.write(b"Finishing\n")
-            os._exit(0)
-        else:  # parent
-            os.close(write_fd)
-            with os.fdopen(read_fd, "rb") as read:
-                lines = list(worker._throttleProgress(read, timeout=0.5))
-            os.waitpid(pid, 0)
-            # Matching the exact sequence of lines would be too brittle, but
-            # we require some things to be true:
-            # All the non-progress lines must be present, in the right
-            # order.
-            self.assertEqual(
-                [u"Starting\n", u"Interval 0\n", u"Interval 1\n",
-                 u"Interval 2\n", u"Interval 3\n", u"Interval 4\n",
-                 u"Finishing\n"],
-                [line for line in lines if not line.endswith(u"\r")])
-            # No more than 15 progress lines may be present (allowing some
-            # slack for the child process being slow).
-            progress_lines = [line for line in lines if line.endswith(u"\r")]
-            self.assertThat(len(progress_lines), LessThan(16))
-            # All the progress lines immediately before interval markers
-            # must be present.
-            self.assertThat(
-                progress_lines,
-                ContainsAll([u"%d ...\r" % i for i in (9, 19, 29, 39, 49)]))
-
-
-class CodeImportBranchOpenPolicyTests(TestCase):
-
-    def setUp(self):
-        super(CodeImportBranchOpenPolicyTests, self).setUp()
-        self.policy = CodeImportBranchOpenPolicy("bzr", "bzr")
-
-    def test_follows_references(self):
-        self.assertEqual(True, self.policy.should_follow_references())
-
-    def assertBadUrl(self, url):
-        self.assertRaises(BadUrl, self.policy.check_one_url, url)
-
-    def assertGoodUrl(self, url):
-        self.policy.check_one_url(url)
-
-    def test_check_one_url(self):
-        self.assertBadUrl("sftp://somehost/";)
-        self.assertBadUrl("/etc/passwd")
-        self.assertBadUrl("file:///etc/passwd")
-        self.assertBadUrl("bzr+ssh://devpad/")
-        self.assertBadUrl("bzr+ssh://devpad/")
-        self.assertBadUrl("unknown-scheme://devpad/")
-        self.assertGoodUrl("http://svn.example/branches/trunk";)
-        self.assertGoodUrl("http://user:password@svn.example/branches/trunk";)
-        self.assertBadUrl("svn+ssh://svn.example.com/bla")
-        self.assertGoodUrl("bzr://bzr.example.com/somebzrurl/")
-
-    def test_check_one_url_git_to_bzr(self):
-        self.policy = CodeImportBranchOpenPolicy("git", "bzr")
-        self.assertBadUrl("/etc/passwd")
-        self.assertBadUrl("file:///etc/passwd")
-        self.assertBadUrl("unknown-scheme://devpad/")
-        self.assertGoodUrl("git://git.example.com/repo")
-
-    def test_check_one_url_git_to_git(self):
-        self.policy = CodeImportBranchOpenPolicy("git", "git")
-        self.assertBadUrl("/etc/passwd")
-        self.assertBadUrl("file:///etc/passwd")
-        self.assertBadUrl("unknown-scheme://devpad/")
-        self.assertGoodUrl("git://git.example.com/repo")
-
-    def test_check_one_url_exclude_hosts(self):
-        self.policy = CodeImportBranchOpenPolicy(
-            "bzr", "bzr",
-            exclude_hosts=["bad.example.com", "worse.example.com"])
-        self.assertGoodUrl("git://good.example.com/repo")
-        self.assertBadUrl("git://bad.example.com/repo")
-        self.assertBadUrl("git://worse.example.com/repo")
-
-
-class RedirectTests(http_utils.TestCaseWithRedirectedWebserver, TestCase):
-
-    layer = ForeignBranchPluginLayer
-
-    def setUp(self):
-        http_utils.TestCaseWithRedirectedWebserver.setUp(self)
-        self.disable_directory_isolation()
-        BranchOpener.install_hook()
-        tree = self.make_branch_and_tree('.')
-        self.revid = tree.commit("A commit")
-        self.bazaar_store = BazaarBranchStore(
-            self.get_transport('bazaar_store'))
-
-    def makeImportWorker(self, url, opener_policy):
-        """Make a new `ImportWorker`."""
-        source_details = self.factory.makeCodeImportSourceDetails(
-            rcstype='bzr', url=url)
-        return BzrImportWorker(
-            source_details, self.get_transport('import_data'),
-            self.bazaar_store, logging.getLogger(), opener_policy)
-
-    def test_follow_redirect(self):
-        worker = self.makeImportWorker(
-            self.get_old_url(), AcceptAnythingPolicy())
-        self.assertEqual(
-            CodeImportWorkerExitCode.SUCCESS, worker.run())
-        branch_url = self.bazaar_store._getMirrorURL(
-            worker.source_details.target_id)
-        branch = Branch.open(branch_url)
-        self.assertEqual(self.revid, branch.last_revision())
-
-    def test_redirect_to_forbidden_url(self):
-        class NewUrlBlacklistPolicy(BranchOpenPolicy):
-
-            def __init__(self, new_url):
-                self.new_url = new_url
-
-            def should_follow_references(self):
-                return True
-
-            def check_one_url(self, url):
-                if url.startswith(self.new_url):
-                    raise BadUrl(url)
-
-            def transform_fallback_location(self, branch, url):
-                return urlutils.join(branch.base, url), False
-
-        policy = NewUrlBlacklistPolicy(self.get_new_url())
-        worker = self.makeImportWorker(self.get_old_url(), policy)
-        self.assertEqual(
-            CodeImportWorkerExitCode.FAILURE_FORBIDDEN, worker.run())
-
-    def test_too_many_redirects(self):
-        # Make the server redirect to itself
-        self.old_server = http_utils.HTTPServerRedirecting(
-            protocol_version=self._protocol_version)
-        self.old_server.redirect_to(self.old_server.host,
-            self.old_server.port)
-        self.old_server._url_protocol = self._url_protocol
-        self.old_server.start_server()
-        try:
-            worker = self.makeImportWorker(
-                self.old_server.get_url(), AcceptAnythingPolicy())
-        finally:
-            self.old_server.stop_server()
-        self.assertEqual(
-            CodeImportWorkerExitCode.FAILURE_INVALID, worker.run())
diff --git a/lib/lp/codehosting/codeimport/tests/test_workermonitor.py b/lib/lp/codehosting/codeimport/tests/test_workermonitor.py
deleted file mode 100644
index f60d19a..0000000
--- a/lib/lp/codehosting/codeimport/tests/test_workermonitor.py
+++ /dev/null
@@ -1,899 +0,0 @@
-# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Tests for the CodeImportWorkerMonitor and related classes."""
-
-from __future__ import absolute_import, print_function
-
-__metaclass__ = type
-
-import io
-import os
-import shutil
-import subprocess
-import tempfile
-from textwrap import dedent
-
-from bzrlib.branch import Branch
-from bzrlib.tests import TestCaseInTempDir
-from dulwich.repo import Repo as GitRepo
-from fixtures import MockPatchObject
-import oops_twisted
-from pymacaroons import Macaroon
-from six.moves import xmlrpc_client
-from testtools.matchers import (
-    AnyMatch,
-    Equals,
-    IsInstance,
-    MatchesListwise,
-    )
-from testtools.twistedsupport import (
-    assert_fails_with,
-    AsynchronousDeferredRunTest,
-    )
-from twisted.internet import (
-    defer,
-    error,
-    protocol,
-    reactor,
-    )
-from twisted.web import (
-    server,
-    xmlrpc,
-    )
-
-from lp.code.enums import CodeImportResultStatus
-from lp.code.tests.helpers import GitHostingFixture
-from lp.codehosting.codeimport.tests.servers import (
-    BzrServer,
-    CVSServer,
-    GitServer,
-    SubversionServer,
-    )
-from lp.codehosting.codeimport.tests.test_worker import (
-    clean_up_default_stores_for_import,
-    )
-from lp.codehosting.codeimport.worker import (
-    CodeImportWorkerExitCode,
-    get_default_bazaar_branch_store,
-    )
-from lp.codehosting.codeimport.workermonitor import (
-    CodeImportWorkerMonitor,
-    CodeImportWorkerMonitorProtocol,
-    ExitQuietly,
-    )
-from lp.services.config import config
-from lp.services.config.fixture import (
-    ConfigFixture,
-    ConfigUseFixture,
-    )
-from lp.services.log.logger import BufferLogger
-from lp.services.twistedsupport import suppress_stderr
-from lp.services.twistedsupport.tests.test_processmonitor import (
-    makeFailure,
-    ProcessTestsMixin,
-    )
-from lp.services.webapp import errorlog
-from lp.testing import TestCase
-from lp.xmlrpc.faults import NoSuchCodeImportJob
-
-
-class TestWorkerMonitorProtocol(ProcessTestsMixin, TestCase):
-
-    class StubWorkerMonitor:
-
-        def __init__(self):
-            self.calls = []
-
-        def updateHeartbeat(self, tail):
-            self.calls.append(('updateHeartbeat', tail))
-
-    def setUp(self):
-        self.worker_monitor = self.StubWorkerMonitor()
-        self.log_file = io.BytesIO()
-        super(TestWorkerMonitorProtocol, self).setUp()
-
-    def makeProtocol(self):
-        """See `ProcessTestsMixin.makeProtocol`."""
-        return CodeImportWorkerMonitorProtocol(
-            self.termination_deferred, self.worker_monitor, self.log_file,
-            self.clock)
-
-    def test_callsUpdateHeartbeatInConnectionMade(self):
-        # The protocol calls updateHeartbeat() as it is connected to the
-        # process.
-        # connectionMade() is called during setUp().
-        self.assertEqual(
-            self.worker_monitor.calls,
-            [('updateHeartbeat', '')])
-
-    def test_callsUpdateHeartbeatRegularly(self):
-        # The protocol calls 'updateHeartbeat' on the worker_monitor every
-        # config.codeimportworker.heartbeat_update_interval seconds.
-        # Forget the call in connectionMade()
-        self.worker_monitor.calls = []
-        # Advance the simulated time a little to avoid fencepost errors.
-        self.clock.advance(0.1)
-        # And check that updateHeartbeat is called at the frequency we expect:
-        for i in range(4):
-            self.protocol.resetTimeout()
-            self.assertEqual(
-                self.worker_monitor.calls,
-                [('updateHeartbeat', '')] * i)
-            self.clock.advance(
-                config.codeimportworker.heartbeat_update_interval)
-
-    def test_updateHeartbeatStopsOnProcessExit(self):
-        # updateHeartbeat is not called after the process has exited.
-        # Forget the call in connectionMade()
-        self.worker_monitor.calls = []
-        self.simulateProcessExit()
-        # Advance the simulated time past the time the next update is due.
-        self.clock.advance(
-            config.codeimportworker.heartbeat_update_interval + 1)
-        # Check that updateHeartbeat was not called.
-        self.assertEqual(self.worker_monitor.calls, [])
-
-    def test_outReceivedWritesToLogFile(self):
-        # outReceived writes the data it is passed into the log file.
-        output = [b'some data\n', b'some more data\n']
-        self.protocol.outReceived(output[0])
-        self.assertEqual(self.log_file.getvalue(), output[0])
-        self.protocol.outReceived(output[1])
-        self.assertEqual(self.log_file.getvalue(), output[0] + output[1])
-
-    def test_outReceivedUpdatesTail(self):
-        # 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.assertEqual(
-            self.protocol._tail, 'line 1\nline 2\nline 3\n')
-        self.protocol.outReceived('\n'.join(lines[3:]) + '\n')
-        self.assertEqual(
-            self.protocol._tail, 'line 3\nline 4\nline 5\nline 6\n')
-
-
-class FakeCodeImportScheduler(xmlrpc.XMLRPC, object):
-    """A fake implementation of `ICodeImportScheduler`.
-
-    The constructor takes a dictionary mapping job ids to information that
-    should be returned by getImportDataForJobID and the fault to return if
-    getImportDataForJobID is called with a job id not in the passed-in
-    dictionary, defaulting to a fault with the same code as
-    NoSuchCodeImportJob (because the class of the fault is lost when you go
-    through XML-RPC serialization).
-    """
-
-    def __init__(self, jobs_dict, no_such_job_fault=None):
-        super(FakeCodeImportScheduler, self).__init__(allowNone=True)
-        self.calls = []
-        self.jobs_dict = jobs_dict
-        if no_such_job_fault is None:
-            no_such_job_fault = xmlrpc.Fault(
-                faultCode=NoSuchCodeImportJob.error_code, faultString='')
-        self.no_such_job_fault = no_such_job_fault
-
-    def xmlrpc_getImportDataForJobID(self, job_id):
-        self.calls.append(('getImportDataForJobID', job_id))
-        if job_id in self.jobs_dict:
-            return self.jobs_dict[job_id]
-        else:
-            return self.no_such_job_fault
-
-    def xmlrpc_updateHeartbeat(self, job_id, log_tail):
-        self.calls.append(('updateHeartbeat', job_id, log_tail))
-        return 0
-
-    def xmlrpc_finishJobID(self, job_id, status_name, log_file):
-        self.calls.append(('finishJobID', job_id, status_name, log_file))
-
-
-class FakeCodeImportSchedulerMixin:
-
-    def makeFakeCodeImportScheduler(self, jobs_dict, no_such_job_fault=None):
-        """Start a `FakeCodeImportScheduler` and return its URL."""
-        scheduler = FakeCodeImportScheduler(
-            jobs_dict, no_such_job_fault=no_such_job_fault)
-        scheduler_listener = reactor.listenTCP(0, server.Site(scheduler))
-        self.addCleanup(scheduler_listener.stopListening)
-        scheduler_port = scheduler_listener.getHost().port
-        return scheduler, 'http://localhost:%d/' % scheduler_port
-
-
-class TestWorkerMonitorUnit(FakeCodeImportSchedulerMixin, TestCase):
-    """Unit tests for most of the `CodeImportWorkerMonitor` class.
-
-    We have to pay attention to the fact that several of the methods of the
-    `CodeImportWorkerMonitor` class are wrapped in decorators that create and
-    commit a transaction, and have to start our own transactions to check what
-    they did.
-    """
-
-    run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=20)
-
-    def makeWorkerMonitorWithJob(self, job_id=1, job_data={}):
-        self.scheduler, scheduler_url = self.makeFakeCodeImportScheduler(
-            {job_id: job_data})
-        return CodeImportWorkerMonitor(
-            job_id, BufferLogger(), xmlrpc.Proxy(scheduler_url), "anything")
-
-    def makeWorkerMonitorWithoutJob(self, fault=None):
-        self.scheduler, scheduler_url = self.makeFakeCodeImportScheduler(
-            {}, fault)
-        return CodeImportWorkerMonitor(
-            1, BufferLogger(), xmlrpc.Proxy(scheduler_url), None)
-
-    def test_getWorkerArguments(self):
-        # getWorkerArguments returns a deferred that fires with the
-        # 'arguments' part of what getImportDataForJobID returns.
-        args = [self.factory.getUniqueString(),
-                self.factory.getUniqueString()]
-        data = {'arguments': args}
-        worker_monitor = self.makeWorkerMonitorWithJob(1, data)
-        return worker_monitor.getWorkerArguments().addCallback(
-            self.assertEqual, args)
-
-    def test_getWorkerArguments_job_not_found_raises_exit_quietly(self):
-        # When getImportDataForJobID signals a fault indicating that
-        # getWorkerArguments didn't find the supplied job, getWorkerArguments
-        # translates this to an 'ExitQuietly' exception.
-        worker_monitor = self.makeWorkerMonitorWithoutJob()
-        return assert_fails_with(
-            worker_monitor.getWorkerArguments(), ExitQuietly)
-
-    def test_getWorkerArguments_endpoint_failure_raises(self):
-        # When getImportDataForJobID raises an arbitrary exception, it is not
-        # handled in any special way by getWorkerArguments.
-        self.useFixture(MockPatchObject(
-            xmlrpc_client, 'loads', side_effect=ZeroDivisionError()))
-        worker_monitor = self.makeWorkerMonitorWithoutJob()
-        return assert_fails_with(
-            worker_monitor.getWorkerArguments(), ZeroDivisionError)
-
-    def test_getWorkerArguments_arbitrary_fault_raises(self):
-        # When getImportDataForJobID signals an arbitrary fault, it is not
-        # handled in any special way by getWorkerArguments.
-        worker_monitor = self.makeWorkerMonitorWithoutJob(
-            fault=xmlrpc.Fault(1, ''))
-        return assert_fails_with(
-            worker_monitor.getWorkerArguments(), xmlrpc.Fault)
-
-    def test_updateHeartbeat(self):
-        # updateHeartbeat calls the updateHeartbeat XML-RPC method.
-        log_tail = self.factory.getUniqueString()
-        job_id = self.factory.getUniqueInteger()
-        worker_monitor = self.makeWorkerMonitorWithJob(job_id)
-
-        def check_updated_details(result):
-            self.assertEqual(
-                [('updateHeartbeat', job_id, log_tail)],
-                self.scheduler.calls)
-
-        return worker_monitor.updateHeartbeat(log_tail).addCallback(
-            check_updated_details)
-
-    def test_finishJob_calls_finishJobID_empty_log_file(self):
-        # When the log file is empty, finishJob calls finishJobID with the
-        # name of the status enum and an empty binary string.
-        job_id = self.factory.getUniqueInteger()
-        worker_monitor = self.makeWorkerMonitorWithJob(job_id)
-        self.assertEqual(worker_monitor._log_file.tell(), 0)
-
-        def check_finishJob_called(result):
-            self.assertEqual(
-                [('finishJobID', job_id, 'SUCCESS',
-                  xmlrpc_client.Binary(b''))],
-                self.scheduler.calls)
-
-        return worker_monitor.finishJob(
-            CodeImportResultStatus.SUCCESS).addCallback(
-            check_finishJob_called)
-
-    def test_finishJob_sends_nonempty_file_to_scheduler(self):
-        # finishJob method calls finishJobID with the contents of the log
-        # file.
-        job_id = self.factory.getUniqueInteger()
-        log_bytes = self.factory.getUniqueBytes()
-        worker_monitor = self.makeWorkerMonitorWithJob(job_id)
-        worker_monitor._log_file.write(log_bytes)
-
-        def check_finishJob_called(result):
-            self.assertEqual(
-                [('finishJobID', job_id, 'SUCCESS',
-                  xmlrpc_client.Binary(log_bytes))],
-                self.scheduler.calls)
-
-        return worker_monitor.finishJob(
-            CodeImportResultStatus.SUCCESS).addCallback(
-            check_finishJob_called)
-
-    def patchOutFinishJob(self, worker_monitor):
-        """Replace `worker_monitor.finishJob` with a `FakeMethod`-alike stub.
-
-        :param worker_monitor: CodeImportWorkerMonitor to patch up.
-        :return: A list of statuses that `finishJob` has been called with.
-            Future calls will be appended to this list.
-        """
-        calls = []
-
-        def finishJob(status):
-            calls.append(status)
-            return defer.succeed(None)
-
-        worker_monitor.finishJob = finishJob
-        return calls
-
-    def test_callFinishJobCallsFinishJobSuccess(self):
-        # callFinishJob calls finishJob with CodeImportResultStatus.SUCCESS if
-        # its argument is not a Failure.
-        worker_monitor = self.makeWorkerMonitorWithJob()
-        calls = self.patchOutFinishJob(worker_monitor)
-        worker_monitor.callFinishJob(None)
-        self.assertEqual(calls, [CodeImportResultStatus.SUCCESS])
-
-    @suppress_stderr
-    def test_callFinishJobCallsFinishJobFailure(self):
-        # callFinishJob calls finishJob with CodeImportResultStatus.FAILURE
-        # and swallows the failure if its argument indicates that the
-        # subprocess exited with an exit code of
-        # CodeImportWorkerExitCode.FAILURE.
-        worker_monitor = self.makeWorkerMonitorWithJob()
-        calls = self.patchOutFinishJob(worker_monitor)
-        ret = worker_monitor.callFinishJob(
-            makeFailure(
-                error.ProcessTerminated,
-                exitCode=CodeImportWorkerExitCode.FAILURE))
-        self.assertEqual(calls, [CodeImportResultStatus.FAILURE])
-        # We return the deferred that callFinishJob returns -- if
-        # callFinishJob did not swallow the error, this will fail the test.
-        return ret
-
-    def test_callFinishJobCallsFinishJobSuccessNoChange(self):
-        # If the argument to callFinishJob indicates that the subprocess
-        # exited with a code of CodeImportWorkerExitCode.SUCCESS_NOCHANGE, it
-        # calls finishJob with a status of SUCCESS_NOCHANGE.
-        worker_monitor = self.makeWorkerMonitorWithJob()
-        calls = self.patchOutFinishJob(worker_monitor)
-        ret = worker_monitor.callFinishJob(
-            makeFailure(
-                error.ProcessTerminated,
-                exitCode=CodeImportWorkerExitCode.SUCCESS_NOCHANGE))
-        self.assertEqual(calls, [CodeImportResultStatus.SUCCESS_NOCHANGE])
-        # We return the deferred that callFinishJob returns -- if
-        # callFinishJob did not swallow the error, this will fail the test.
-        return ret
-
-    @suppress_stderr
-    def test_callFinishJobCallsFinishJobArbitraryFailure(self):
-        # If the argument to callFinishJob indicates that there was some other
-        # failure that had nothing to do with the subprocess, it records
-        # failure.
-        worker_monitor = self.makeWorkerMonitorWithJob()
-        calls = self.patchOutFinishJob(worker_monitor)
-        ret = worker_monitor.callFinishJob(makeFailure(RuntimeError))
-        self.assertEqual(calls, [CodeImportResultStatus.FAILURE])
-        # We return the deferred that callFinishJob returns -- if
-        # callFinishJob did not swallow the error, this will fail the test.
-        return ret
-
-    def test_callFinishJobCallsFinishJobPartial(self):
-        # If the argument to callFinishJob indicates that the subprocess
-        # exited with a code of CodeImportWorkerExitCode.SUCCESS_PARTIAL, it
-        # calls finishJob with a status of SUCCESS_PARTIAL.
-        worker_monitor = self.makeWorkerMonitorWithJob()
-        calls = self.patchOutFinishJob(worker_monitor)
-        ret = worker_monitor.callFinishJob(
-            makeFailure(
-                error.ProcessTerminated,
-                exitCode=CodeImportWorkerExitCode.SUCCESS_PARTIAL))
-        self.assertEqual(calls, [CodeImportResultStatus.SUCCESS_PARTIAL])
-        # We return the deferred that callFinishJob returns -- if
-        # callFinishJob did not swallow the error, this will fail the test.
-        return ret
-
-    def test_callFinishJobCallsFinishJobInvalid(self):
-        # If the argument to callFinishJob indicates that the subprocess
-        # exited with a code of CodeImportWorkerExitCode.FAILURE_INVALID, it
-        # calls finishJob with a status of FAILURE_INVALID.
-        worker_monitor = self.makeWorkerMonitorWithJob()
-        calls = self.patchOutFinishJob(worker_monitor)
-        ret = worker_monitor.callFinishJob(
-            makeFailure(
-                error.ProcessTerminated,
-                exitCode=CodeImportWorkerExitCode.FAILURE_INVALID))
-        self.assertEqual(calls, [CodeImportResultStatus.FAILURE_INVALID])
-        # We return the deferred that callFinishJob returns -- if
-        # callFinishJob did not swallow the error, this will fail the test.
-        return ret
-
-    def test_callFinishJobCallsFinishJobUnsupportedFeature(self):
-        # If the argument to callFinishJob indicates that the subprocess
-        # exited with a code of FAILURE_UNSUPPORTED_FEATURE, it
-        # calls finishJob with a status of FAILURE_UNSUPPORTED_FEATURE.
-        worker_monitor = self.makeWorkerMonitorWithJob()
-        calls = self.patchOutFinishJob(worker_monitor)
-        ret = worker_monitor.callFinishJob(makeFailure(
-            error.ProcessTerminated,
-            exitCode=CodeImportWorkerExitCode.FAILURE_UNSUPPORTED_FEATURE))
-        self.assertEqual(
-            calls, [CodeImportResultStatus.FAILURE_UNSUPPORTED_FEATURE])
-        # We return the deferred that callFinishJob returns -- if
-        # callFinishJob did not swallow the error, this will fail the test.
-        return ret
-
-    def test_callFinishJobCallsFinishJobRemoteBroken(self):
-        # If the argument to callFinishJob indicates that the subprocess
-        # exited with a code of FAILURE_REMOTE_BROKEN, it
-        # calls finishJob with a status of FAILURE_REMOTE_BROKEN.
-        worker_monitor = self.makeWorkerMonitorWithJob()
-        calls = self.patchOutFinishJob(worker_monitor)
-        ret = worker_monitor.callFinishJob(
-            makeFailure(
-                error.ProcessTerminated,
-                exitCode=CodeImportWorkerExitCode.FAILURE_REMOTE_BROKEN))
-        self.assertEqual(
-            calls, [CodeImportResultStatus.FAILURE_REMOTE_BROKEN])
-        # We return the deferred that callFinishJob returns -- if
-        # callFinishJob did not swallow the error, this will fail the test.
-        return ret
-
-    @suppress_stderr
-    def test_callFinishJobLogsTracebackOnFailure(self):
-        # When callFinishJob is called with a failure, it dumps the traceback
-        # of the failure into the log file.
-        worker_monitor = self.makeWorkerMonitorWithJob()
-        ret = worker_monitor.callFinishJob(makeFailure(RuntimeError))
-
-        def check_log_file(ignored):
-            worker_monitor._log_file.seek(0)
-            log_bytes = worker_monitor._log_file.read()
-            self.assertIn(b'Traceback (most recent call last)', log_bytes)
-            self.assertIn(b'RuntimeError', log_bytes)
-        return ret.addCallback(check_log_file)
-
-    def test_callFinishJobRespects_call_finish_job(self):
-        # callFinishJob does not call finishJob if _call_finish_job is False.
-        # This is to support exiting without fuss when the job we are working
-        # on is deleted in the web UI.
-        worker_monitor = self.makeWorkerMonitorWithJob()
-        calls = self.patchOutFinishJob(worker_monitor)
-        worker_monitor._call_finish_job = False
-        worker_monitor.callFinishJob(None)
-        self.assertEqual(calls, [])
-
-
-class TestWorkerMonitorRunNoProcess(FakeCodeImportSchedulerMixin, TestCase):
-    """Tests for `CodeImportWorkerMonitor.run` that don't launch a subprocess.
-    """
-
-    run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=20)
-
-    class WorkerMonitor(CodeImportWorkerMonitor):
-        """See `CodeImportWorkerMonitor`.
-
-        Override _launchProcess to return a deferred that we can
-        callback/errback as we choose.  Passing ``has_job=False`` to the
-        constructor will cause getWorkerArguments() to raise ExitQuietly (this
-        bit is tested above).
-        """
-
-        def __init__(self, job_id, logger, codeimport_endpoint, access_policy,
-                     process_deferred):
-            CodeImportWorkerMonitor.__init__(
-                self, job_id, logger, codeimport_endpoint, access_policy)
-            self.result_status = None
-            self.process_deferred = process_deferred
-
-        def _launchProcess(self, worker_arguments):
-            return self.process_deferred
-
-        def finishJob(self, status):
-            assert self.result_status is None, "finishJob called twice!"
-            self.result_status = status
-            return defer.succeed(None)
-
-    def makeWorkerMonitor(self, process_deferred, has_job=True):
-        if has_job:
-            job_data = {1: {'arguments': []}}
-        else:
-            job_data = {}
-        _, scheduler_url = self.makeFakeCodeImportScheduler(job_data)
-        return self.WorkerMonitor(
-            1, BufferLogger(), xmlrpc.Proxy(scheduler_url), "anything",
-            process_deferred)
-
-    def assertFinishJobCalledWithStatus(self, ignored, worker_monitor,
-                                        status):
-        """Assert that finishJob was called with the given status."""
-        self.assertEqual(worker_monitor.result_status, status)
-
-    def assertFinishJobNotCalled(self, ignored, worker_monitor):
-        """Assert that finishJob was called with the given status."""
-        self.assertFinishJobCalledWithStatus(ignored, worker_monitor, None)
-
-    def test_success(self):
-        # In the successful case, finishJob is called with
-        # CodeImportResultStatus.SUCCESS.
-        worker_monitor = self.makeWorkerMonitor(defer.succeed(None))
-        return worker_monitor.run().addCallback(
-            self.assertFinishJobCalledWithStatus, worker_monitor,
-            CodeImportResultStatus.SUCCESS)
-
-    def test_failure(self):
-        # If the process deferred is fired with a failure, finishJob is called
-        # with CodeImportResultStatus.FAILURE, but the call to run() still
-        # succeeds.
-        # Need a twisted error reporting stack (normally set up by
-        # loggingsupport.set_up_oops_reporting).
-        errorlog.globalErrorUtility.configure(
-            config_factory=oops_twisted.Config,
-            publisher_adapter=oops_twisted.defer_publisher,
-            publisher_helpers=oops_twisted.publishers)
-        self.addCleanup(errorlog.globalErrorUtility.configure)
-        worker_monitor = self.makeWorkerMonitor(defer.fail(RuntimeError()))
-        return worker_monitor.run().addCallback(
-            self.assertFinishJobCalledWithStatus, worker_monitor,
-            CodeImportResultStatus.FAILURE)
-
-    def test_quiet_exit(self):
-        # If the process deferred fails with ExitQuietly, the call to run()
-        # succeeds, and finishJob is not called at all.
-        worker_monitor = self.makeWorkerMonitor(
-            defer.succeed(None), has_job=False)
-        return worker_monitor.run().addCallback(
-            self.assertFinishJobNotCalled, worker_monitor)
-
-    def test_quiet_exit_from_finishJob(self):
-        # If finishJob fails with ExitQuietly, the call to run() still
-        # succeeds.
-        worker_monitor = self.makeWorkerMonitor(defer.succeed(None))
-
-        def finishJob(reason):
-            raise ExitQuietly
-        worker_monitor.finishJob = finishJob
-        return worker_monitor.run()
-
-    def test_callFinishJob_logs_failure(self):
-        # callFinishJob logs a failure from the child process.
-        errorlog.globalErrorUtility.configure(
-            config_factory=oops_twisted.Config,
-            publisher_adapter=oops_twisted.defer_publisher,
-            publisher_helpers=oops_twisted.publishers)
-        self.addCleanup(errorlog.globalErrorUtility.configure)
-        failure_msg = b"test_callFinishJob_logs_failure expected failure"
-        worker_monitor = self.makeWorkerMonitor(
-            defer.fail(RuntimeError(failure_msg)))
-        d = worker_monitor.run()
-
-        def check_log_file(ignored):
-            worker_monitor._log_file.seek(0)
-            log_bytes = worker_monitor._log_file.read()
-            self.assertIn(
-                b"Failure: exceptions.RuntimeError: " + failure_msg,
-                log_bytes)
-
-        d.addCallback(check_log_file)
-        return d
-
-
-class CIWorkerMonitorProtocolForTesting(CodeImportWorkerMonitorProtocol):
-    """A `CodeImportWorkerMonitorProtocol` that counts `resetTimeout` calls.
-    """
-
-    def __init__(self, deferred, worker_monitor, log_file, clock=None):
-        """See `CodeImportWorkerMonitorProtocol.__init__`."""
-        CodeImportWorkerMonitorProtocol.__init__(
-            self, deferred, worker_monitor, log_file, clock)
-        self.reset_calls = 0
-
-    def resetTimeout(self):
-        """See `ProcessMonitorProtocolWithTimeout.resetTimeout`."""
-        CodeImportWorkerMonitorProtocol.resetTimeout(self)
-        self.reset_calls += 1
-
-
-class CIWorkerMonitorForTesting(CodeImportWorkerMonitor):
-    """A `CodeImportWorkerMonitor` that hangs on to the process protocol."""
-
-    def _makeProcessProtocol(self, deferred):
-        """See `CodeImportWorkerMonitor._makeProcessProtocol`.
-
-        We hang on to the constructed object for later inspection -- see
-        `TestWorkerMonitorIntegration.assertImported`.
-        """
-        protocol = CIWorkerMonitorProtocolForTesting(
-            deferred, self, self._log_file)
-        self._protocol = protocol
-        return protocol
-
-
-class TestWorkerMonitorIntegration(FakeCodeImportSchedulerMixin,
-                                   TestCaseInTempDir, TestCase):
-
-    run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=60)
-
-    def setUp(self):
-        super(TestWorkerMonitorIntegration, self).setUp()
-        self.repo_path = tempfile.mkdtemp()
-        self.disable_directory_isolation()
-        self.addCleanup(shutil.rmtree, self.repo_path)
-        self.foreign_commit_count = 0
-
-    def makeCVSCodeImport(self):
-        """Make arguments that point to a real CVS repository."""
-        cvs_server = CVSServer(self.repo_path)
-        cvs_server.start_server()
-        self.addCleanup(cvs_server.stop_server)
-
-        cvs_server.makeModule('trunk', [('README', 'original\n')])
-        self.foreign_commit_count = 2
-
-        return [
-            str(self.factory.getUniqueInteger()), 'cvs', 'bzr',
-            cvs_server.getRoot(), '--cvs-module', 'trunk',
-            ]
-
-    def makeBzrSvnCodeImport(self):
-        """Make arguments that point to a real Subversion repository."""
-        self.subversion_server = SubversionServer(
-            self.repo_path, use_svn_serve=True)
-        self.subversion_server.start_server()
-        self.addCleanup(self.subversion_server.stop_server)
-        url = self.subversion_server.makeBranch(
-            'trunk', [('README', b'contents')])
-        self.foreign_commit_count = 2
-
-        return [
-            str(self.factory.getUniqueInteger()), 'bzr-svn', 'bzr',
-            url,
-            ]
-
-    def makeGitCodeImport(self, target_rcs_type='bzr'):
-        """Make arguments that point to a real Git repository."""
-        self.git_server = GitServer(self.repo_path, use_server=False)
-        self.git_server.start_server()
-        self.addCleanup(self.git_server.stop_server)
-
-        self.git_server.makeRepo('source', [('README', 'contents')])
-        self.foreign_commit_count = 1
-
-        target_id = (
-            str(self.factory.getUniqueInteger()) if target_rcs_type == 'bzr'
-            else self.factory.getUniqueUnicode())
-        arguments = [
-            target_id, 'git', target_rcs_type,
-            self.git_server.get_url('source'),
-            ]
-        if target_rcs_type == 'git':
-            arguments.extend(['--macaroon', Macaroon().serialize()])
-        return arguments
-
-    def makeBzrCodeImport(self):
-        """Make arguments that point to a real Bazaar branch."""
-        self.bzr_server = BzrServer(self.repo_path)
-        self.bzr_server.start_server()
-        self.addCleanup(self.bzr_server.stop_server)
-
-        self.bzr_server.makeRepo([('README', 'contents')])
-        self.foreign_commit_count = 1
-
-        return [
-            str(self.factory.getUniqueInteger()), 'bzr', 'bzr',
-            self.bzr_server.get_url(),
-            ]
-
-    def getStartedJobForImport(self, arguments):
-        """Get a started `CodeImportJob` for `code_import`.
-
-        This method returns a job ID and job data, imitating an approved job
-        on Launchpad.  It also makes sure there are no branches or foreign
-        trees in the default stores to interfere with processing this job.
-        """
-        if arguments[2] == 'bzr':
-            target_id = int(arguments[0])
-            clean_up_default_stores_for_import(target_id)
-            self.addCleanup(clean_up_default_stores_for_import, target_id)
-        return (1, {'arguments': arguments})
-
-    def makeTargetGitServer(self):
-        """Set up a target Git server that can receive imports."""
-        self.target_store = tempfile.mkdtemp()
-        self.addCleanup(shutil.rmtree, self.target_store)
-        self.target_git_server = GitServer(self.target_store, use_server=True)
-        self.target_git_server.start_server()
-        self.addCleanup(self.target_git_server.stop_server)
-        config_name = self.factory.getUniqueUnicode()
-        config_fixture = self.useFixture(ConfigFixture(
-            config_name, os.environ['LPCONFIG']))
-        setting_lines = [
-            "[codehosting]",
-            "git_browse_root: %s" % self.target_git_server.get_url(""),
-            "",
-            "[launchpad]",
-            "internal_macaroon_secret_key: some-secret",
-            ]
-        config_fixture.add_section("\n" + "\n".join(setting_lines))
-        self.useFixture(ConfigUseFixture(config_name))
-        self.useFixture(GitHostingFixture())
-
-    def assertBranchImportedOKForCodeImport(self, target_id):
-        """Assert that a branch was pushed into the default branch store."""
-        if target_id.isdigit():
-            url = get_default_bazaar_branch_store()._getMirrorURL(
-                int(target_id))
-            branch = Branch.open(url)
-            commit_count = branch.revno()
-        else:
-            repo_path = os.path.join(self.target_store, target_id)
-            commit_count = int(subprocess.check_output(
-                ["git", "rev-list", "--count", "HEAD"],
-                cwd=repo_path, universal_newlines=True))
-        self.assertEqual(self.foreign_commit_count, commit_count)
-
-    def assertImported(self, job_id, job_data):
-        """Assert that the code import with the given job id was imported.
-
-        Since we don't have a full Launchpad appserver instance here, we
-        just check that the code import worker has made the correct XML-RPC
-        calls via the given worker monitor.
-        """
-        # In the in-memory tests, check that resetTimeout on the
-        # CodeImportWorkerMonitorProtocol was called at least once.
-        if self._protocol is not None:
-            self.assertPositive(self._protocol.reset_calls)
-        self.assertThat(self.scheduler.calls, AnyMatch(
-            MatchesListwise([
-                Equals('finishJobID'),
-                Equals(job_id),
-                Equals('SUCCESS'),
-                IsInstance(xmlrpc_client.Binary),
-                ])))
-        self.assertBranchImportedOKForCodeImport(job_data['arguments'][0])
-
-    @defer.inlineCallbacks
-    def performImport(self, job_id, job_data):
-        """Perform the import job with ID job_id and data job_data.
-
-        Return a Deferred that fires when the job is done.
-
-        This implementation does it in-process.
-        """
-        logger = BufferLogger()
-        self.scheduler, scheduler_url = self.makeFakeCodeImportScheduler(
-            {job_id: job_data})
-        worker_monitor = CIWorkerMonitorForTesting(
-            job_id, logger, xmlrpc.Proxy(scheduler_url), "anything")
-        result = yield worker_monitor.run()
-        self._protocol = worker_monitor._protocol
-        defer.returnValue(result)
-
-    @defer.inlineCallbacks
-    def test_import_cvs(self):
-        # Create a CVS CodeImport and import it.
-        job_id, job_data = self.getStartedJobForImport(
-            self.makeCVSCodeImport())
-        yield self.performImport(job_id, job_data)
-        self.assertImported(job_id, job_data)
-
-    @defer.inlineCallbacks
-    def test_import_git(self):
-        # Create a Git CodeImport and import it.
-        job_id, job_data = self.getStartedJobForImport(
-            self.makeGitCodeImport())
-        yield self.performImport(job_id, job_data)
-        self.assertImported(job_id, job_data)
-
-    @defer.inlineCallbacks
-    def test_import_git_to_git(self):
-        # Create a Git-to-Git CodeImport and import it.
-        self.makeTargetGitServer()
-        job_id, job_data = self.getStartedJobForImport(
-            self.makeGitCodeImport(target_rcs_type='git'))
-        target_repo_path = os.path.join(
-            self.target_store, job_data['arguments'][0])
-        os.makedirs(target_repo_path)
-        self.target_git_server.createRepository(target_repo_path, bare=True)
-        yield self.performImport(job_id, job_data)
-        self.assertImported(job_id, job_data)
-        target_repo = GitRepo(target_repo_path)
-        self.assertContentEqual(
-            ["heads/master"], target_repo.refs.keys(base="refs"))
-        self.assertEqual(
-            "ref: refs/heads/master", target_repo.refs.read_ref("HEAD"))
-
-    @defer.inlineCallbacks
-    def test_import_git_to_git_refs_changed(self):
-        # Create a Git-to-Git CodeImport and import it incrementally with
-        # ref and HEAD changes.
-        self.makeTargetGitServer()
-        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"]
-        target_repo_path = os.path.join(
-            self.target_store, job_data['arguments'][0])
-        self.target_git_server.makeRepo(
-            job_data['arguments'][0], [("NEWS", "contents")])
-        yield self.performImport(job_id, job_data)
-        self.assertImported(job_id, job_data)
-        target_repo = GitRepo(target_repo_path)
-        self.assertContentEqual(
-            ["heads/one", "heads/two"], target_repo.refs.keys(base="refs"))
-        self.assertEqual(
-            "ref: refs/heads/one",
-            GitRepo(target_repo_path).refs.read_ref("HEAD"))
-
-    @defer.inlineCallbacks
-    def test_import_bzr(self):
-        # Create a Bazaar CodeImport and import it.
-        job_id, job_data = self.getStartedJobForImport(
-            self.makeBzrCodeImport())
-        yield self.performImport(job_id, job_data)
-        self.assertImported(job_id, job_data)
-
-    @defer.inlineCallbacks
-    def test_import_bzrsvn(self):
-        # Create a Subversion-via-bzr-svn CodeImport and import it.
-        job_id, job_data = self.getStartedJobForImport(
-            self.makeBzrSvnCodeImport())
-        yield self.performImport(job_id, job_data)
-        self.assertImported(job_id, job_data)
-
-
-class DeferredOnExit(protocol.ProcessProtocol):
-
-    def __init__(self, deferred):
-        self._deferred = deferred
-
-    def processEnded(self, reason):
-        if reason.check(error.ProcessDone):
-            self._deferred.callback(None)
-        else:
-            self._deferred.errback(reason)
-
-
-class TestWorkerMonitorIntegrationScript(TestWorkerMonitorIntegration):
-    """Tests for CodeImportWorkerMonitor that execute a child process."""
-
-    def setUp(self):
-        super(TestWorkerMonitorIntegrationScript, self).setUp()
-        self._protocol = None
-
-    def performImport(self, job_id, job_data):
-        """Perform the import job with ID job_id and data job_data.
-
-        Return a Deferred that fires when the job is done.
-
-        This implementation does it in a child process.
-        """
-        self.scheduler, scheduler_url = self.makeFakeCodeImportScheduler(
-            {job_id: job_data})
-        config_name = self.factory.getUniqueUnicode()
-        config_fixture = self.useFixture(
-            ConfigFixture(config_name, os.environ['LPCONFIG']))
-        config_fixture.add_section(dedent("""
-            [codeimportdispatcher]
-            codeimportscheduler_url: %s
-            """) % scheduler_url)
-        self.useFixture(ConfigUseFixture(config_name))
-        script_path = os.path.join(
-            config.root, 'scripts', 'code-import-worker-monitor.py')
-        process_end_deferred = defer.Deferred()
-        # The "childFDs={0:0, 1:1, 2:2}" means that any output from the script
-        # goes to the test runner's console rather than to pipes that noone is
-        # listening too.
-        interpreter = '%s/bin/py' % config.root
-        reactor.spawnProcess(
-            DeferredOnExit(process_end_deferred), interpreter, [
-                interpreter,
-                script_path,
-                '--access-policy=anything',
-                str(job_id),
-                '-q',
-                ], childFDs={0: 0, 1: 1, 2: 2}, env=os.environ)
-        return process_end_deferred
diff --git a/lib/lp/codehosting/codeimport/uifactory.py b/lib/lp/codehosting/codeimport/uifactory.py
deleted file mode 100644
index 2b8bfd8..0000000
--- a/lib/lp/codehosting/codeimport/uifactory.py
+++ /dev/null
@@ -1,207 +0,0 @@
-# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""A UIFactory useful for code imports."""
-
-from __future__ import absolute_import, print_function
-
-__metaclass__ = type
-__all__ = ['LoggingUIFactory']
-
-
-import sys
-import time
-
-from bzrlib.ui import NoninteractiveUIFactory
-from bzrlib.ui.text import TextProgressView
-import six
-
-
-class LoggingUIFactory(NoninteractiveUIFactory):
-    """A UI Factory that produces reasonably sparse logging style output.
-
-    The goal is to produce a line of output no more often than once a minute
-    (by default).
-    """
-
-    # XXX: JelmerVernooij 2011-08-02 bug=820127: This seems generic enough to
-    # live in bzrlib.ui
-
-    def __init__(self, time_source=time.time, logger=None, interval=60.0):
-        """Construct a `LoggingUIFactory`.
-
-        :param time_source: A callable that returns time in seconds since the
-            epoch.  Defaults to ``time.time`` and should be replaced with
-            something deterministic in tests.
-        :param logger: The logger object to write to
-        :param interval: Don't produce output more often than once every this
-            many seconds.  Defaults to 60 seconds.
-        """
-        NoninteractiveUIFactory.__init__(self)
-        self.interval = interval
-        self.logger = logger
-        self._progress_view = LoggingTextProgressView(
-            time_source, lambda m: logger.info("%s", m), interval)
-
-    def show_user_warning(self, warning_id, **message_args):
-        self.logger.warning(
-            "%s", self.format_user_warning(warning_id, message_args))
-
-    def show_warning(self, msg):
-        self.logger.warning("%s", six.ensure_binary(msg))
-
-    def get_username(self, prompt, **kwargs):
-        return None
-
-    def get_password(self, prompt, **kwargs):
-        return None
-
-    def show_message(self, msg):
-        self.logger.info("%s", msg)
-
-    def note(self, msg):
-        self.logger.info("%s", msg)
-
-    def show_error(self, msg):
-        self.logger.error("%s", msg)
-
-    def _progress_updated(self, task):
-        """A task has been updated and wants to be displayed.
-        """
-        if not self._task_stack:
-            self.logger.warning("%r updated but no tasks are active", task)
-        self._progress_view.show_progress(task)
-
-    def _progress_all_finished(self):
-        self._progress_view.clear()
-
-    def report_transport_activity(self, transport, byte_count, direction):
-        """Called by transports as they do IO.
-
-        This may update a progress bar, spinner, or similar display.
-        By default it does nothing.
-        """
-        self._progress_view.show_transport_activity(transport,
-            direction, byte_count)
-
-
-class LoggingTextProgressView(TextProgressView):
-    """Support class for `LoggingUIFactory`. """
-
-    def __init__(self, time_source, writer, interval):
-        """See `LoggingUIFactory.__init__` for descriptions of the parameters.
-        """
-        # If anything refers to _term_file, that's a bug for us.
-        TextProgressView.__init__(self, term_file=None)
-        self._writer = writer
-        self.time_source = time_source
-        if writer is None:
-            self.write = sys.stdout.write
-        else:
-            self.write = writer
-        # _transport_expire_time is how long to keep the transport activity in
-        # the progress bar for when show_progress is called.  We opt for
-        # always just showing the task info.
-        self._transport_expire_time = 0
-        # We repaint only after 'interval' seconds whether we're being told
-        # about task progress or transport activity.
-        self._update_repaint_frequency = interval
-        self._transport_repaint_frequency = interval
-
-    def _show_line(self, s):
-        # This is a bit hackish: even though this method is just expected to
-        # produce output, we reset the _bytes_since_update so that transport
-        # activity is reported as "since last log message" and
-        # _transport_update_time so that transport activity doesn't cause an
-        # update until it occurs more than _transport_repaint_frequency
-        # seconds after the last update of any kind.
-        self._bytes_since_update = 0
-        self._transport_update_time = self.time_source()
-        self._writer(s)
-
-    def _render_bar(self):
-        # There's no point showing a progress bar in a flat log.
-        return ''
-
-    def _render_line(self):
-        bar_string = self._render_bar()
-        if self._last_task:
-            task_part, counter_part = self._format_task(self._last_task)
-        else:
-            task_part = counter_part = ''
-        if self._last_task and not self._last_task.show_transport_activity:
-            trans = ''
-        else:
-            trans = self._last_transport_msg
-        # the bar separates the transport activity from the message, so even
-        # if there's no bar or spinner, we must show something if both those
-        # fields are present
-        if (task_part and trans) and not bar_string:
-            bar_string = ' | '
-        s = trans + bar_string + task_part + counter_part
-        return s
-
-    def _format_transport_msg(self, scheme, dir_char, rate):
-        # 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
-    # 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,
-    # _transport_expire_time and _transport_repaint_frequency instance
-    # variables rather than having these as hard coded constants.  These
-    # changes could and should be ported upstream and then we won't have to
-    # carry our version of this code around any more.
-
-    def show_progress(self, task):
-        """Called by the task object when it has changed.
-
-        :param task: The top task object; its parents are also included
-            by following links.
-        """
-        must_update = task is not self._last_task
-        self._last_task = task
-        now = self.time_source()
-        if ((not must_update) and
-            (now < self._last_repaint + self._update_repaint_frequency)):
-            return
-        if now > self._transport_update_time + self._transport_expire_time:
-            # no recent activity; expire it
-            self._last_transport_msg = ''
-        self._last_repaint = now
-        self._repaint()
-
-    def show_transport_activity(self, transport, direction, byte_count):
-        """Called by transports via the ui_factory, as they do IO.
-
-        This may update a progress bar, spinner, or similar display.
-        By default it does nothing.
-        """
-        # XXX: Probably there should be a transport activity model, and that
-        # too should be seen by the progress view, rather than being poked in
-        # here.
-        self._total_byte_count += byte_count
-        self._bytes_since_update += byte_count
-        now = self.time_source()
-        if self._transport_update_time is None:
-            self._transport_update_time = now
-        elif now >= (self._transport_update_time
-                     + self._transport_repaint_frequency):
-            # guard against clock stepping backwards, and don't update too
-            # often
-            rate = self._bytes_since_update / (
-                now - self._transport_update_time)
-            scheme = getattr(transport, '_scheme', None) or repr(transport)
-            if direction == 'read':
-                dir_char = '>'
-            elif direction == 'write':
-                dir_char = '<'
-            else:
-                dir_char = '?'
-            msg = self._format_transport_msg(scheme, dir_char, rate)
-            self._transport_update_time = now
-            self._last_repaint = now
-            self._bytes_since_update = 0
-            self._last_transport_msg = msg
-            self._repaint()
diff --git a/lib/lp/codehosting/codeimport/worker.py b/lib/lp/codehosting/codeimport/worker.py
deleted file mode 100644
index 43f6938..0000000
--- a/lib/lp/codehosting/codeimport/worker.py
+++ /dev/null
@@ -1,1221 +0,0 @@
-# Copyright 2009-2019 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""The code import worker. This imports code from foreign repositories."""
-
-from __future__ import absolute_import, print_function
-
-__metaclass__ = type
-__all__ = [
-    'BazaarBranchStore',
-    'BzrImportWorker',
-    'BzrSvnImportWorker',
-    'CSCVSImportWorker',
-    'CodeImportBranchOpenPolicy',
-    'CodeImportSourceDetails',
-    'CodeImportWorkerExitCode',
-    'ForeignTreeStore',
-    'GitImportWorker',
-    'ImportWorker',
-    'ToBzrImportWorker',
-    'get_default_bazaar_branch_store',
-    ]
-
-from argparse import ArgumentParser
-import io
-import os
-import shutil
-import signal
-import subprocess
-
-# FIRST Ensure correct plugins are loaded. Do not delete this comment or the
-# line below this comment.
-import lp.codehosting
-
-from bzrlib.branch import (
-    Branch,
-    InterBranch,
-    )
-from bzrlib.bzrdir import (
-    BzrDir,
-    BzrDirFormat,
-    )
-from bzrlib.errors import (
-    ConnectionError,
-    InvalidEntryName,
-    NoRepositoryPresent,
-    NoSuchFile,
-    NotBranchError,
-    TooManyRedirections,
-    )
-from bzrlib.transport import (
-    get_transport_from_path,
-    get_transport_from_url,
-    )
-import bzrlib.ui
-from bzrlib.upgrade import upgrade
-from bzrlib.url_policy_open import (
-    BadUrl,
-    BranchOpener,
-    BranchOpenPolicy,
-    )
-from bzrlib.urlutils import (
-    join as urljoin,
-    local_path_from_url,
-    )
-import cscvs
-from cscvs.cmds import totla
-import CVS
-from dulwich.errors import GitProtocolError
-from dulwich.protocol import (
-    pkt_line,
-    Protocol,
-    )
-from lazr.uri import (
-    InvalidURIError,
-    URI,
-    )
-from pymacaroons import Macaroon
-import SCM
-import six
-from six.moves.urllib.parse import (
-    urlsplit,
-    urlunsplit,
-    )
-
-from lp.codehosting.codeimport.foreigntree import CVSWorkingTree
-from lp.codehosting.codeimport.tarball import (
-    create_tarball,
-    extract_tarball,
-    )
-from lp.codehosting.codeimport.uifactory import LoggingUIFactory
-from lp.services.config import config
-from lp.services.propertycache import cachedproperty
-from lp.services.timeout import (
-    raise_for_status_redacted,
-    urlfetch,
-    )
-from lp.services.utils import sanitise_urls
-
-
-class CodeImportBranchOpenPolicy(BranchOpenPolicy):
-    """Branch open policy for code imports.
-
-    In summary:
-     - follow references,
-     - only open non-Launchpad URLs for imports from Bazaar to Bazaar or
-       from Git to Git
-     - only open the allowed schemes
-    """
-
-    allowed_schemes = ['http', 'https', 'svn', 'git', 'ftp', 'bzr']
-
-    def __init__(self, rcstype, target_rcstype, exclude_hosts=None):
-        self.rcstype = rcstype
-        self.target_rcstype = target_rcstype
-        self.exclude_hosts = list(exclude_hosts or [])
-
-    def should_follow_references(self):
-        """See `BranchOpenPolicy.should_follow_references`.
-
-        We traverse branch references for MIRRORED branches because they
-        provide a useful redirection mechanism and we want to be consistent
-        with the bzr command line.
-        """
-        return True
-
-    def transform_fallback_location(self, branch, url):
-        """See `BranchOpenPolicy.transform_fallback_location`.
-
-        For mirrored branches, we stack on whatever the remote branch claims
-        to stack on, but this URL still needs to be checked.
-        """
-        return urljoin(branch.base, url), True
-
-    def check_one_url(self, url):
-        """See `BranchOpenPolicy.check_one_url`.
-
-        We refuse to mirror Bazaar branches from Launchpad, or any branches
-        from a ssh-like or file URL.
-        """
-        try:
-            uri = URI(url)
-        except InvalidURIError:
-            raise BadUrl(url)
-        for hostname in self.exclude_hosts:
-            if uri.underDomain(hostname):
-                raise BadUrl(url)
-        if uri.scheme not in self.allowed_schemes:
-            raise BadUrl(url)
-
-
-class CodeImportWorkerExitCode:
-    """Exit codes used by the code import worker script."""
-
-    SUCCESS = 0
-    FAILURE = 1
-    SUCCESS_NOCHANGE = 2
-    SUCCESS_PARTIAL = 3
-    FAILURE_INVALID = 4
-    FAILURE_UNSUPPORTED_FEATURE = 5
-    FAILURE_FORBIDDEN = 6
-    FAILURE_REMOTE_BROKEN = 7
-
-
-class BazaarBranchStore:
-    """A place where Bazaar branches of code imports are kept."""
-
-    def __init__(self, transport):
-        """Construct a Bazaar branch store based at `transport`."""
-        self.transport = transport
-
-    def _getMirrorURL(self, db_branch_id, push=False):
-        """Return the URL that `db_branch` is stored at."""
-        base_url = self.transport.base
-        if push:
-            # Pulling large branches over sftp is less CPU-intensive, but
-            # pushing over bzr+ssh seems to be more reliable.
-            split = urlsplit(base_url)
-            if split.scheme == 'sftp':
-                base_url = urlunsplit([
-                    'bzr+ssh', split.netloc, split.path, split.query,
-                    split.fragment])
-        return urljoin(base_url, '%08x' % db_branch_id)
-
-    def pull(self, db_branch_id, target_path, required_format,
-             needs_tree=False, stacked_on_url=None):
-        """Pull down the Bazaar branch of an import to `target_path`.
-
-        :return: A Bazaar branch for the code import corresponding to the
-            database branch with id `db_branch_id`.
-        """
-        remote_url = self._getMirrorURL(db_branch_id)
-        try:
-            remote_bzr_dir = BzrDir.open(remote_url)
-        except NotBranchError:
-            local_branch = BzrDir.create_branch_and_repo(
-                target_path, format=required_format)
-            if needs_tree:
-                local_branch.bzrdir.create_workingtree()
-            if stacked_on_url:
-                local_branch.set_stacked_on_url(stacked_on_url)
-            return local_branch
-        # The proper thing to do here would be to call
-        # "remote_bzr_dir.sprout()".  But 2a fetch slowly checks which
-        # revisions are in the ancestry of the tip of the remote branch, which
-        # we strictly don't care about, so we just copy the whole thing down
-        # at the vfs level.
-        control_dir = remote_bzr_dir.root_transport.relpath(
-            remote_bzr_dir.transport.abspath('.'))
-        target = get_transport_from_path(target_path)
-        target_control = target.clone(control_dir)
-        target_control.create_prefix()
-        remote_bzr_dir.transport.copy_tree_to_transport(target_control)
-        local_bzr_dir = BzrDir.open_from_transport(target)
-        if local_bzr_dir.needs_format_conversion(format=required_format):
-            try:
-                local_bzr_dir.root_transport.delete_tree('backup.bzr')
-            except NoSuchFile:
-                pass
-            upgrade(target_path, required_format, clean_up=True)
-        if needs_tree:
-            local_bzr_dir.create_workingtree()
-        return local_bzr_dir.open_branch()
-
-    def push(self, db_branch_id, bzr_branch, required_format,
-             stacked_on_url=None):
-        """Push up `bzr_branch` as the Bazaar branch for `code_import`.
-
-        :return: A boolean that is true if the push was non-trivial
-            (i.e. actually transferred revisions).
-        """
-        self.transport.create_prefix()
-        target_url = self._getMirrorURL(db_branch_id, push=True)
-        try:
-            remote_branch = Branch.open(target_url)
-        except NotBranchError:
-            remote_branch = BzrDir.create_branch_and_repo(
-                target_url, format=required_format)
-            old_branch = None
-        else:
-            if remote_branch.bzrdir.needs_format_conversion(
-                    required_format):
-                # For upgrades, push to a new branch in
-                # the new format. When done pushing,
-                # retire the old .bzr directory and rename
-                # the new one in place.
-                old_branch = remote_branch
-                upgrade_url = urljoin(target_url, "backup.bzr")
-                try:
-                    remote_branch.bzrdir.root_transport.delete_tree(
-                        'backup.bzr')
-                except NoSuchFile:
-                    pass
-                remote_branch = BzrDir.create_branch_and_repo(
-                    upgrade_url, format=required_format)
-            else:
-                old_branch = None
-        # This can be done safely, since only modern formats are used to
-        # import to.
-        if stacked_on_url is not None:
-            remote_branch.set_stacked_on_url(stacked_on_url)
-        pull_result = remote_branch.pull(bzr_branch, overwrite=True)
-        # Because of the way we do incremental imports, there may be revisions
-        # in the branch's repo that are not in the ancestry of the branch tip.
-        # We need to transfer them too.
-        remote_branch.repository.fetch(bzr_branch.repository)
-        if old_branch is not None:
-            # The format has changed; move the new format
-            # branch in place.
-            base_transport = old_branch.bzrdir.root_transport
-            base_transport.delete_tree('.bzr')
-            base_transport.rename("backup.bzr/.bzr", ".bzr")
-            base_transport.rmdir("backup.bzr")
-        return pull_result.old_revid != pull_result.new_revid
-
-
-def get_default_bazaar_branch_store():
-    """Return the default `BazaarBranchStore`."""
-    return BazaarBranchStore(
-        get_transport_from_url(config.codeimport.bazaar_branch_store))
-
-
-class CodeImportSourceDetails:
-    """The information needed to process an import.
-
-    As the worker doesn't talk to the database, we don't use
-    `CodeImport` objects for this.
-
-    The 'fromArguments' method builds an instance of this class from a form
-    of the information suitable for passing around on executables' command
-    lines.
-
-    :ivar target_id: The id of the Bazaar branch or the path of the Git
-        repository associated with this code import, used for locating the
-        existing import and the foreign tree.
-    :ivar rcstype: 'cvs', 'git', 'bzr-svn', 'bzr' as appropriate.
-    :ivar target_rcstype: 'bzr' or 'git' as appropriate.
-    :ivar url: The branch URL if rcstype in ['bzr-svn', 'git', 'bzr'], None
-        otherwise.
-    :ivar cvs_root: The $CVSROOT if rcstype == 'cvs', None otherwise.
-    :ivar cvs_module: The CVS module if rcstype == 'cvs', None otherwise.
-    :ivar stacked_on_url: The URL of the branch that the associated branch
-        is stacked on, if any.
-    :ivar macaroon: A macaroon granting authority to push to the target
-        repository if target_rcstype == 'git', None otherwise.
-    :ivar exclude_hosts: A list of host names from which we should not
-        mirror branches, or None.
-    """
-
-    def __init__(self, target_id, rcstype, target_rcstype, url=None,
-                 cvs_root=None, cvs_module=None, stacked_on_url=None,
-                 macaroon=None, exclude_hosts=None):
-        self.target_id = target_id
-        self.rcstype = rcstype
-        self.target_rcstype = target_rcstype
-        self.url = url
-        self.cvs_root = cvs_root
-        self.cvs_module = cvs_module
-        self.stacked_on_url = stacked_on_url
-        self.macaroon = macaroon
-        self.exclude_hosts = exclude_hosts
-
-    @classmethod
-    def fromArguments(cls, arguments):
-        """Convert command line-style arguments to an instance."""
-        # Keep this in sync with CodeImportJob.makeWorkerArguments.
-        parser = ArgumentParser(description='Code import worker.')
-        parser.add_argument(
-            'target_id', help='Target branch ID or repository unique name')
-        parser.add_argument(
-            'rcstype', choices=('bzr', 'bzr-svn', 'cvs', 'git'),
-            help='Source revision control system type')
-        parser.add_argument(
-            'target_rcstype', choices=('bzr', 'git'),
-            help='Target revision control system type')
-        parser.add_argument(
-            'url', help='Source URL (or $CVSROOT for rcstype cvs)')
-        parser.add_argument(
-            '--cvs-module', help='CVS module (only valid for rcstype cvs)')
-        parser.add_argument(
-            '--stacked-on',
-            help='Stacked-on URL (only valid for target_rcstype bzr)')
-        parser.add_argument(
-            '--macaroon', type=Macaroon.deserialize,
-            help=(
-                'Macaroon authorising push (only valid for target_rcstype '
-                'git)'))
-        parser.add_argument(
-            '--exclude-host', action='append', dest='exclude_hosts',
-            help='Refuse to mirror branches from this host')
-        args = parser.parse_args(arguments)
-        target_id = args.target_id
-        rcstype = args.rcstype
-        target_rcstype = args.target_rcstype
-        url = args.url if rcstype in ('bzr', 'bzr-svn', 'git') else None
-        if rcstype == 'cvs':
-            if args.cvs_module is None:
-                parser.error('rcstype cvs requires --cvs-module')
-            cvs_root = args.url
-            cvs_module = args.cvs_module
-        else:
-            cvs_root = None
-            cvs_module = None
-        if target_rcstype == 'bzr':
-            try:
-                target_id = int(target_id)
-            except ValueError:
-                parser.error(
-                    'rcstype bzr requires target_id to be an integer')
-            stacked_on_url = args.stacked_on
-            macaroon = None
-        elif target_rcstype == 'git':
-            if args.macaroon is None:
-                parser.error('target_rcstype git requires --macaroon')
-            stacked_on_url = None
-            macaroon = args.macaroon
-        return cls(
-            target_id, rcstype, target_rcstype, url, cvs_root, cvs_module,
-            stacked_on_url, macaroon, exclude_hosts=args.exclude_hosts)
-
-
-class ImportDataStore:
-    """A store for data associated with an import.
-
-    Import workers can store and retreive files into and from the store using
-    `put()` and `fetch()`.
-
-    So this store can find files stored by previous versions of this code, the
-    files are stored at ``<BRANCH ID IN HEX>.<EXT>`` where BRANCH ID comes
-    from the CodeImportSourceDetails used to construct the instance and EXT
-    comes from the local name passed to `put` or `fetch`.
-    """
-
-    def __init__(self, transport, source_details):
-        """Initialize an `ImportDataStore`.
-
-        :param transport: The transport files will be stored on.
-        :param source_details: The `CodeImportSourceDetails` object, used to
-            know where to store files on the remote transport.
-        """
-        self.source_details = source_details
-        self._transport = transport
-        self._target_id = source_details.target_id
-
-    def _getRemoteName(self, local_name):
-        """Convert `local_name` to the name used to store a file.
-
-        The algorithm is a little stupid for historical reasons: we chop off
-        the extension and stick that on the end of the branch id from the
-        source_details we were constructed with, in hex padded to 8
-        characters.  For example 'tree.tar.gz' might become '0000a23d.tar.gz'
-        or 'git.db' might become '00003e4.db'.
-
-        :param local_name: The local name of the file to be stored.
-        :return: The name to store the file as on the remote transport.
-        """
-        if '/' in local_name:
-            raise AssertionError("local_name must be a name, not a path")
-        dot_index = local_name.index('.')
-        if dot_index < 0:
-            raise AssertionError("local_name must have an extension.")
-        ext = local_name[dot_index:]
-        return '%08x%s' % (self._target_id, ext)
-
-    def fetch(self, filename, dest_transport=None):
-        """Retrieve `filename` from the store.
-
-        :param filename: The name of the file to retrieve (must be a filename,
-            not a path).
-        :param dest_transport: The transport to retrieve the file to,
-            defaulting to ``get_transport_from_path('.')``.
-        :return: A boolean, true if the file was found and retrieved, false
-            otherwise.
-        """
-        if dest_transport is None:
-            dest_transport = get_transport_from_path('.')
-        remote_name = self._getRemoteName(filename)
-        if self._transport.has(remote_name):
-            dest_transport.put_file(
-                filename, self._transport.get(remote_name))
-            return True
-        else:
-            return False
-
-    def put(self, filename, source_transport=None):
-        """Put `filename` into the store.
-
-        :param filename: The name of the file to store (must be a filename,
-            not a path).
-        :param source_transport: The transport to look for the file on,
-            defaulting to ``get_transport('.')``.
-        """
-        if source_transport is None:
-            source_transport = get_transport_from_path('.')
-        remote_name = self._getRemoteName(filename)
-        local_file = source_transport.get(filename)
-        self._transport.create_prefix()
-        try:
-            self._transport.put_file(remote_name, local_file)
-        finally:
-            local_file.close()
-
-
-class ForeignTreeStore:
-    """Manages retrieving and storing foreign working trees.
-
-    The code import system stores tarballs of CVS and SVN working trees on
-    another system. The tarballs are kept in predictable locations based on
-    the ID of the branch associated to the `CodeImport`.
-
-    The tarballs are all kept in one directory. The filename of a tarball is
-    XXXXXXXX.tar.gz, where 'XXXXXXXX' is the ID of the `CodeImport`'s branch
-    in hex.
-    """
-
-    def __init__(self, import_data_store):
-        """Construct a `ForeignTreeStore`.
-
-        :param transport: A writable transport that points to the base
-            directory where the tarballs are stored.
-        :ptype transport: `bzrlib.transport.Transport`.
-        """
-        self.import_data_store = import_data_store
-
-    def _getForeignTree(self, target_path):
-        """Return a foreign tree object for `target_path`."""
-        source_details = self.import_data_store.source_details
-        if source_details.rcstype == 'cvs':
-            return CVSWorkingTree(
-                source_details.cvs_root, source_details.cvs_module,
-                target_path)
-        else:
-            raise AssertionError(
-                "unknown RCS type: %r" % source_details.rcstype)
-
-    def archive(self, foreign_tree):
-        """Archive the foreign tree."""
-        local_name = 'foreign_tree.tar.gz'
-        create_tarball(foreign_tree.local_path, 'foreign_tree.tar.gz')
-        self.import_data_store.put(local_name)
-
-    def fetch(self, target_path):
-        """Fetch the foreign branch for `source_details` to `target_path`.
-
-        If there is no tarball archived for `source_details`, then try to
-        download (i.e. checkout) the foreign tree from its source repository,
-        generally on a third party server.
-        """
-        try:
-            return self.fetchFromArchive(target_path)
-        except NoSuchFile:
-            return self.fetchFromSource(target_path)
-
-    def fetchFromSource(self, target_path):
-        """Fetch the foreign tree for `source_details` to `target_path`."""
-        branch = self._getForeignTree(target_path)
-        branch.checkout()
-        return branch
-
-    def fetchFromArchive(self, target_path):
-        """Fetch the foreign tree for `source_details` from the archive."""
-        local_name = 'foreign_tree.tar.gz'
-        if not self.import_data_store.fetch(local_name):
-            raise NoSuchFile(local_name)
-        extract_tarball(local_name, target_path)
-        tree = self._getForeignTree(target_path)
-        tree.update()
-        return tree
-
-
-class ImportWorker:
-    """Oversees the actual work of a code import."""
-
-    def __init__(self, source_details, logger, opener_policy):
-        """Construct an `ImportWorker`.
-
-        :param source_details: A `CodeImportSourceDetails` object.
-        :param logger: A `Logger` to pass to cscvs.
-        :param opener_policy: Policy object that decides what branches can
-             be imported
-        """
-        self.source_details = source_details
-        self._logger = logger
-        self._opener_policy = opener_policy
-
-    def getWorkingDirectory(self):
-        """The directory we should change to and store all scratch files in.
-        """
-        base = config.codeimportworker.working_directory_root
-        dirname = 'worker-for-branch-%s' % self.source_details.target_id
-        return os.path.join(base, dirname)
-
-    def run(self):
-        """Run the code import job.
-
-        This is the primary public interface to the `ImportWorker`. This
-        method:
-
-         1. Retrieves an up-to-date foreign tree to import.
-         2. Gets the Bazaar branch to import into.
-         3. Imports the foreign tree into the Bazaar branch. If we've
-            already imported this before, we synchronize the imported Bazaar
-            branch with the latest changes to the foreign tree.
-         4. Publishes the newly-updated Bazaar branch, making it available to
-            Launchpad users.
-         5. Archives the foreign tree, so that we can update it quickly next
-            time.
-        """
-        working_directory = self.getWorkingDirectory()
-        if os.path.exists(working_directory):
-            shutil.rmtree(working_directory)
-        os.makedirs(working_directory)
-        saved_pwd = os.getcwd()
-        os.chdir(working_directory)
-        try:
-            return self._doImport()
-        finally:
-            shutil.rmtree(working_directory)
-            os.chdir(saved_pwd)
-
-    def _doImport(self):
-        """Perform the import.
-
-        :return: A CodeImportWorkerExitCode
-        """
-        raise NotImplementedError()
-
-
-class ToBzrImportWorker(ImportWorker):
-    """Oversees the actual work of a code import to Bazaar."""
-
-    # Where the Bazaar working tree will be stored.
-    BZR_BRANCH_PATH = 'bzr_branch'
-
-    # Should `getBazaarBranch` create a working tree?
-    needs_bzr_tree = True
-
-    required_format = BzrDirFormat.get_default_format()
-
-    def __init__(self, source_details, import_data_transport,
-                 bazaar_branch_store, logger, opener_policy):
-        """Construct a `ToBzrImportWorker`.
-
-        :param source_details: A `CodeImportSourceDetails` object.
-        :param bazaar_branch_store: A `BazaarBranchStore`. The import worker
-            uses this to fetch and store the Bazaar branches that are created
-            and updated during the import process.
-        :param logger: A `Logger` to pass to cscvs.
-        :param opener_policy: Policy object that decides what branches can
-             be imported
-        """
-        super(ToBzrImportWorker, self).__init__(
-            source_details, logger, opener_policy)
-        self.bazaar_branch_store = bazaar_branch_store
-        self.import_data_store = ImportDataStore(
-            import_data_transport, self.source_details)
-
-    def getBazaarBranch(self):
-        """Return the Bazaar `Branch` that we are importing into."""
-        if os.path.isdir(self.BZR_BRANCH_PATH):
-            shutil.rmtree(self.BZR_BRANCH_PATH)
-        return self.bazaar_branch_store.pull(
-            self.source_details.target_id, self.BZR_BRANCH_PATH,
-            self.required_format, self.needs_bzr_tree,
-            stacked_on_url=self.source_details.stacked_on_url)
-
-    def pushBazaarBranch(self, bazaar_branch, remote_branch=None):
-        """Push the updated Bazaar branch to the server.
-
-        :return: True if revisions were transferred.
-        """
-        return self.bazaar_branch_store.push(
-            self.source_details.target_id, bazaar_branch,
-            self.required_format,
-            stacked_on_url=self.source_details.stacked_on_url)
-
-
-class CSCVSImportWorker(ToBzrImportWorker):
-    """An ImportWorker for imports that use CSCVS.
-
-    As well as invoking cscvs to do the import, this class also needs to
-    manage a foreign working tree.
-    """
-
-    # Where the foreign working tree will be stored.
-    FOREIGN_WORKING_TREE_PATH = 'foreign_working_tree'
-
-    @cachedproperty
-    def foreign_tree_store(self):
-        return ForeignTreeStore(self.import_data_store)
-
-    def getForeignTree(self):
-        """Return the foreign branch object that we are importing from.
-
-        :return: A `CVSWorkingTree`.
-        """
-        if os.path.isdir(self.FOREIGN_WORKING_TREE_PATH):
-            shutil.rmtree(self.FOREIGN_WORKING_TREE_PATH)
-        os.mkdir(self.FOREIGN_WORKING_TREE_PATH)
-        return self.foreign_tree_store.fetch(self.FOREIGN_WORKING_TREE_PATH)
-
-    def importToBazaar(self, foreign_tree, bazaar_branch):
-        """Actually import `foreign_tree` into `bazaar_branch`.
-
-        :param foreign_tree: A `CVSWorkingTree`.
-        :param bazaar_tree: A `bzrlib.branch.Branch`, which must have a
-            colocated working tree.
-        """
-        foreign_directory = foreign_tree.local_path
-        bzr_directory = str(bazaar_branch.bzrdir.open_workingtree().basedir)
-
-        scm_branch = SCM.branch(bzr_directory)
-        last_commit = cscvs.findLastCscvsCommit(scm_branch)
-
-        # If branch in `bazaar_tree` doesn't have any identifiable CSCVS
-        # revisions, CSCVS "initializes" the branch.
-        if last_commit is None:
-            self._runToBaz(
-                foreign_directory, "-SI", "MAIN.1", bzr_directory)
-
-        # Now we synchronise the branch, that is, import all new revisions
-        # from the foreign branch into the Bazaar branch. If we've just
-        # initialized the Bazaar branch, then this means we import *all*
-        # revisions.
-        last_commit = cscvs.findLastCscvsCommit(scm_branch)
-        self._runToBaz(
-            foreign_directory, "-SC", "%s::" % last_commit, bzr_directory)
-
-    def _runToBaz(self, source_dir, flags, revisions, bazpath):
-        """Actually run the CSCVS utility that imports revisions.
-
-        :param source_dir: The directory containing the foreign working tree
-            that we are importing from.
-        :param flags: Flags to pass to `totla.totla`.
-        :param revisions: The revisions to import.
-        :param bazpath: The directory containing the Bazaar working tree that
-            we are importing into.
-        """
-        # XXX: JonathanLange 2008-02-08: We need better documentation for
-        # `flags` and `revisions`.
-        config = CVS.Config(source_dir)
-        config.args = ["--strict", "-b", bazpath,
-                       flags, revisions, bazpath]
-        totla.totla(config, self._logger, config.args, SCM.tree(source_dir))
-
-    def _doImport(self):
-        foreign_tree = self.getForeignTree()
-        bazaar_branch = self.getBazaarBranch()
-        self.importToBazaar(foreign_tree, bazaar_branch)
-        non_trivial = self.pushBazaarBranch(bazaar_branch)
-        self.foreign_tree_store.archive(foreign_tree)
-        if non_trivial:
-            return CodeImportWorkerExitCode.SUCCESS
-        else:
-            return CodeImportWorkerExitCode.SUCCESS_NOCHANGE
-
-
-class PullingImportWorker(ToBzrImportWorker):
-    """An import worker for imports that can be done by a bzr plugin.
-
-    Subclasses need to implement `probers`.
-    """
-
-    needs_bzr_tree = False
-
-    @property
-    def invalid_branch_exceptions(self):
-        """Exceptions that indicate no (valid) remote branch is present."""
-        raise NotImplementedError
-
-    @property
-    def unsupported_feature_exceptions(self):
-        """The exceptions to consider for unsupported features."""
-        raise NotImplementedError
-
-    @property
-    def broken_remote_exceptions(self):
-        """The exceptions to consider for broken remote branches."""
-        raise NotImplementedError
-
-    @property
-    def probers(self):
-        """The probers that should be tried for this import."""
-        raise NotImplementedError
-
-    def getRevisionLimit(self):
-        """Return maximum number of revisions to fetch (None for no limit).
-        """
-        return None
-
-    def _doImport(self):
-        self._logger.info("Starting job.")
-        saved_factory = bzrlib.ui.ui_factory
-        opener = BranchOpener(self._opener_policy, self.probers)
-        bzrlib.ui.ui_factory = LoggingUIFactory(logger=self._logger)
-        try:
-            self._logger.info(
-                "Getting exising bzr branch from central store.")
-            bazaar_branch = self.getBazaarBranch()
-            try:
-                remote_branch = opener.open(
-                    six.ensure_str(self.source_details.url))
-            except TooManyRedirections:
-                self._logger.info("Too many redirections.")
-                return CodeImportWorkerExitCode.FAILURE_INVALID
-            except NotBranchError:
-                self._logger.info("No branch found at remote location.")
-                return CodeImportWorkerExitCode.FAILURE_INVALID
-            except BadUrl as e:
-                self._logger.info("Invalid URL: %s" % e)
-                return CodeImportWorkerExitCode.FAILURE_FORBIDDEN
-            except ConnectionError as e:
-                self._logger.info("Unable to open remote branch: %s" % e)
-                return CodeImportWorkerExitCode.FAILURE_INVALID
-            try:
-                remote_branch_tip = remote_branch.last_revision()
-                inter_branch = InterBranch.get(remote_branch, bazaar_branch)
-                self._logger.info("Importing branch.")
-                revision_limit = self.getRevisionLimit()
-                inter_branch.fetch(limit=revision_limit)
-                if bazaar_branch.repository.has_revision(remote_branch_tip):
-                    pull_result = inter_branch.pull(overwrite=True)
-                    if pull_result.old_revid != pull_result.new_revid:
-                        result = CodeImportWorkerExitCode.SUCCESS
-                    else:
-                        result = CodeImportWorkerExitCode.SUCCESS_NOCHANGE
-                else:
-                    result = CodeImportWorkerExitCode.SUCCESS_PARTIAL
-            except Exception as e:
-                if e.__class__ in self.unsupported_feature_exceptions:
-                    self._logger.info(
-                        "Unable to import branch because of limitations in "
-                        "Bazaar.")
-                    self._logger.info(str(e))
-                    return (
-                        CodeImportWorkerExitCode.FAILURE_UNSUPPORTED_FEATURE)
-                elif e.__class__ in self.invalid_branch_exceptions:
-                    self._logger.info("Branch invalid: %s", str(e))
-                    return CodeImportWorkerExitCode.FAILURE_INVALID
-                elif e.__class__ in self.broken_remote_exceptions:
-                    self._logger.info("Remote branch broken: %s", str(e))
-                    return CodeImportWorkerExitCode.FAILURE_REMOTE_BROKEN
-                else:
-                    raise
-            self._logger.info("Pushing local import branch to central store.")
-            self.pushBazaarBranch(bazaar_branch, remote_branch=remote_branch)
-            self._logger.info("Job complete.")
-            return result
-        finally:
-            bzrlib.ui.ui_factory = saved_factory
-
-
-class GitImportWorker(PullingImportWorker):
-    """An import worker for Git imports.
-
-    The only behaviour we add is preserving the 'git.db' shamap between runs.
-    """
-
-    @property
-    def invalid_branch_exceptions(self):
-        return [
-            NoRepositoryPresent,
-            NotBranchError,
-            ConnectionError,
-        ]
-
-    @property
-    def unsupported_feature_exceptions(self):
-        from bzrlib.plugins.git.fetch import SubmodulesRequireSubtrees
-        return [
-            InvalidEntryName,
-            SubmodulesRequireSubtrees,
-        ]
-
-    @property
-    def broken_remote_exceptions(self):
-        return []
-
-    @property
-    def probers(self):
-        """See `PullingImportWorker.probers`."""
-        from bzrlib.plugins.git import (
-            LocalGitProber, RemoteGitProber)
-        return [LocalGitProber, RemoteGitProber]
-
-    def getRevisionLimit(self):
-        """See `PullingImportWorker.getRevisionLimit`."""
-        return config.codeimport.git_revisions_import_limit
-
-    def getBazaarBranch(self):
-        """See `ToBzrImportWorker.getBazaarBranch`.
-
-        In addition to the superclass' behaviour, we retrieve bzr-git's
-        caches, both legacy and modern, from the import data store and put
-        them where bzr-git will find them in the Bazaar tree, that is at
-        '.bzr/repository/git.db' and '.bzr/repository/git'.
-        """
-        branch = PullingImportWorker.getBazaarBranch(self)
-        # Fetch the legacy cache from the store, if present.
-        self.import_data_store.fetch(
-            'git.db', branch.repository._transport)
-        # The cache dir from newer bzr-gits is stored as a tarball.
-        local_name = 'git-cache.tar.gz'
-        if self.import_data_store.fetch(local_name):
-            repo_transport = branch.repository._transport
-            repo_transport.mkdir('git')
-            git_db_dir = os.path.join(
-                local_path_from_url(repo_transport.base), 'git')
-            extract_tarball(local_name, git_db_dir)
-        return branch
-
-    def pushBazaarBranch(self, bazaar_branch, remote_branch=None):
-        """See `ToBzrImportWorker.pushBazaarBranch`.
-
-        In addition to the superclass' behaviour, we store bzr-git's cache
-        directory at .bzr/repository/git in the import data store.
-        """
-        non_trivial = PullingImportWorker.pushBazaarBranch(
-            self, bazaar_branch)
-        repo_base = bazaar_branch.repository._transport.base
-        git_db_dir = os.path.join(local_path_from_url(repo_base), 'git')
-        local_name = 'git-cache.tar.gz'
-        create_tarball(git_db_dir, local_name)
-        self.import_data_store.put(local_name)
-        return non_trivial
-
-
-class BzrSvnImportWorker(PullingImportWorker):
-    """An import worker for importing Subversion via bzr-svn."""
-
-    @property
-    def invalid_branch_exceptions(self):
-        return [
-            NoRepositoryPresent,
-            NotBranchError,
-            ConnectionError,
-        ]
-
-    @property
-    def unsupported_feature_exceptions(self):
-        from bzrlib.plugins.svn.errors import InvalidFileName
-        return [
-            InvalidEntryName,
-            InvalidFileName,
-        ]
-
-    @property
-    def broken_remote_exceptions(self):
-        from bzrlib.plugins.svn.errors import IncompleteRepositoryHistory
-        return [IncompleteRepositoryHistory]
-
-    def getRevisionLimit(self):
-        """See `PullingImportWorker.getRevisionLimit`."""
-        return config.codeimport.svn_revisions_import_limit
-
-    @property
-    def probers(self):
-        """See `PullingImportWorker.probers`."""
-        from bzrlib.plugins.svn import SvnRemoteProber
-        return [SvnRemoteProber]
-
-    def getBazaarBranch(self):
-        """See `ToBzrImportWorker.getBazaarBranch`.
-
-        In addition to the superclass' behaviour, we retrieve bzr-svn's
-        cache from the import data store and put it where bzr-svn will find
-        it.
-        """
-        from bzrlib.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):
-            extract_tarball(local_name, create_cache_dir())
-        return branch
-
-    def pushBazaarBranch(self, bazaar_branch, remote_branch=None):
-        """See `ToBzrImportWorker.pushBazaarBranch`.
-
-        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
-        non_trivial = super(BzrSvnImportWorker, self).pushBazaarBranch(
-            bazaar_branch)
-        if remote_branch is not None:
-            cache = get_cache(remote_branch.repository.uuid)
-            cache_dir = cache.create_cache_dir()
-            local_name = 'svn-cache.tar.gz'
-            create_tarball(
-                os.path.dirname(cache_dir), local_name,
-                filenames=[os.path.basename(cache_dir)])
-            self.import_data_store.put(local_name)
-            # XXX cjwatson 2019-02-06: Once this is behaving well on
-            # production, consider removing the local cache after pushing a
-            # copy of it to the import data store.
-        return non_trivial
-
-
-class BzrImportWorker(PullingImportWorker):
-    """An import worker for importing Bazaar branches."""
-
-    invalid_branch_exceptions = [
-        NotBranchError,
-        ConnectionError,
-        ]
-    unsupported_feature_exceptions = []
-    broken_remote_exceptions = []
-
-    def getRevisionLimit(self):
-        """See `PullingImportWorker.getRevisionLimit`."""
-        # For now, just grab the whole branch at once.
-        # bzr does support fetch(limit=) but it isn't very efficient at
-        # the moment.
-        return None
-
-    @property
-    def probers(self):
-        """See `PullingImportWorker.probers`."""
-        from bzrlib.bzrdir import BzrProber, RemoteBzrProber
-        return [BzrProber, RemoteBzrProber]
-
-
-class GitToGitImportWorker(ImportWorker):
-    """An import worker for imports from Git to Git."""
-
-    def _throttleProgress(self, file_obj, timeout=15.0):
-        """Throttle progress messages from a file object.
-
-        git can produce progress output on stderr, but it produces rather a
-        lot of it and we don't want it all to end up in logs.  Throttle this
-        so that we only produce output every `timeout` seconds, or when we
-        see a line terminated with a newline rather than a carriage return.
-
-        :param file_obj: a file-like object opened in binary mode.
-        :param timeout: emit progress output only after this many seconds
-            have elapsed.
-        :return: an iterator of interesting text lines read from the file.
-        """
-        # newline="" requests universal newlines mode, but without
-        # translation.
-        if six.PY2 and isinstance(file_obj, file):
-            # A Python 2 file object can't be used directly to construct an
-            # io.TextIOWrapper.
-            class _ReadableFileWrapper:
-                def __init__(self, raw):
-                    self._raw = raw
-
-                def __enter__(self):
-                    return self
-
-                def __exit__(self, exc_type, exc_value, exc_tb):
-                    pass
-
-                def readable(self):
-                    return True
-
-                def writable(self):
-                    return False
-
-                def seekable(self):
-                    return True
-
-                def __getattr__(self, name):
-                    return getattr(self._raw, name)
-
-            with _ReadableFileWrapper(file_obj) as readable:
-                with io.BufferedReader(readable) as buffered:
-                    for line in self._throttleProgress(
-                            buffered, timeout=timeout):
-                        yield line
-                    return
-
-        class ReceivedAlarm(Exception):
-            pass
-
-        def alarm_handler(signum, frame):
-            raise ReceivedAlarm()
-
-        old_alarm = signal.signal(signal.SIGALRM, alarm_handler)
-        try:
-            progress_buffer = None
-            with io.TextIOWrapper(
-                    file_obj, encoding="UTF-8", errors="replace",
-                    newline="") as wrapped_file:
-                while True:
-                    try:
-                        signal.setitimer(signal.ITIMER_REAL, timeout)
-                        line = next(wrapped_file)
-                        signal.setitimer(signal.ITIMER_REAL, 0)
-                        if line.endswith(u"\r"):
-                            progress_buffer = line
-                        else:
-                            if progress_buffer is not None:
-                                yield progress_buffer
-                                progress_buffer = None
-                            yield line
-                    except ReceivedAlarm:
-                        if progress_buffer is not None:
-                            yield progress_buffer
-                            progress_buffer = None
-                    except StopIteration:
-                        break
-        finally:
-            signal.setitimer(signal.ITIMER_REAL, 0)
-            signal.signal(signal.SIGALRM, old_alarm)
-
-    def _runGit(self, *args, **kwargs):
-        """Run git with arguments, sending output to the logger."""
-        cmd = ["git"] + list(args)
-        git_process = subprocess.Popen(
-            cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs)
-        for line in self._throttleProgress(git_process.stdout):
-            self._logger.info(sanitise_urls(line.rstrip("\r\n")))
-        retcode = git_process.wait()
-        if retcode:
-            raise subprocess.CalledProcessError(retcode, cmd)
-
-    def _getHead(self, repository, remote_name):
-        """Get HEAD from a configured remote in a local repository.
-
-        The returned ref name will be adjusted in such a way that it can be
-        passed to `_setHead` (e.g. refs/remotes/origin/master ->
-        refs/heads/master).
-        """
-        # This is a bit weird, but set-head will bail out if the target
-        # doesn't exist in the correct remotes namespace.  git 2.8.0 has
-        # "git ls-remote --symref <repository> HEAD" which would involve
-        # less juggling.
-        self._runGit(
-            "fetch", "-q", ".", "refs/heads/*:refs/remotes/%s/*" % remote_name,
-            cwd=repository)
-        self._runGit(
-            "remote", "set-head", remote_name, "--auto", cwd=repository)
-        ref_prefix = "refs/remotes/%s/" % remote_name
-        target_ref = subprocess.check_output(
-            ["git", "symbolic-ref", ref_prefix + "HEAD"],
-            cwd=repository, universal_newlines=True).rstrip("\n")
-        if not target_ref.startswith(ref_prefix):
-            raise GitProtocolError(
-                "'git remote set-head %s --auto' did not leave remote HEAD "
-                "under %s" % (remote_name, ref_prefix))
-        real_target_ref = "refs/heads/" + target_ref[len(ref_prefix):]
-        # Ensure the result is a valid ref name, just in case.
-        self._runGit("check-ref-format", real_target_ref, cwd="repository")
-        return real_target_ref
-
-    def _setHead(self, target_url, target_ref):
-        """Set HEAD on a remote repository.
-
-        This relies on the turnip-set-symbolic-ref extension.
-        """
-        service = "turnip-set-symbolic-ref"
-        url = urljoin(target_url, service)
-        headers = {
-            "Content-Type": "application/x-%s-request" % service,
-            }
-        body = pkt_line("HEAD %s" % target_ref) + pkt_line(None)
-        try:
-            response = urlfetch(url, method="POST", headers=headers, data=body)
-            raise_for_status_redacted(response)
-        except Exception as e:
-            raise GitProtocolError(str(e))
-        content_type = response.headers.get("Content-Type")
-        if content_type != ("application/x-%s-result" % service):
-            raise GitProtocolError(
-                "Invalid Content-Type from server: %s" % content_type)
-        content = io.BytesIO(response.content)
-        proto = Protocol(content.read, None)
-        pkt = proto.read_pkt_line()
-        if pkt is None:
-            raise GitProtocolError("Unexpected flush-pkt from server")
-        elif pkt.rstrip(b"\n") == b"ACK HEAD":
-            pass
-        elif pkt.startswith(b"ERR "):
-            raise GitProtocolError(
-                pkt[len(b"ERR "):].rstrip(b"\n").decode("UTF-8"))
-        else:
-            raise GitProtocolError("Unexpected packet %r from server" % pkt)
-
-    def _deleteRefs(self, repository, pattern):
-        """Delete all refs in `repository` matching `pattern`."""
-        # XXX cjwatson 2016-11-08: We might ideally use something like:
-        # "git for-each-ref --format='delete %(refname)%00%(objectname)%00' \
-        #   <pattern> | git update-ref --stdin -z
-        # ... which would be faster, but that requires git 1.8.5.
-        remote_refs = subprocess.check_output(
-            ["git", "for-each-ref", "--format=%(refname)", pattern],
-            cwd="repository").splitlines()
-        for remote_ref in remote_refs:
-            self._runGit("update-ref", "-d", remote_ref, cwd="repository")
-
-    def _doImport(self):
-        self._logger.info("Starting job.")
-        try:
-            self._opener_policy.check_one_url(self.source_details.url)
-        except BadUrl as e:
-            self._logger.info("Invalid URL: %s" % e)
-            return CodeImportWorkerExitCode.FAILURE_FORBIDDEN
-        unauth_target_url = urljoin(
-            config.codehosting.git_browse_root, self.source_details.target_id)
-        split = urlsplit(unauth_target_url)
-        target_netloc = ":%s@%s" % (
-            self.source_details.macaroon.serialize(), split.hostname)
-        if split.port:
-            target_netloc += ":%s" % split.port
-        target_url = urlunsplit([
-            split.scheme, target_netloc, split.path, "", ""])
-        # XXX cjwatson 2016-10-11: Ideally we'd put credentials in a
-        # credentials store instead.  However, git only accepts credentials
-        # that have both a non-empty username and a non-empty password.
-        self._logger.info("Getting existing repository from hosting service.")
-        try:
-            self._runGit("clone", "--mirror", target_url, "repository")
-        except subprocess.CalledProcessError as e:
-            self._logger.info(
-                "Unable to get existing repository from hosting service: "
-                "git clone exited %s" % e.returncode)
-            return CodeImportWorkerExitCode.FAILURE
-        self._logger.info("Fetching remote repository.")
-        try:
-            self._runGit("config", "gc.auto", "0", cwd="repository")
-            # Remove any stray remote-tracking refs from the last time round.
-            self._deleteRefs("repository", "refs/remotes/source/**")
-            self._runGit(
-                "remote", "add", "source", self.source_details.url,
-                cwd="repository")
-            self._runGit(
-                "fetch", "--prune", "source", "+refs/*:refs/*",
-                cwd="repository")
-            try:
-                new_head = self._getHead("repository", "source")
-            except (subprocess.CalledProcessError, GitProtocolError) as e2:
-                self._logger.info("Unable to fetch default branch: %s" % e2)
-                new_head = None
-            self._runGit("remote", "rm", "source", cwd="repository")
-            # XXX cjwatson 2016-11-03: For some reason "git remote rm"
-            # doesn't actually remove the refs.
-            self._deleteRefs("repository", "refs/remotes/source/**")
-        except subprocess.CalledProcessError as e:
-            self._logger.info("Unable to fetch remote repository: %s" % e)
-            return CodeImportWorkerExitCode.FAILURE_INVALID
-        self._logger.info("Pushing repository to hosting service.")
-        try:
-            if new_head is not None:
-                # Push the target of HEAD first to ensure that it is always
-                # available.
-                self._runGit(
-                    "push", "--progress", target_url,
-                    "+%s:%s" % (new_head, new_head), cwd="repository")
-                try:
-                    self._setHead(target_url, new_head)
-                except GitProtocolError as e:
-                    self._logger.info("Unable to set default branch: %s" % e)
-            self._runGit(
-                "push", "--progress", "--mirror", target_url, cwd="repository")
-        except subprocess.CalledProcessError as e:
-            self._logger.info(
-                "Unable to push to hosting service: git push exited %s" %
-                e.returncode)
-            return CodeImportWorkerExitCode.FAILURE
-        return CodeImportWorkerExitCode.SUCCESS
diff --git a/lib/lp/codehosting/codeimport/workermonitor.py b/lib/lp/codehosting/codeimport/workermonitor.py
deleted file mode 100644
index 6fa304b..0000000
--- a/lib/lp/codehosting/codeimport/workermonitor.py
+++ /dev/null
@@ -1,252 +0,0 @@
-# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Code to talk to the database about what the worker script is doing."""
-
-from __future__ import absolute_import, print_function
-
-__metaclass__ = type
-__all__ = []
-
-
-import os
-import tempfile
-
-import six
-from six.moves import xmlrpc_client
-from twisted.internet import (
-    defer,
-    error,
-    reactor,
-    task,
-    )
-from twisted.python import failure
-from twisted.web import xmlrpc
-
-from lp.code.enums import CodeImportResultStatus
-from lp.codehosting.codeimport.worker import CodeImportWorkerExitCode
-from lp.services.config import config
-from lp.services.twistedsupport.processmonitor import (
-    ProcessMonitorProtocolWithTimeout,
-    )
-from lp.xmlrpc.faults import NoSuchCodeImportJob
-
-
-class CodeImportWorkerMonitorProtocol(ProcessMonitorProtocolWithTimeout):
-    """The protocol by which the child process talks to the monitor.
-
-    In terms of bytes, the protocol is extremely simple: any output is stored
-    in the log file and seen as timeout-resetting activity.  Every
-    config.codeimportworker.heartbeat_update_interval seconds we ask the
-    monitor to update the heartbeat of the job we are working on and pass the
-    tail of the log output.
-    """
-
-    def __init__(self, deferred, worker_monitor, log_file, clock=None):
-        """Construct an instance.
-
-        :param deferred: See `ProcessMonitorProtocol.__init__` -- the deferred
-            that will be fired when the process has exited.
-        :param worker_monitor: A `CodeImportWorkerMonitor` instance.
-        :param log_file: A file object that the output of the child
-            process will be logged to.
-        :param clock: A provider of Twisted's IReactorTime.  This parameter
-            exists to allow testing that does not depend on an external clock.
-            If a clock is not passed in explicitly the reactor is used.
-        """
-        ProcessMonitorProtocolWithTimeout.__init__(
-            self, deferred, clock=clock,
-            timeout=config.codeimport.worker_inactivity_timeout)
-        self.worker_monitor = worker_monitor
-        self._tail = b''
-        self._log_file = log_file
-        self._looping_call = task.LoopingCall(self._updateHeartbeat)
-        self._looping_call.clock = self._clock
-
-    def connectionMade(self):
-        """See `BaseProtocol.connectionMade`.
-
-        We call updateHeartbeat for the first time when we are connected to
-        the process and every
-        config.codeimportworker.heartbeat_update_interval seconds thereafter.
-        """
-        ProcessMonitorProtocolWithTimeout.connectionMade(self)
-        self._looping_call.start(
-            config.codeimportworker.heartbeat_update_interval)
-
-    def _updateHeartbeat(self):
-        """Ask the monitor to update the heartbeat.
-
-        We use runNotification() to serialize the updates and ensure
-        that any errors are handled properly.  We do not return the
-        deferred, as we want this function to be called at a frequency
-        independent of how long it takes to update the heartbeat."""
-        self.runNotification(
-            self.worker_monitor.updateHeartbeat, self._tail)
-
-    def outReceived(self, data):
-        """See `ProcessProtocol.outReceived`.
-
-        Any output resets the timeout, is stored in the logfile and
-        updates the tail of the log.
-        """
-        self.resetTimeout()
-        self._log_file.write(data)
-        self._tail = b'\n'.join((self._tail + data).split(b'\n')[-5:])
-
-    errReceived = outReceived
-
-    def processEnded(self, reason):
-        """See `ProcessMonitorProtocolWithTimeout.processEnded`.
-
-        We stop updating the heartbeat when the process exits.
-        """
-        ProcessMonitorProtocolWithTimeout.processEnded(self, reason)
-        self._looping_call.stop()
-
-
-class ExitQuietly(Exception):
-    """Raised to indicate that we should abort and exit without fuss.
-
-    Raised when the job we are working on disappears, as we assume
-    this is the result of the job being killed or reclaimed.
-    """
-    pass
-
-
-class CodeImportWorkerMonitor:
-    """Controller for a single import job.
-
-    An instance of `CodeImportWorkerMonitor` runs a child process to
-    perform an import and communicates status to the database.
-    """
-
-    path_to_script = os.path.join(
-        config.root, 'scripts', 'code-import-worker.py')
-
-    def __init__(self, job_id, logger, codeimport_endpoint, access_policy):
-        """Construct an instance.
-
-        :param job_id: The ID of the CodeImportJob we are to work on.
-        :param logger: A `Logger` object.
-        """
-        self._job_id = job_id
-        self._logger = logger
-        self.codeimport_endpoint = codeimport_endpoint
-        self._call_finish_job = True
-        self._log_file = tempfile.TemporaryFile()
-        self._access_policy = access_policy
-
-    def _trap_nosuchcodeimportjob(self, failure):
-        failure.trap(xmlrpc.Fault)
-        if failure.value.faultCode == NoSuchCodeImportJob.error_code:
-            self._call_finish_job = False
-            raise ExitQuietly
-        else:
-            raise failure.value
-
-    def getWorkerArguments(self):
-        """Get arguments for the worker for the import we are working on."""
-        deferred = self.codeimport_endpoint.callRemote(
-            'getImportDataForJobID', self._job_id)
-
-        def _processResult(result):
-            code_import_arguments = result['arguments']
-            self._logger.info(
-                'Found source details: %s', code_import_arguments)
-            return code_import_arguments
-        return deferred.addCallbacks(
-            _processResult, self._trap_nosuchcodeimportjob)
-
-    def updateHeartbeat(self, tail):
-        """Call the updateHeartbeat method for the job we are working on."""
-        self._logger.debug("Updating heartbeat.")
-        # The log tail is really bytes, but it's stored in the database as a
-        # text column, so it's easiest to convert it to text now; passing
-        # text over XML-RPC requires less boilerplate than bytes anyway.
-        deferred = self.codeimport_endpoint.callRemote(
-            'updateHeartbeat', self._job_id,
-            six.ensure_text(tail, errors='replace'))
-        return deferred.addErrback(self._trap_nosuchcodeimportjob)
-
-    def finishJob(self, status):
-        """Call the finishJobID method for the job we are working on."""
-        self._log_file.seek(0)
-        return self.codeimport_endpoint.callRemote(
-            'finishJobID', self._job_id, status.name,
-            xmlrpc_client.Binary(self._log_file.read())
-            ).addErrback(self._trap_nosuchcodeimportjob)
-
-    def _makeProcessProtocol(self, deferred):
-        """Make an `CodeImportWorkerMonitorProtocol` for a subprocess."""
-        return CodeImportWorkerMonitorProtocol(deferred, self, self._log_file)
-
-    def _launchProcess(self, worker_arguments):
-        """Launch the code-import-worker.py child process."""
-        deferred = defer.Deferred()
-        protocol = self._makeProcessProtocol(deferred)
-        interpreter = '%s/bin/py' % config.root
-        args = [interpreter, self.path_to_script]
-        if self._access_policy is not None:
-            args.append("--access-policy=%s" % self._access_policy)
-        args.append('--')
-        command = args + worker_arguments
-        self._logger.info(
-            "Launching worker child process %s.", command)
-        reactor.spawnProcess(
-            protocol, interpreter, command, env=os.environ, usePTY=True)
-        return deferred
-
-    def run(self):
-        """Perform the import."""
-        return self.getWorkerArguments().addCallback(
-            self._launchProcess).addBoth(
-            self.callFinishJob).addErrback(
-            self._silenceQuietExit)
-
-    def _silenceQuietExit(self, failure):
-        """Quietly swallow a ExitQuietly failure."""
-        failure.trap(ExitQuietly)
-        return None
-
-    def _reasonToStatus(self, reason):
-        """Translate the 'reason' for process exit into a result status.
-
-        Different exit codes are presumed by Twisted to be errors, but are
-        different kinds of success for us.
-        """
-        exit_code_map = {
-            CodeImportWorkerExitCode.SUCCESS_NOCHANGE:
-                CodeImportResultStatus.SUCCESS_NOCHANGE,
-            CodeImportWorkerExitCode.SUCCESS_PARTIAL:
-                CodeImportResultStatus.SUCCESS_PARTIAL,
-            CodeImportWorkerExitCode.FAILURE_UNSUPPORTED_FEATURE:
-                CodeImportResultStatus.FAILURE_UNSUPPORTED_FEATURE,
-            CodeImportWorkerExitCode.FAILURE_INVALID:
-                CodeImportResultStatus.FAILURE_INVALID,
-            CodeImportWorkerExitCode.FAILURE_FORBIDDEN:
-                CodeImportResultStatus.FAILURE_FORBIDDEN,
-            CodeImportWorkerExitCode.FAILURE_REMOTE_BROKEN:
-                CodeImportResultStatus.FAILURE_REMOTE_BROKEN,
-                }
-        if isinstance(reason, failure.Failure):
-            if reason.check(error.ProcessTerminated):
-                return exit_code_map.get(reason.value.exitCode,
-                    CodeImportResultStatus.FAILURE)
-            return CodeImportResultStatus.FAILURE
-        else:
-            return CodeImportResultStatus.SUCCESS
-
-    def callFinishJob(self, reason):
-        """Call finishJob() with the appropriate status."""
-        if not self._call_finish_job:
-            return reason
-        status = self._reasonToStatus(reason)
-        if status == CodeImportResultStatus.FAILURE:
-            self._log_file.write(b"Import failed:\n")
-            self._log_file.write(reason.getTraceback().encode("UTF-8"))
-            self._logger.info(
-                "Import failed: %s: %s" % (reason.type, reason.value))
-        else:
-            self._logger.info('Import succeeded.')
-        return self.finishJob(status)
diff --git a/lib/lp/codehosting/puller/worker.py b/lib/lp/codehosting/puller/worker.py
index c0b426f..7dd7073 100644
--- a/lib/lp/codehosting/puller/worker.py
+++ b/lib/lp/codehosting/puller/worker.py
@@ -495,8 +495,7 @@ class PullerWorkerUIFactory(SilentUIFactory):
     def confirm_action(self, prompt, confirmation_id, args):
         """If we're asked to break a lock like a stale lock of ours, say yes.
         """
-        if confirmation_id not in (
-                'bzrlib.lockdir.break', 'breezy.lockdir.break'):
+        if confirmation_id != 'breezy.lockdir.break':
             raise AssertionError(
                 "Didn't expect confirmation id %r" % (confirmation_id,))
         branch_id = self.puller_worker_protocol.branch_id
diff --git a/lib/lp/codehosting/tests/helpers.py b/lib/lp/codehosting/tests/helpers.py
index 295acaa..81fbea1 100644
--- a/lib/lp/codehosting/tests/helpers.py
+++ b/lib/lp/codehosting/tests/helpers.py
@@ -16,6 +16,7 @@ __all__ = [
 
 import os
 
+from breezy.controldir import ControlDir
 from breezy.errors import FileExists
 from breezy.plugins.loom import branch as loom_branch
 from breezy.tests import (
@@ -86,13 +87,10 @@ class LoomTestMixin:
 
 def create_branch_with_one_revision(branch_dir, format=None):
     """Create a dummy 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
     if not os.path.exists(branch_dir):
         os.makedirs(branch_dir)
     try:
-        tree = BzrDir.create_standalone_workingtree(branch_dir, format)
+        tree = ControlDir.create_standalone_workingtree(branch_dir, format)
     except FileExists:
         return
     f = open(os.path.join(branch_dir, 'hello'), 'w')
diff --git a/lib/lp/scripts/utilities/test.py b/lib/lp/scripts/utilities/test.py
index 644c496..3d6753f 100755
--- a/lib/lp/scripts/utilities/test.py
+++ b/lib/lp/scripts/utilities/test.py
@@ -120,9 +120,6 @@ def filter_warnings():
     warnings.filterwarnings(
         'ignore', 'twisted.python.plugin', DeprecationWarning,
         )
-    warnings.filterwarnings(
-        'ignore', 'bzrlib.*was deprecated', DeprecationWarning,
-        )
     # XXX cjwatson 2019-10-18: This can be dropped once the port to Breezy
     # is complete.
     warnings.filterwarnings(
diff --git a/lib/lp/testing/__init__.py b/lib/lp/testing/__init__.py
index b9f7c58..55759e8 100644
--- a/lib/lp/testing/__init__.py
+++ b/lib/lp/testing/__init__.py
@@ -179,9 +179,6 @@ from lp.testing.fixture import (
 from lp.testing.karma import KarmaRecorder
 from lp.testing.mail_helpers import pop_notifications
 
-if six.PY2:
-    from bzrlib import trace as bzr_trace
-
 # The following names have been imported for the purpose of being
 # exported. They are referred to here to silence lint warnings.
 admin_logged_in
@@ -842,8 +839,6 @@ class TestCaseWithFactory(TestCase):
         # make it so in order to avoid "No handlers for "brz" logger'
         # messages.
         trace._brz_logger = logging.getLogger('brz')
-        if six.PY2:
-            bzr_trace._bzr_logger = logging.getLogger('bzr')
 
     def getUserBrowser(self, url=None, user=None):
         """Return a Browser logged in as a fresh user, maybe opened at `url`.
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index 685a142..7d60973 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -520,44 +520,6 @@ class ObjectFactory(
         epoch = datetime(2009, 1, 1, tzinfo=pytz.UTC)
         return epoch + timedelta(minutes=self.getUniqueInteger())
 
-    def makeCodeImportSourceDetails(self, target_id=None, rcstype=None,
-                                    target_rcstype=None, url=None,
-                                    cvs_root=None, cvs_module=None,
-                                    stacked_on_url=None, macaroon=None):
-        if not six.PY2:
-            raise NotImplementedError(
-                "Code imports do not yet work on Python 3.")
-
-        # XXX cjwatson 2020-08-07: Move this back up to module level once
-        # codeimport has been ported to Breezy.
-        from lp.codehosting.codeimport.worker import CodeImportSourceDetails
-
-        if target_id is None:
-            target_id = self.getUniqueInteger()
-        if rcstype is None:
-            rcstype = 'bzr-svn'
-        if target_rcstype is None:
-            target_rcstype = 'bzr'
-        if rcstype in ['bzr-svn', 'bzr']:
-            assert cvs_root is cvs_module is None
-            if url is None:
-                url = self.getUniqueURL()
-        elif rcstype == 'cvs':
-            assert url is None
-            if cvs_root is None:
-                cvs_root = self.getUniqueUnicode()
-            if cvs_module is None:
-                cvs_module = self.getUniqueUnicode()
-        elif rcstype == 'git':
-            assert cvs_root is cvs_module is None
-            if url is None:
-                url = self.getUniqueURL(scheme='git')
-        else:
-            raise AssertionError("Unknown rcstype %r." % rcstype)
-        return CodeImportSourceDetails(
-            target_id, rcstype, target_rcstype, url, cvs_root, cvs_module,
-            stacked_on_url=stacked_on_url, macaroon=macaroon)
-
 
 class BareLaunchpadObjectFactory(ObjectFactory):
     """Factory methods for creating Launchpad objects.
diff --git a/requirements/launchpad.txt b/requirements/launchpad.txt
index c80a759..d280824 100644
--- a/requirements/launchpad.txt
+++ b/requirements/launchpad.txt
@@ -21,8 +21,6 @@ breezy==3.0.1
 bson==0.5.9
 boto3==1.16.2
 botocore==1.19.2
-# lp:~launchpad/bzr/lp
-bzr==2.6.0.lp.4
 celery==4.1.1
 Chameleon==3.6.2
 configobj==5.0.6
@@ -146,7 +144,6 @@ statsd==3.3.0
 # lp:~launchpad-committers/storm/lp
 storm==0.24+lp416
 subprocess32==3.2.6
-subvertpy==0.9.1
 tenacity==6.1.0
 testresources==0.2.7
 testscenarios==0.4
diff --git a/scripts/code-import-worker-monitor.py b/scripts/code-import-worker-monitor.py
deleted file mode 100755
index a505225..0000000
--- a/scripts/code-import-worker-monitor.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/python2 -S
-#
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""When passed a CodeImportJob id on the command line, process that job.
-
-The actual work of processing a job is done by the code-import-worker.py
-script which this process runs as a child process and updates the database on
-its progress and result.
-
-This script is usually run by the code-import-dispatcher cronscript.
-"""
-
-__metaclass__ = type
-
-
-import _pythonpath
-
-import os
-
-from twisted.internet import (
-    defer,
-    reactor,
-    )
-from twisted.python import log
-from twisted.web import xmlrpc
-
-from lp.codehosting.codeimport.workermonitor import CodeImportWorkerMonitor
-from lp.services.config import config
-from lp.services.scripts.base import LaunchpadScript
-from lp.services.twistedsupport.loggingsupport import set_up_oops_reporting
-
-
-class CodeImportWorker(LaunchpadScript):
-
-    def __init__(self, name, dbuser=None, test_args=None):
-        LaunchpadScript.__init__(self, name, dbuser, test_args)
-        # The logfile changes its name according to the code in
-        # CodeImportDispatcher, so we pull it from the command line
-        # options.
-        set_up_oops_reporting(
-            self.name, 'codeimportworker', logfile=self.options.log_file)
-
-    def add_my_options(self):
-        """See `LaunchpadScript`."""
-        self.parser.add_option(
-            "--access-policy", type="choice", metavar="ACCESS_POLICY",
-            choices=["anything", "default"], default=None)
-
-    def _init_db(self, isolation):
-        # This script doesn't access the database.
-        pass
-
-    def main(self):
-        arg, = self.args
-        job_id = int(arg)
-        # XXX: MichaelHudson 2008-05-07 bug=227586: Setting up the component
-        # architecture overrides $GNUPGHOME to something stupid.
-        os.environ['GNUPGHOME'] = ''
-        reactor.callWhenRunning(self._do_import, job_id)
-        reactor.run()
-
-    def _do_import(self, job_id):
-        defer.maybeDeferred(self._main, job_id).addErrback(
-            log.err).addCallback(
-            lambda ignored: reactor.stop())
-
-    def _main(self, job_id):
-        worker = CodeImportWorkerMonitor(
-            job_id, self.logger,
-            xmlrpc.Proxy(
-                config.codeimportdispatcher.codeimportscheduler_url.encode(
-                    'UTF-8')),
-            self.options.access_policy)
-        return worker.run()
-
-if __name__ == '__main__':
-    script = CodeImportWorker('codeimportworker')
-    script.run()
diff --git a/scripts/code-import-worker.py b/scripts/code-import-worker.py
deleted file mode 100755
index 434b449..0000000
--- a/scripts/code-import-worker.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/usr/bin/python2 -S
-#
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Process a code import described by the command line arguments.
-
-By 'processing a code import' we mean importing or updating code from a
-remote, non-Bazaar, repository.
-
-This script is usually run by the code-import-worker-monitor.py script that
-communicates progress and results to the database.
-"""
-
-__metaclass__ = type
-
-
-import _pythonpath
-
-from optparse import OptionParser
-import sys
-
-from bzrlib.transport import get_transport
-from bzrlib.url_policy_open import AcceptAnythingPolicy
-
-from lp.codehosting.codeimport.worker import (
-    BzrImportWorker,
-    BzrSvnImportWorker,
-    CodeImportBranchOpenPolicy,
-    CodeImportSourceDetails,
-    CSCVSImportWorker,
-    get_default_bazaar_branch_store,
-    GitImportWorker,
-    GitToGitImportWorker,
-    )
-from lp.services import scripts
-from lp.services.config import config
-from lp.services.timeout import set_default_timeout_function
-
-
-opener_policies = {
-    "anything": (
-        lambda rcstype, target_rcstype, exclude_hosts=None:
-        AcceptAnythingPolicy()),
-    "default": CodeImportBranchOpenPolicy,
-    }
-
-
-def force_bzr_to_use_urllib():
-    """Prevent bzr from using pycurl to connect to http: urls.
-
-    We want this because pycurl rejects self signed certificates, which
-    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
-    register_lazy_transport('http://', 'bzrlib.transport.http._urllib',
-                            'HttpTransport_urllib')
-    register_lazy_transport('https://', 'bzrlib.transport.http._urllib',
-                            'HttpTransport_urllib')
-
-
-class CodeImportWorker:
-
-    def __init__(self):
-        parser = OptionParser()
-        scripts.logger_options(parser)
-        parser.add_option(
-            "--access-policy", type="choice", metavar="ACCESS_POLICY",
-            choices=["anything", "default"], default="default",
-            help="Access policy to use when accessing branches to import.")
-        self.options, self.args = parser.parse_args()
-        self.logger = scripts.logger(self.options, 'code-import-worker')
-
-    def main(self):
-        force_bzr_to_use_urllib()
-        set_default_timeout_function(lambda: 60.0)
-        source_details = CodeImportSourceDetails.fromArguments(self.args)
-        if source_details.rcstype == 'git':
-            if source_details.target_rcstype == 'bzr':
-                import_worker_cls = GitImportWorker
-            else:
-                import_worker_cls = GitToGitImportWorker
-        elif source_details.rcstype == 'bzr-svn':
-            import_worker_cls = BzrSvnImportWorker
-        elif source_details.rcstype == 'bzr':
-            import_worker_cls = BzrImportWorker
-        elif source_details.rcstype == 'cvs':
-            import_worker_cls = CSCVSImportWorker
-        else:
-            raise AssertionError(
-                'unknown rcstype %r' % source_details.rcstype)
-        opener_policy = opener_policies[self.options.access_policy](
-            source_details.rcstype, source_details.target_rcstype,
-            exclude_hosts=source_details.exclude_hosts)
-        if source_details.target_rcstype == 'bzr':
-            import_worker = import_worker_cls(
-                source_details,
-                get_transport(config.codeimport.foreign_tree_store),
-                get_default_bazaar_branch_store(), self.logger, opener_policy)
-        else:
-            import_worker = import_worker_cls(
-                source_details, self.logger, opener_policy)
-        return import_worker.run()
-
-
-if __name__ == '__main__':
-    script = CodeImportWorker()
-    sys.exit(script.main())
diff --git a/setup.py b/setup.py
index de4fafa..b5696f3 100644
--- a/setup.py
+++ b/setup.py
@@ -155,10 +155,6 @@ setup(
         'beautifulsoup4[lxml]',
         'boto3',
         'breezy',
-        # XXX cjwatson 2020-08-07: This should eventually be removed
-        # entirely, but we need to retain it until codeimport has been
-        # ported to Breezy.
-        'bzr; python_version < "3"',
         'celery',
         'contextlib2; python_version < "3.3"',
         'cssselect',
@@ -237,9 +233,6 @@ setup(
         'Sphinx',
         'statsd',
         'storm',
-        # XXX cjwatson 2020-08-07: Temporarily dropped on Python 3 until
-        # codeimport can be ported to Breezy.
-        'subvertpy; python_version < "3"',
         'tenacity',
         'testscenarios',
         'testtools',
diff --git a/system-packages.txt b/system-packages.txt
index 6812444..11a6f29 100644
--- a/system-packages.txt
+++ b/system-packages.txt
@@ -31,6 +31,3 @@ PIL
 # Dependency of cscvs.
 sqlite; python_version < "3"
 _sqlite; python_version < "3"
-
-# Used by bzr-git and bzr-svn.
-tdb
diff --git a/utilities/mock-code-import b/utilities/mock-code-import
deleted file mode 100755
index 524289e..0000000
--- a/utilities/mock-code-import
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/usr/bin/python2 -S
-#
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Make a Subversion repostiory and then make a CodeImportJob for it.
-
-USAGE: ./utilities/mock-code-import
-
-Run 'make schema' first! This utility mutates the DB and doesn't restore
-afterwards.
-
-This means that the valid_vcs_details constraint on CodeImport is lost and
-that there are new, crappy test objects in the DB. The utility will bork on
-these when run again.
-
-Details of the Subversion server are printed to stdout.
-"""
-
-# XXX: JonathanLange 2008-01-03: This is deliberately horrible.
-# You can make it nicer if you want.
-
-from __future__ import absolute_import, print_function
-
-import _pythonpath
-
-import os
-from subprocess import (
-    PIPE,
-    Popen,
-    )
-import tempfile
-
-import transaction
-
-from lp.codehosting.codeimport.tests.test_foreigntree import SubversionServer
-from lp.services.scripts import execute_zcml_for_scripts
-from lp.services.webapp import canonical_url
-from lp.testing.factory import LaunchpadObjectFactory
-
-
-def shell(*args):
-    print(' '.join(args))
-    return Popen(args, stdout=PIPE).communicate()[0]
-
-
-def make_import_job(svn_url):
-    factory = LaunchpadObjectFactory()
-    code_import = factory.makeCodeImport(svn_branch_url=svn_url)
-    return factory.makeCodeImportJob(code_import)
-
-
-def main():
-    execute_zcml_for_scripts(use_web_security=False)
-    temp_directory = tempfile.mkdtemp()
-    svn_repo_path = os.path.join(temp_directory, 'svn-repository')
-    svn_server = SubversionServer(svn_repo_path)
-    svn_server.setUp()
-    try:
-        svn_url = svn_server.makeBranch(
-            'trunk', [('README', 'No real content\n.')])
-        job = make_import_job(svn_url)
-        transaction.commit()
-        print("CodeImportJob.id:", job.id)
-        print("Code Import URL:", canonical_url(job.code_import))
-        print("Subversion Repository:", svn_repo_path)
-        print("Subversion branch URL:", job.code_import.svn_branch_url)
-        print("Launchpad branch URL:", canonical_url(job.code_import.branch))
-        print()
-    finally:
-        svn_server.tearDown()
-
-
-if __name__ == '__main__':
-    main()
diff --git a/utilities/sourcedeps.cache b/utilities/sourcedeps.cache
index 906459e..99f67fb 100644
--- a/utilities/sourcedeps.cache
+++ b/utilities/sourcedeps.cache
@@ -7,14 +7,6 @@
         166,
         "jelmer@xxxxxxxxx-20190822193925-ydrq7fgdi78lpgm7"
     ],
-    "bzr-git": [
-        280,
-        "launchpad@xxxxxxxxxxxxxxxxx-20171222005919-u98ut0f5z2g618um"
-    ],
-    "bzr-svn": [
-        2725,
-        "launchpad@xxxxxxxxxxxxxxxxx-20130816045016-wzr810hu2z459t4y"
-    ],
     "cscvs": [
         433,
         "launchpad@xxxxxxxxxxxxxxxxx-20130816043319-bts3l3bckmx431q1"
diff --git a/utilities/sourcedeps.conf b/utilities/sourcedeps.conf
index 59773ad..7755ec7 100644
--- a/utilities/sourcedeps.conf
+++ b/utilities/sourcedeps.conf
@@ -9,8 +9,6 @@
 
 brz-builder lp:~jelmer/brz-builder/trunk;revno=183
 brz-loom lp:~jelmer/brz-loom/trunk;revno=166
-bzr-git lp:~launchpad-pqm/bzr-git/devel;revno=280
-bzr-svn lp:~launchpad-pqm/bzr-svn/devel;revno=2725
 cscvs lp:~launchpad-pqm/launchpad-cscvs/devel;revno=433
 difftacular lp:~launchpad/difftacular/trunk;revno=11
 loggerhead lp:~loggerhead-team/loggerhead/trunk-rich;revno=511