← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/use-bzr-policy-open into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/use-bzr-policy-open into lp:launchpad.

Commit message:
Use new bzrlib.url_policy_open API for safely opening branches.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/use-bzr-policy-open/+merge/368761

Launchpad's safe_open API was fairly generic and has been moved into bzrlib as bzrlib.url_policy_open.

This branch changes Launchpad to use the copy of safe_open that is present in bzrlib, and removes it from Launchpad itself.

This is a resurrection of https://code.launchpad.net/~jelmer/launchpad/use-bzr-policy-open/+merge/112529, updated to current Launchpad and with some bugs fixed.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/use-bzr-policy-open into lp:launchpad.
=== modified file 'constraints.txt'
--- constraints.txt	2019-06-07 09:52:52 +0000
+++ constraints.txt	2019-06-13 10:09:50 +0000
@@ -234,7 +234,8 @@
 beautifulsoup4[lxml]==4.7.1
 billiard==3.5.0.5
 bson==0.3.3
-bzr==2.6.0.lp.3
+# lp:~launchpad/bzr/lp
+bzr==2.6.0.lp.4
 celery==4.1.1
 cffi==1.11.2
 Chameleon==2.11

=== modified file 'lib/launchpad_loggerhead/app.py'
--- lib/launchpad_loggerhead/app.py	2018-05-16 14:38:22 +0000
+++ lib/launchpad_loggerhead/app.py	2019-06-13 10:09:50 +0000
@@ -18,6 +18,7 @@
     favicon_app,
     static_app,
     )
+from bzrlib.url_policy_open import open_only_scheme
 from loggerhead.apps.branch import BranchWSGIApp
 import oops_wsgi
 from openid.consumer.consumer import (
@@ -47,7 +48,6 @@
     LAUNCHPAD_ANONYMOUS,
     LAUNCHPAD_SERVICES,
     )
-from lp.codehosting.safe_open import safe_open
 from lp.codehosting.vfs import get_lp_server
 from lp.services.config import config
 from lp.services.webapp.errorlog import ErrorReportingUtility
@@ -275,7 +275,7 @@
                 self.log.info("Branch is public")
 
             try:
-                bzr_branch = safe_open(
+                bzr_branch = open_only_scheme(
                     lp_server.get_url().strip(':/'), branch_url)
             except errors.NotBranchError as err:
                 self.log.warning('Not a branch: %s', err)

=== modified file 'lib/lp/code/interfaces/branch.py'
--- lib/lp/code/interfaces/branch.py	2019-04-17 12:03:22 +0000
+++ lib/lp/code/interfaces/branch.py	2019-06-13 10:09:50 +0000
@@ -1012,7 +1012,7 @@
         You can only call this if a server returned by `get_ro_server` or
         `get_rw_server` is running.
 
-        :raise lp.codehosting.safe_open.BadUrl: If the branch is stacked
+        :raise bzrlib.url_policy_open.BadUrl: If the branch is stacked
             on or a reference to an unacceptable URL.
         """
 

=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py	2019-04-17 10:34:13 +0000
+++ lib/lp/code/model/branch.py	2019-06-13 10:09:50 +0000
@@ -16,6 +16,7 @@
 
 from bzrlib import urlutils
 from bzrlib.revision import NULL_REVISION
+from bzrlib.url_policy_open import open_only_scheme
 from lazr.lifecycle.event import ObjectCreatedEvent
 import pytz
 import six
@@ -138,7 +139,6 @@
     RevisionAuthor,
     )
 from lp.code.model.seriessourcepackagebranch import SeriesSourcePackageBranch
-from lp.codehosting.safe_open import safe_open
 from lp.registry.enums import PersonVisibility
 from lp.registry.errors import CannotChangeInformationType
 from lp.registry.interfaces.accesspolicy import (
@@ -733,11 +733,11 @@
 
     def getInternalBzrUrl(self):
         """See `IBranch`."""
-        return 'lp-internal:///' + self.unique_name
+        return six.ensure_str('lp-internal:///' + self.unique_name)
 
     def getBzrBranch(self):
         """See `IBranch`."""
-        return safe_open('lp-internal', self.getInternalBzrUrl())
+        return open_only_scheme('lp-internal', self.getInternalBzrUrl())
 
     @property
     def display_name(self):

=== modified file 'lib/lp/code/model/tests/test_branch.py'
--- lib/lp/code/model/tests/test_branch.py	2019-05-22 14:57:45 +0000
+++ lib/lp/code/model/tests/test_branch.py	2019-06-13 10:09:50 +0000
@@ -16,6 +16,7 @@
 from bzrlib.branch import Branch
 from bzrlib.bzrdir import BzrDir
 from bzrlib.revision import NULL_REVISION
+from bzrlib.url_policy_open import BadUrl
 from pytz import UTC
 from sqlobject import SQLObjectNotFound
 from storm.exceptions import LostObjectError
@@ -116,7 +117,6 @@
     add_revision_to_branch,
     BranchHostingFixture,
     )
-from lp.codehosting.safe_open import BadUrl
 from lp.codehosting.vfs.branchfs import get_real_branch_path
 from lp.registry.enums import (
     BranchSharingPolicy,
@@ -3271,7 +3271,7 @@
         self.useBzrBranches(direct_database=True)
 
     def test_simple(self):
-        # safe_open returns the underlying bzr branch of a database branch in
+        # open_only_scheme returns the underlying bzr branch of a database branch in
         # the simple, unstacked, case.
         db_branch, tree = self.create_branch_and_tree()
         # XXX: AaronBentley 2010-08-06 bug=614404: a bzr username is
@@ -3283,7 +3283,7 @@
 
     def test_acceptable_stacking(self):
         # If the underlying bzr branch of a database branch is stacked on
-        # another launchpad branch safe_open returns it.
+        # another launchpad branch open_only_scheme returns it.
         db_stacked_on, stacked_on_tree = self.create_branch_and_tree()
         db_stacked, stacked_tree = self.create_branch_and_tree()
         stacked_tree.branch.set_stacked_on_url(

=== modified file 'lib/lp/codehosting/codeimport/tests/test_worker.py'
--- lib/lp/codehosting/codeimport/tests/test_worker.py	2019-05-22 14:57:45 +0000
+++ lib/lp/codehosting/codeimport/tests/test_worker.py	2019-06-13 10:09:50 +0000
@@ -40,6 +40,13 @@
     join as urljoin,
     local_path_from_url,
     )
+from bzrlib.url_policy_open import (
+    _BlacklistPolicy,
+    AcceptAnythingPolicy,
+    BadUrl,
+    BranchOpenPolicy,
+    BranchOpener,
+    )
 from CVS import (
     Repository,
     tree as CVSTree,
@@ -76,13 +83,6 @@
     ImportDataStore,
     ToBzrImportWorker,
     )
-from lp.codehosting.safe_open import (
-    AcceptAnythingPolicy,
-    BadUrl,
-    BlacklistPolicy,
-    BranchOpenPolicy,
-    SafeBranchOpener,
-    )
 from lp.codehosting.tests.helpers import create_branch_with_one_revision
 from lp.services.config import config
 from lp.services.log.logger import BufferLogger
@@ -129,7 +129,7 @@
     def setUp(self):
         super(WorkerTest, self).setUp()
         self.disable_directory_isolation()
-        SafeBranchOpener.install_hook()
+        BranchOpener.install_hook()
 
     def assertDirectoryTreesEqual(self, directory1, directory2):
         """Assert that `directory1` has the same structure as `directory2`.
@@ -1242,7 +1242,7 @@
         cache_dir = get_cache(uuid).create_cache_dir()
         cache_dir_contents = os.listdir(cache_dir)
         self.assertNotEqual([], cache_dir_contents)
-        opener = SafeBranchOpener(worker._opener_policy, worker.probers)
+        opener = BranchOpener(worker._opener_policy, worker.probers)
         remote_branch = opener.open(worker.source_details.url)
         worker.pushBazaarBranch(
             self.make_branch('.'), remote_branch=remote_branch)
@@ -1331,7 +1331,7 @@
         reference_url, target_url = self.createBranchReference()
         source_details = self.factory.makeCodeImportSourceDetails(
             url=reference_url, rcstype='bzr')
-        policy = BlacklistPolicy(True, [target_url])
+        policy = _BlacklistPolicy(True, [target_url])
         worker = self.makeImportWorker(source_details, opener_policy=policy)
         self.assertEqual(
             CodeImportWorkerExitCode.FAILURE_FORBIDDEN, worker.run())
@@ -1363,15 +1363,15 @@
         self.policy = CodeImportBranchOpenPolicy("bzr", "bzr")
 
     def test_follows_references(self):
-        self.assertEqual(True, self.policy.shouldFollowReferences())
+        self.assertEqual(True, self.policy.should_follow_references())
 
     def assertBadUrl(self, url):
-        self.assertRaises(BadUrl, self.policy.checkOneURL, url)
+        self.assertRaises(BadUrl, self.policy.check_one_url, url)
 
     def assertGoodUrl(self, url):
-        self.policy.checkOneURL(url)
+        self.policy.check_one_url(url)
 
-    def test_checkOneURL(self):
+    def test_check_one_url(self):
         self.assertBadUrl("sftp://somehost/";)
         self.assertBadUrl("/etc/passwd")
         self.assertBadUrl("file:///etc/passwd")
@@ -1384,7 +1384,7 @@
         self.assertGoodUrl("bzr://bzr.example.com/somebzrurl/")
         self.assertBadUrl("bzr://bazaar.launchpad.test/example")
 
-    def test_checkOneURL_git_to_bzr(self):
+    def test_check_one_url_git_to_bzr(self):
         self.policy = CodeImportBranchOpenPolicy("git", "bzr")
         self.assertBadUrl("/etc/passwd")
         self.assertBadUrl("file:///etc/passwd")
@@ -1392,7 +1392,7 @@
         self.assertGoodUrl("git://git.example.com/repo")
         self.assertGoodUrl("git://git.launchpad.test/example")
 
-    def test_checkOneURL_git_to_git(self):
+    def test_check_one_url_git_to_git(self):
         self.policy = CodeImportBranchOpenPolicy("git", "git")
         self.assertBadUrl("/etc/passwd")
         self.assertBadUrl("file:///etc/passwd")
@@ -1408,7 +1408,7 @@
     def setUp(self):
         http_utils.TestCaseWithRedirectedWebserver.setUp(self)
         self.disable_directory_isolation()
-        SafeBranchOpener.install_hook()
+        BranchOpener.install_hook()
         tree = self.make_branch_and_tree('.')
         self.revid = tree.commit("A commit")
         self.bazaar_store = BazaarBranchStore(
@@ -1438,14 +1438,14 @@
             def __init__(self, new_url):
                 self.new_url = new_url
 
-            def shouldFollowReferences(self):
+            def should_follow_references(self):
                 return True
 
-            def checkOneURL(self, url):
+            def check_one_url(self, url):
                 if url.startswith(self.new_url):
                     raise BadUrl(url)
 
-            def transformFallbackLocation(self, branch, url):
+            def transform_fallback_location(self, branch, url):
                 return urlutils.join(branch.base, url), False
 
         policy = NewUrlBlacklistPolicy(self.get_new_url())

=== modified file 'lib/lp/codehosting/codeimport/worker.py'
--- lib/lp/codehosting/codeimport/worker.py	2019-03-07 10:37:59 +0000
+++ lib/lp/codehosting/codeimport/worker.py	2019-06-13 10:09:50 +0000
@@ -59,6 +59,12 @@
     join as urljoin,
     local_path_from_url,
     )
+from bzrlib.url_policy_open import (
+    BadUrl,
+    BranchOpenPolicy,
+    BranchOpener,
+    )
+
 import cscvs
 from cscvs.cmds import totla
 import CVS
@@ -81,11 +87,6 @@
     extract_tarball,
     )
 from lp.codehosting.codeimport.uifactory import LoggingUIFactory
-from lp.codehosting.safe_open import (
-    BadUrl,
-    BranchOpenPolicy,
-    SafeBranchOpener,
-    )
 from lp.services.config import config
 from lp.services.propertycache import cachedproperty
 from lp.services.timeout import urlfetch
@@ -108,8 +109,8 @@
         self.rcstype = rcstype
         self.target_rcstype = target_rcstype
 
-    def shouldFollowReferences(self):
-        """See `BranchOpenPolicy.shouldFollowReferences`.
+    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
@@ -117,16 +118,16 @@
         """
         return True
 
-    def transformFallbackLocation(self, branch, url):
-        """See `BranchOpenPolicy.transformFallbackLocation`.
+    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 checkOneURL(self, url):
-        """See `BranchOpenPolicy.checkOneURL`.
+    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.
@@ -726,7 +727,7 @@
     def _doImport(self):
         self._logger.info("Starting job.")
         saved_factory = bzrlib.ui.ui_factory
-        opener = SafeBranchOpener(self._opener_policy, self.probers)
+        opener = BranchOpener(self._opener_policy, self.probers)
         bzrlib.ui.ui_factory = LoggingUIFactory(logger=self._logger)
         try:
             self._logger.info(
@@ -1046,7 +1047,7 @@
     def _doImport(self):
         self._logger.info("Starting job.")
         try:
-            self._opener_policy.checkOneURL(self.source_details.url)
+            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

=== modified file 'lib/lp/codehosting/puller/tests/__init__.py'
--- lib/lp/codehosting/puller/tests/__init__.py	2012-04-16 23:02:44 +0000
+++ lib/lp/codehosting/puller/tests/__init__.py	2019-06-13 10:09:50 +0000
@@ -16,6 +16,7 @@
     TestingHTTPServer,
     TestingThreadingHTTPServer,
     )
+from bzrlib.url_policy_open import AcceptAnythingPolicy
 
 from lp.codehosting.puller.worker import (
     BranchMirrorer,
@@ -23,7 +24,6 @@
     PullerWorker,
     PullerWorkerProtocol,
     )
-from lp.codehosting.safe_open import AcceptAnythingPolicy
 from lp.codehosting.tests.helpers import LoomTestMixin
 from lp.testing import TestCaseWithFactory
 

=== modified file 'lib/lp/codehosting/puller/tests/test_errors.py'
--- lib/lp/codehosting/puller/tests/test_errors.py	2019-05-22 14:57:45 +0000
+++ lib/lp/codehosting/puller/tests/test_errors.py	2019-06-13 10:09:50 +0000
@@ -18,6 +18,10 @@
     UnknownFormatError,
     UnsupportedFormatError,
     )
+from bzrlib.url_policy_open import (
+    BranchLoopError,
+    BranchReferenceForbidden,
+    )
 from lazr.uri import InvalidURIError
 
 from lp.code.enums import BranchType
@@ -29,10 +33,6 @@
     PullerWorker,
     PullerWorkerProtocol,
     )
-from lp.codehosting.safe_open import (
-    BranchLoopError,
-    BranchReferenceForbidden,
-    )
 from lp.testing import TestCase
 
 

=== modified file 'lib/lp/codehosting/puller/tests/test_worker.py'
--- lib/lp/codehosting/puller/tests/test_worker.py	2019-05-22 14:57:45 +0000
+++ lib/lp/codehosting/puller/tests/test_worker.py	2019-06-13 10:09:50 +0000
@@ -22,6 +22,12 @@
     TestCaseWithTransport,
     )
 from bzrlib.transport import get_transport
+from bzrlib.url_policy_open import (
+    AcceptAnythingPolicy,
+    BadUrl,
+    BranchOpenPolicy,
+    BranchOpener,
+    )
 
 from lp.code.enums import BranchType
 from lp.codehosting.puller.tests import (
@@ -37,14 +43,8 @@
     install_worker_ui_factory,
     MirroredBranchPolicy,
     PullerWorkerProtocol,
-    SafeBranchOpener,
     WORKER_ACTIVITY_NETWORK,
     )
-from lp.codehosting.safe_open import (
-    AcceptAnythingPolicy,
-    BadUrl,
-    BranchOpenPolicy,
-    )
 from lp.testing import TestCase
 from lp.testing.factory import (
     LaunchpadObjectFactory,
@@ -86,7 +86,7 @@
 
     def setUp(self):
         super(TestPullerWorker, self).setUp()
-        SafeBranchOpener.install_hook()
+        BranchOpener.install_hook()
 
     def test_mirror_opener_with_stacked_on_url(self):
         # A PullerWorker for a mirrored branch gets a MirroredBranchPolicy as
@@ -277,7 +277,7 @@
 
     def setUp(self):
         super(TestReferenceOpener, self).setUp()
-        SafeBranchOpener.install_hook()
+        BranchOpener.install_hook()
 
     def createBranchReference(self, url):
         """Create a pure branch reference that points to the specified URL.
@@ -318,20 +318,20 @@
         self.assertEqual(opened_branch.base, target_branch.base)
 
     def testFollowReferenceValue(self):
-        # SafeBranchOpener.followReference gives the reference value for
+        # BranchOpener.follow_reference gives the reference value for
         # a branch reference.
-        opener = SafeBranchOpener(BranchOpenPolicy())
+        opener = BranchOpener(BranchOpenPolicy())
         reference_value = 'http://example.com/branch'
         reference_url = self.createBranchReference(reference_value)
         self.assertEqual(
-            reference_value, opener.followReference(reference_url))
+            reference_value, opener.follow_reference(reference_url))
 
     def testFollowReferenceNone(self):
-        # SafeBranchOpener.followReference gives None for a normal branch.
+        # BranchOpener.follow_reference gives None for a normal branch.
         self.make_branch('repo')
         branch_url = self.get_url('repo')
-        opener = SafeBranchOpener(BranchOpenPolicy())
-        self.assertIs(None, opener.followReference(branch_url))
+        opener = BranchOpener(BranchOpenPolicy())
+        self.assertIs(None, opener.follow_reference(branch_url))
 
 
 class TestMirroredBranchPolicy(TestCase):
@@ -344,44 +344,44 @@
     def testNoFileURL(self):
         policy = MirroredBranchPolicy()
         self.assertRaises(
-            BadUrlScheme, policy.checkOneURL,
+            BadUrlScheme, policy.check_one_url,
             self.factory.getUniqueURL(scheme='file'))
 
     def testNoUnknownSchemeURLs(self):
         policy = MirroredBranchPolicy()
         self.assertRaises(
-            BadUrlScheme, policy.checkOneURL,
+            BadUrlScheme, policy.check_one_url,
             self.factory.getUniqueURL(scheme='decorator+scheme'))
 
     def testNoSSHURL(self):
         policy = MirroredBranchPolicy()
         self.assertRaises(
-            BadUrlSsh, policy.checkOneURL,
+            BadUrlSsh, policy.check_one_url,
             self.factory.getUniqueURL(scheme='bzr+ssh'))
 
     def testNoSftpURL(self):
         policy = MirroredBranchPolicy()
         self.assertRaises(
-            BadUrlSsh, policy.checkOneURL,
+            BadUrlSsh, policy.check_one_url,
             self.factory.getUniqueURL(scheme='sftp'))
 
     def testNoLaunchpadURL(self):
         policy = MirroredBranchPolicy()
         self.assertRaises(
-            BadUrlLaunchpad, policy.checkOneURL,
+            BadUrlLaunchpad, policy.check_one_url,
             self.factory.getUniqueURL(host='bazaar.launchpad.test'))
 
     def testNoHTTPSLaunchpadURL(self):
         policy = MirroredBranchPolicy()
         self.assertRaises(
-            BadUrlLaunchpad, policy.checkOneURL,
+            BadUrlLaunchpad, policy.check_one_url,
             self.factory.getUniqueURL(
                 host='bazaar.launchpad.test', scheme='https'))
 
     def testNoOtherHostLaunchpadURL(self):
         policy = MirroredBranchPolicy()
         self.assertRaises(
-            BadUrlLaunchpad, policy.checkOneURL,
+            BadUrlLaunchpad, policy.check_one_url,
             self.factory.getUniqueURL(host='code.launchpad.test'))
 
     def testLocalhost(self):
@@ -389,9 +389,9 @@
             'codehosting', blacklisted_hostnames='localhost,127.0.0.1')
         policy = MirroredBranchPolicy()
         localhost_url = self.factory.getUniqueURL(host='localhost')
-        self.assertRaises(BadUrl, policy.checkOneURL, localhost_url)
+        self.assertRaises(BadUrl, policy.check_one_url, localhost_url)
         localhost_url = self.factory.getUniqueURL(host='127.0.0.1')
-        self.assertRaises(BadUrl, policy.checkOneURL, localhost_url)
+        self.assertRaises(BadUrl, policy.check_one_url, localhost_url)
 
     def test_no_stacked_on_url(self):
         # By default, a MirroredBranchPolicy does not stack branches.
@@ -495,7 +495,7 @@
 
     def setUp(self):
         super(TestWorkerProgressReporting, self).setUp()
-        SafeBranchOpener.install_hook()
+        BranchOpener.install_hook()
         self.saved_factory = bzrlib.ui.ui_factory
         self.disable_directory_isolation()
         self.addCleanup(setattr, bzrlib.ui, 'ui_factory', self.saved_factory)

=== modified file 'lib/lp/codehosting/puller/tests/test_worker_formats.py'
--- lib/lp/codehosting/puller/tests/test_worker_formats.py	2015-09-28 17:38:45 +0000
+++ lib/lp/codehosting/puller/tests/test_worker_formats.py	2019-06-13 10:09:50 +0000
@@ -15,10 +15,10 @@
 from bzrlib.repofmt.knitpack_repo import RepositoryFormatKnitPack5
 from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1
 from bzrlib.tests.per_repository import TestCaseWithRepository
+from bzrlib.url_policy_open import BranchOpener
 
 import lp.codehosting  # For bzr plugins.
 from lp.codehosting.puller.tests import PullerWorkerMixin
-from lp.codehosting.safe_open import SafeBranchOpener
 from lp.codehosting.tests.helpers import LoomTestMixin
 
 
@@ -29,7 +29,7 @@
         TestCaseWithRepository.setUp(self)
         # make_bzrdir relies on this being a relative filesystem path.
         self._source_branch_path = 'source-branch'
-        SafeBranchOpener.install_hook()
+        BranchOpener.install_hook()
         self.worker = self.makePullerWorker(
             self.get_url(self._source_branch_path),
             self.get_url('dest-path'))

=== modified file 'lib/lp/codehosting/puller/worker.py'
--- lib/lp/codehosting/puller/worker.py	2012-06-29 08:40:05 +0000
+++ lib/lp/codehosting/puller/worker.py	2019-06-13 10:09:50 +0000
@@ -27,6 +27,14 @@
 from bzrlib.transport import get_transport
 import bzrlib.ui
 from bzrlib.ui import SilentUIFactory
+from bzrlib.url_policy_open import (
+    BadUrl,
+    BranchLoopError,
+    BranchOpenPolicy,
+    BranchReferenceForbidden,
+    BranchOpener,
+    )
+
 from lazr.uri import (
     InvalidURIError,
     URI,
@@ -39,13 +47,6 @@
 from lp.code.enums import BranchType
 from lp.codehosting.bzrutils import identical_formats
 from lp.codehosting.puller import get_lock_id_for_branch_id
-from lp.codehosting.safe_open import (
-    BadUrl,
-    BranchLoopError,
-    BranchOpenPolicy,
-    BranchReferenceForbidden,
-    SafeBranchOpener,
-    )
 from lp.services.config import config
 from lp.services.webapp import errorlog
 
@@ -75,7 +76,7 @@
     """Found a URL with an untrusted scheme."""
 
     def __init__(self, scheme, url):
-        BadUrl.__init__(self, scheme, url)
+        BadUrl.__init__(self, url)
         self.scheme = scheme
 
 
@@ -195,7 +196,7 @@
         """
         self.policy = policy
         self.protocol = protocol
-        self.opener = SafeBranchOpener(policy)
+        self.opener = BranchOpener(policy)
         if log is not None:
             self.log = log
         else:
@@ -213,7 +214,7 @@
             URL must point to a writable location.
         :return: The destination branch.
         """
-        return self.opener.runWithTransformFallbackLocationHookInstalled(
+        return self.opener.run_with_transform_fallback_location_hook_installed(
             self.policy.createDestinationBranch, source_branch,
             destination_url)
 
@@ -552,8 +553,8 @@
             return None
         return self.stacked_on_url
 
-    def shouldFollowReferences(self):
-        """See `BranchOpenPolicy.shouldFollowReferences`.
+    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
@@ -561,16 +562,16 @@
         """
         return True
 
-    def transformFallbackLocation(self, branch, url):
-        """See `BranchOpenPolicy.transformFallbackLocation`.
+    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 urlutils.join(branch.base, url), True
 
-    def checkOneURL(self, url):
-        """See `BranchOpenPolicy.checkOneURL`.
+    def check_one_url(self, url):
+        """See `BranchOpenPolicy.check_one_url`.
 
         We refuse to mirror from Launchpad or a ssh-like or file URL.
         """
@@ -619,23 +620,23 @@
                 break
         return Branch.open_from_transport(dest_transport)
 
-    def shouldFollowReferences(self):
-        """See `BranchOpenerPolicy.shouldFollowReferences`.
+    def should_follow_references(self):
+        """See `BranchOpenerPolicy.should_follow_references`.
 
         We do not traverse references for IMPORTED branches because the
         code-import system should never produce branch references.
         """
         return False
 
-    def transformFallbackLocation(self, branch, url):
-        """See `BranchOpenerPolicy.transformFallbackLocation`.
+    def transform_fallback_location(self, branch, url):
+        """See `BranchOpenerPolicy.transform_fallback_location`.
 
         Import branches should not be stacked, ever.
         """
         raise AssertionError("Import branch unexpectedly stacked!")
 
-    def checkOneURL(self, url):
-        """See `BranchOpenerPolicy.checkOneURL`.
+    def check_one_url(self, url):
+        """See `BranchOpenerPolicy.check_one_url`.
 
         If the URL we are mirroring from does not start how we expect the pull
         URLs of import branches to start, something has gone badly wrong, so

=== removed file 'lib/lp/codehosting/safe_open.py'
--- lib/lp/codehosting/safe_open.py	2012-08-21 17:34:42 +0000
+++ lib/lp/codehosting/safe_open.py	1970-01-01 00:00:00 +0000
@@ -1,354 +0,0 @@
-# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Safe branch opening."""
-
-__metaclass__ = type
-
-import threading
-
-from bzrlib import (
-    errors,
-    trace,
-    urlutils,
-    )
-from bzrlib.branch import Branch
-from bzrlib.bzrdir import (
-    BzrProber,
-    RemoteBzrProber,
-    )
-from bzrlib.transport import (
-    do_catching_redirections,
-    get_transport,
-    )
-from lazr.uri import URI
-
-
-__all__ = [
-    'AcceptAnythingPolicy',
-    'BadUrl',
-    'BlacklistPolicy',
-    'BranchLoopError',
-    'BranchOpenPolicy',
-    'BranchReferenceForbidden',
-    'SafeBranchOpener',
-    'WhitelistPolicy',
-    'safe_open',
-    ]
-
-
-# TODO JelmerVernooij 2011-08-06: This module is generic enough to be
-# in bzrlib, and may be of use to others. bug=850843
-
-# These are the default probers that SafeBranchOpener will try to use,
-# unless a different set was specified.
-
-DEFAULT_PROBERS = [BzrProber, RemoteBzrProber]
-
-
-class BadUrl(Exception):
-    """Tried to access a branch from a bad URL."""
-
-
-class BranchReferenceForbidden(Exception):
-    """Trying to mirror a branch reference and the branch type does not allow
-    references.
-    """
-
-
-class BranchLoopError(Exception):
-    """Encountered a branch cycle.
-
-    A URL may point to a branch reference or it may point to a stacked branch.
-    In either case, it's possible for there to be a cycle in these references,
-    and this exception is raised when we detect such a cycle.
-    """
-
-
-class BranchOpenPolicy:
-    """Policy on how to open branches.
-
-    In particular, a policy determines which branches are safe to open by
-    checking their URLs and deciding whether or not to follow branch
-    references.
-    """
-
-    def shouldFollowReferences(self):
-        """Whether we traverse references when mirroring.
-
-        Subclasses must override this method.
-
-        If we encounter a branch reference and this returns false, an error is
-        raised.
-
-        :returns: A boolean to indicate whether to follow a branch reference.
-        """
-        raise NotImplementedError(self.shouldFollowReferences)
-
-    def transformFallbackLocation(self, branch, url):
-        """Validate, maybe modify, 'url' to be used as a stacked-on location.
-
-        :param branch:  The branch that is being opened.
-        :param url: The URL that the branch provides for its stacked-on
-            location.
-        :return: (new_url, check) where 'new_url' is the URL of the branch to
-            actually open and 'check' is true if 'new_url' needs to be
-            validated by checkAndFollowBranchReference.
-        """
-        raise NotImplementedError(self.transformFallbackLocation)
-
-    def checkOneURL(self, url):
-        """Check the safety of the source URL.
-
-        Subclasses must override this method.
-
-        :param url: The source URL to check.
-        :raise BadUrl: subclasses are expected to raise this or a subclass
-            when it finds a URL it deems to be unsafe.
-        """
-        raise NotImplementedError(self.checkOneURL)
-
-
-class BlacklistPolicy(BranchOpenPolicy):
-    """Branch policy that forbids certain URLs."""
-
-    def __init__(self, should_follow_references, unsafe_urls=None):
-        if unsafe_urls is None:
-            unsafe_urls = set()
-        self._unsafe_urls = unsafe_urls
-        self._should_follow_references = should_follow_references
-
-    def shouldFollowReferences(self):
-        return self._should_follow_references
-
-    def checkOneURL(self, url):
-        if url in self._unsafe_urls:
-            raise BadUrl(url)
-
-    def transformFallbackLocation(self, branch, url):
-        """See `BranchOpenPolicy.transformFallbackLocation`.
-
-        This class is not used for testing our smarter stacking features so we
-        just do the simplest thing: return the URL that would be used anyway
-        and don't check it.
-        """
-        return urlutils.join(branch.base, url), False
-
-
-class AcceptAnythingPolicy(BlacklistPolicy):
-    """Accept anything, to make testing easier."""
-
-    def __init__(self):
-        super(AcceptAnythingPolicy, self).__init__(True, set())
-
-
-class WhitelistPolicy(BranchOpenPolicy):
-    """Branch policy that only allows certain URLs."""
-
-    def __init__(self, should_follow_references, allowed_urls=None,
-                 check=False):
-        if allowed_urls is None:
-            allowed_urls = []
-        self.allowed_urls = set(url.rstrip('/') for url in allowed_urls)
-        self.check = check
-
-    def shouldFollowReferences(self):
-        return self._should_follow_references
-
-    def checkOneURL(self, url):
-        if url.rstrip('/') not in self.allowed_urls:
-            raise BadUrl(url)
-
-    def transformFallbackLocation(self, branch, url):
-        """See `BranchOpenPolicy.transformFallbackLocation`.
-
-        Here we return the URL that would be used anyway and optionally check
-        it.
-        """
-        return urlutils.join(branch.base, url), self.check
-
-
-class SingleSchemePolicy(BranchOpenPolicy):
-    """Branch open policy that rejects URLs not on the given scheme."""
-
-    def __init__(self, allowed_scheme):
-        self.allowed_scheme = allowed_scheme
-
-    def shouldFollowReferences(self):
-        return True
-
-    def transformFallbackLocation(self, branch, url):
-        return urlutils.join(branch.base, url), True
-
-    def checkOneURL(self, url):
-        """Check that `url` is safe to open."""
-        if URI(url).scheme != self.allowed_scheme:
-            raise BadUrl(url)
-
-
-class SafeBranchOpener(object):
-    """Safe branch opener.
-
-    All locations that are opened (stacked-on branches, references) are
-    checked against a policy object.
-
-    The policy object is expected to have the following methods:
-    * checkOneURL
-    * shouldFollowReferences
-    * transformFallbackLocation
-    """
-
-    _threading_data = threading.local()
-
-    def __init__(self, policy, probers=None):
-        """Create a new SafeBranchOpener.
-
-        :param policy: The opener policy to use.
-        :param probers: Optional list of probers to allow.
-            Defaults to local and remote bzr probers.
-        """
-        self.policy = policy
-        self._seen_urls = set()
-        if probers is None:
-            self.probers = list(DEFAULT_PROBERS)
-        else:
-            self.probers = probers
-
-    @classmethod
-    def install_hook(cls):
-        """Install the ``transformFallbackLocation`` hook.
-
-        This is done at module import time, but transformFallbackLocationHook
-        doesn't do anything unless the `_active_openers` threading.Local
-        object has a 'opener' attribute in this thread.
-
-        This is in a module-level function rather than performed at module
-        level so that it can be called in setUp for testing `SafeBranchOpener`
-        as bzrlib.tests.TestCase.setUp clears hooks.
-        """
-        Branch.hooks.install_named_hook(
-            'transform_fallback_location',
-            cls.transformFallbackLocationHook,
-            'SafeBranchOpener.transformFallbackLocationHook')
-
-    def checkAndFollowBranchReference(self, url):
-        """Check URL (and possibly the referenced URL) for safety.
-
-        This method checks that `url` passes the policy's `checkOneURL`
-        method, and if `url` refers to a branch reference, it checks whether
-        references are allowed and whether the reference's URL passes muster
-        also -- recursively, until a real branch is found.
-
-        :param url: URL to check
-        :raise BranchLoopError: If the branch references form a loop.
-        :raise BranchReferenceForbidden: If this opener forbids branch
-            references.
-        """
-        while True:
-            if url in self._seen_urls:
-                raise BranchLoopError()
-            self._seen_urls.add(url)
-            self.policy.checkOneURL(url)
-            next_url = self.followReference(url)
-            if next_url is None:
-                return url
-            url = next_url
-            if not self.policy.shouldFollowReferences():
-                raise BranchReferenceForbidden(url)
-
-    @classmethod
-    def transformFallbackLocationHook(cls, branch, url):
-        """Installed as the 'transform_fallback_location' Branch hook.
-
-        This method calls `transformFallbackLocation` on the policy object and
-        either returns the url it provides or passes it back to
-        checkAndFollowBranchReference.
-        """
-        try:
-            opener = getattr(cls._threading_data, "opener")
-        except AttributeError:
-            return url
-        new_url, check = opener.policy.transformFallbackLocation(branch, url)
-        if check:
-            return opener.checkAndFollowBranchReference(new_url)
-        else:
-            return new_url
-
-    def runWithTransformFallbackLocationHookInstalled(
-            self, callable, *args, **kw):
-        assert (self.transformFallbackLocationHook in
-                Branch.hooks['transform_fallback_location'])
-        self._threading_data.opener = self
-        try:
-            return callable(*args, **kw)
-        finally:
-            del self._threading_data.opener
-            # We reset _seen_urls here to avoid multiple calls to open giving
-            # spurious loop exceptions.
-            self._seen_urls = set()
-
-    def followReference(self, url):
-        """Get the branch-reference value at the specified url.
-
-        This exists as a separate method only to be overridden in unit tests.
-        """
-        bzrdir = self._open_dir(url)
-        return bzrdir.get_branch_reference()
-
-    def _open_dir(self, url):
-        """Simple BzrDir.open clone that only uses specific probers.
-
-        :param url: URL to open
-        :return: ControlDir instance
-        """
-        def redirected(transport, e, redirection_notice):
-            self.policy.checkOneURL(e.target)
-            redirected_transport = transport._redirected_to(
-                e.source, e.target)
-            if redirected_transport is None:
-                raise errors.NotBranchError(e.source)
-            trace.note('%s is%s redirected to %s',
-                 transport.base, e.permanently, redirected_transport.base)
-            return redirected_transport
-
-        def find_format(transport):
-            last_error = errors.NotBranchError(transport.base)
-            for prober_kls in self.probers:
-                prober = prober_kls()
-                try:
-                    return transport, prober.probe_transport(transport)
-                except errors.NotBranchError as e:
-                    last_error = e
-            else:
-                raise last_error
-        transport = get_transport(url)
-        transport, format = do_catching_redirections(find_format, transport,
-            redirected)
-        return format.open(transport)
-
-    def open(self, url, ignore_fallbacks=False):
-        """Open the Bazaar branch at url, first checking for safety.
-
-        What safety means is defined by a subclasses `followReference` and
-        `checkOneURL` methods.
-        """
-        url = self.checkAndFollowBranchReference(url)
-
-        def open_branch(url, ignore_fallbacks):
-            dir = self._open_dir(url)
-            return dir.open_branch(ignore_fallbacks=ignore_fallbacks)
-        return self.runWithTransformFallbackLocationHookInstalled(
-            open_branch, url, ignore_fallbacks)
-
-
-def safe_open(allowed_scheme, url, ignore_fallbacks=False):
-    """Open the branch at `url`, only accessing URLs on `allowed_scheme`.
-
-    :raises BadUrl: An attempt was made to open a URL that was not on
-        `allowed_scheme`.
-    """
-    return SafeBranchOpener(SingleSchemePolicy(allowed_scheme)).open(url,
-                            ignore_fallbacks=ignore_fallbacks)
-
-
-SafeBranchOpener.install_hook()

=== modified file 'lib/lp/codehosting/scanner/tests/test_bzrsync.py'
--- lib/lp/codehosting/scanner/tests/test_bzrsync.py	2018-01-02 16:10:26 +0000
+++ lib/lp/codehosting/scanner/tests/test_bzrsync.py	2019-06-13 10:09:50 +0000
@@ -14,6 +14,7 @@
     )
 from bzrlib.tests import TestCaseWithTransport
 from bzrlib.uncommit import uncommit
+from bzrlib.url_policy_open import BranchOpener
 from fixtures import TempDir
 import pytz
 from storm.locals import Store
@@ -45,7 +46,6 @@
     read_locked,
     write_locked,
     )
-from lp.codehosting.safe_open import SafeBranchOpener
 from lp.codehosting.scanner.bzrsync import BzrSync
 from lp.services.config import config
 from lp.services.database.interfaces import IStore
@@ -86,7 +86,7 @@
 
     def setUp(self):
         super(BzrSyncTestCase, self).setUp()
-        SafeBranchOpener.install_hook()
+        BranchOpener.install_hook()
         self.disable_directory_isolation()
         self.useBzrBranches(direct_database=True)
         self.makeFixtures()

=== removed file 'lib/lp/codehosting/tests/test_safe_open.py'
--- lib/lp/codehosting/tests/test_safe_open.py	2018-01-02 16:10:26 +0000
+++ lib/lp/codehosting/tests/test_safe_open.py	1970-01-01 00:00:00 +0000
@@ -1,378 +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 the safe branch open code."""
-
-
-__metaclass__ = type
-
-from bzrlib.branch import (
-    Branch,
-    BranchReferenceFormat,
-    BzrBranchFormat7,
-    )
-from bzrlib.bzrdir import (
-    BzrDir,
-    BzrDirMetaFormat1,
-    BzrProber,
-    )
-from bzrlib.controldir import ControlDirFormat
-from bzrlib.errors import NotBranchError
-from bzrlib.repofmt.knitpack_repo import RepositoryFormatKnitPack1
-from bzrlib.tests import TestCaseWithTransport
-from bzrlib.transport import chroot
-from lazr.uri import URI
-
-from lp.codehosting.safe_open import (
-    BadUrl,
-    BlacklistPolicy,
-    BranchLoopError,
-    BranchReferenceForbidden,
-    safe_open,
-    SafeBranchOpener,
-    WhitelistPolicy,
-    )
-from lp.codehosting.tests.helpers import force_stacked_on_url
-from lp.testing import TestCase
-
-
-class TestSafeBranchOpenerCheckAndFollowBranchReference(TestCase):
-    """Unit tests for `SafeBranchOpener.checkAndFollowBranchReference`."""
-
-    def setUp(self):
-        super(TestSafeBranchOpenerCheckAndFollowBranchReference, self).setUp()
-        SafeBranchOpener.install_hook()
-
-    class StubbedSafeBranchOpener(SafeBranchOpener):
-        """SafeBranchOpener that provides canned answers.
-
-        We implement the methods we need to to be able to control all the
-        inputs to the `BranchMirrorer.checkSource` method, which is what is
-        being tested in this class.
-        """
-
-        def __init__(self, references, policy):
-            parent_cls = TestSafeBranchOpenerCheckAndFollowBranchReference
-            super(parent_cls.StubbedSafeBranchOpener, self).__init__(policy)
-            self._reference_values = {}
-            for i in range(len(references) - 1):
-                self._reference_values[references[i]] = references[i + 1]
-            self.follow_reference_calls = []
-
-        def followReference(self, url):
-            self.follow_reference_calls.append(url)
-            return self._reference_values[url]
-
-    def makeBranchOpener(self, should_follow_references, references,
-                         unsafe_urls=None):
-        policy = BlacklistPolicy(should_follow_references, unsafe_urls)
-        opener = self.StubbedSafeBranchOpener(references, policy)
-        return opener
-
-    def testCheckInitialURL(self):
-        # checkSource rejects all URLs that are not allowed.
-        opener = self.makeBranchOpener(None, [], set(['a']))
-        self.assertRaises(
-            BadUrl, opener.checkAndFollowBranchReference, 'a')
-
-    def testNotReference(self):
-        # When branch references are forbidden, checkAndFollowBranchReference
-        # does not raise on non-references.
-        opener = self.makeBranchOpener(False, ['a', None])
-        self.assertEqual('a', opener.checkAndFollowBranchReference('a'))
-        self.assertEqual(['a'], opener.follow_reference_calls)
-
-    def testBranchReferenceForbidden(self):
-        # checkAndFollowBranchReference raises BranchReferenceForbidden if
-        # branch references are forbidden and the source URL points to a
-        # branch reference.
-        opener = self.makeBranchOpener(False, ['a', 'b'])
-        self.assertRaises(
-            BranchReferenceForbidden,
-            opener.checkAndFollowBranchReference, 'a')
-        self.assertEqual(['a'], opener.follow_reference_calls)
-
-    def testAllowedReference(self):
-        # checkAndFollowBranchReference does not raise if following references
-        # is allowed and the source URL points to a branch reference to a
-        # permitted location.
-        opener = self.makeBranchOpener(True, ['a', 'b', None])
-        self.assertEqual('b', opener.checkAndFollowBranchReference('a'))
-        self.assertEqual(['a', 'b'], opener.follow_reference_calls)
-
-    def testCheckReferencedURLs(self):
-        # checkAndFollowBranchReference checks if the URL a reference points
-        # to is safe.
-        opener = self.makeBranchOpener(
-            True, ['a', 'b', None], unsafe_urls=set('b'))
-        self.assertRaises(
-            BadUrl, opener.checkAndFollowBranchReference, 'a')
-        self.assertEqual(['a'], opener.follow_reference_calls)
-
-    def testSelfReferencingBranch(self):
-        # checkAndFollowBranchReference raises BranchReferenceLoopError if
-        # following references is allowed and the source url points to a
-        # self-referencing branch reference.
-        opener = self.makeBranchOpener(True, ['a', 'a'])
-        self.assertRaises(
-            BranchLoopError, opener.checkAndFollowBranchReference, 'a')
-        self.assertEqual(['a'], opener.follow_reference_calls)
-
-    def testBranchReferenceLoop(self):
-        # checkAndFollowBranchReference raises BranchReferenceLoopError if
-        # following references is allowed and the source url points to a loop
-        # of branch references.
-        references = ['a', 'b', 'a']
-        opener = self.makeBranchOpener(True, references)
-        self.assertRaises(
-            BranchLoopError, opener.checkAndFollowBranchReference, 'a')
-        self.assertEqual(['a', 'b'], opener.follow_reference_calls)
-
-
-class TrackingProber(BzrProber):
-    """Subclass of BzrProber which tracks URLs it has been asked to open."""
-
-    seen_urls = []
-
-    @classmethod
-    def probe_transport(klass, transport):
-        klass.seen_urls.append(transport.base)
-        return BzrProber.probe_transport(transport)
-
-
-class TestSafeBranchOpenerStacking(TestCaseWithTransport):
-
-    def setUp(self):
-        super(TestSafeBranchOpenerStacking, self).setUp()
-        SafeBranchOpener.install_hook()
-
-    def makeBranchOpener(self, allowed_urls, probers=None):
-        policy = WhitelistPolicy(True, allowed_urls, True)
-        return SafeBranchOpener(policy, probers)
-
-    def makeBranch(self, path, branch_format, repository_format):
-        """Make a Bazaar branch at 'path' with the given formats."""
-        bzrdir_format = BzrDirMetaFormat1()
-        bzrdir_format.set_branch_format(branch_format)
-        bzrdir = self.make_bzrdir(path, format=bzrdir_format)
-        repository_format.initialize(bzrdir)
-        return bzrdir.create_branch()
-
-    def testProbers(self):
-        # Only the specified probers should be used
-        b = self.make_branch('branch')
-        opener = self.makeBranchOpener([b.base], probers=[])
-        self.assertRaises(NotBranchError, opener.open, b.base)
-        opener = self.makeBranchOpener([b.base], probers=[BzrProber])
-        self.assertEqual(b.base, opener.open(b.base).base)
-
-    def testDefaultProbers(self):
-        # If no probers are specified to the constructor
-        # of SafeBranchOpener, then a safe set will be used,
-        # rather than all probers registered in bzr.
-        self.addCleanup(ControlDirFormat.unregister_prober, TrackingProber)
-        ControlDirFormat.register_prober(TrackingProber)
-        # Open a location without any branches, so that all probers are
-        # tried.
-        # First, check that the TrackingProber tracks correctly.
-        TrackingProber.seen_urls = []
-        opener = self.makeBranchOpener(["."], probers=[TrackingProber])
-        self.assertRaises(NotBranchError, opener.open, ".")
-        self.assertEqual(1, len(TrackingProber.seen_urls))
-        TrackingProber.seen_urls = []
-        # And make sure it's registered in such a way that BzrDir.open would
-        # use it.
-        self.assertRaises(NotBranchError, BzrDir.open, ".")
-        self.assertEqual(1, len(TrackingProber.seen_urls))
-        TrackingProber.seen_urls = []
-        # Make sure that SafeBranchOpener doesn't use it if no
-        # probers were specified
-        opener = self.makeBranchOpener(["."])
-        self.assertRaises(NotBranchError, opener.open, ".")
-        self.assertEqual(0, len(TrackingProber.seen_urls))
-
-    def testAllowedURL(self):
-        # checkSource does not raise an exception for branches stacked on
-        # branches with allowed URLs.
-        stacked_on_branch = self.make_branch('base-branch')
-        stacked_branch = self.make_branch('stacked-branch')
-        stacked_branch.set_stacked_on_url(stacked_on_branch.base)
-        opener = self.makeBranchOpener(
-            [stacked_branch.base, stacked_on_branch.base])
-        # This doesn't raise an exception.
-        opener.open(stacked_branch.base)
-
-    def testUnstackableRepository(self):
-        # checkSource treats branches with UnstackableRepositoryFormats as
-        # being not stacked.
-        branch = self.makeBranch(
-            'unstacked', BzrBranchFormat7(), RepositoryFormatKnitPack1())
-        opener = self.makeBranchOpener([branch.base])
-        # This doesn't raise an exception.
-        opener.open(branch.base)
-
-    def testAllowedRelativeURL(self):
-        # checkSource passes on absolute urls to checkOneURL, even if the
-        # value of stacked_on_location in the config is set to a relative URL.
-        stacked_on_branch = self.make_branch('base-branch')
-        stacked_branch = self.make_branch('stacked-branch')
-        stacked_branch.set_stacked_on_url('../base-branch')
-        opener = self.makeBranchOpener(
-            [stacked_branch.base, stacked_on_branch.base])
-        # Note that stacked_on_branch.base is not '../base-branch', it's an
-        # absolute URL.
-        self.assertNotEqual('../base-branch', stacked_on_branch.base)
-        # This doesn't raise an exception.
-        opener.open(stacked_branch.base)
-
-    def testAllowedRelativeNested(self):
-        # Relative URLs are resolved relative to the stacked branch.
-        self.get_transport().mkdir('subdir')
-        a = self.make_branch('subdir/a')
-        b = self.make_branch('b')
-        b.set_stacked_on_url('../subdir/a')
-        c = self.make_branch('subdir/c')
-        c.set_stacked_on_url('../../b')
-        opener = self.makeBranchOpener([c.base, b.base, a.base])
-        # This doesn't raise an exception.
-        opener.open(c.base)
-
-    def testForbiddenURL(self):
-        # checkSource raises a BadUrl exception if a branch is stacked on a
-        # branch with a forbidden URL.
-        stacked_on_branch = self.make_branch('base-branch')
-        stacked_branch = self.make_branch('stacked-branch')
-        stacked_branch.set_stacked_on_url(stacked_on_branch.base)
-        opener = self.makeBranchOpener([stacked_branch.base])
-        self.assertRaises(BadUrl, opener.open, stacked_branch.base)
-
-    def testForbiddenURLNested(self):
-        # checkSource raises a BadUrl exception if a branch is stacked on a
-        # branch that is in turn stacked on a branch with a forbidden URL.
-        a = self.make_branch('a')
-        b = self.make_branch('b')
-        b.set_stacked_on_url(a.base)
-        c = self.make_branch('c')
-        c.set_stacked_on_url(b.base)
-        opener = self.makeBranchOpener([c.base, b.base])
-        self.assertRaises(BadUrl, opener.open, c.base)
-
-    def testSelfStackedBranch(self):
-        # checkSource raises StackingLoopError if a branch is stacked on
-        # itself. This avoids infinite recursion errors.
-        a = self.make_branch('a')
-        force_stacked_on_url(a, a.base)
-        opener = self.makeBranchOpener([a.base])
-        self.assertRaises(BranchLoopError, opener.open, a.base)
-
-    def testLoopStackedBranch(self):
-        # checkSource raises StackingLoopError if a branch is stacked in such
-        # a way so that it is ultimately stacked on itself. e.g. a stacked on
-        # b stacked on a.
-        a = self.make_branch('a')
-        b = self.make_branch('b')
-        a.set_stacked_on_url(b.base)
-        b.set_stacked_on_url(a.base)
-        opener = self.makeBranchOpener([a.base, b.base])
-        self.assertRaises(BranchLoopError, opener.open, a.base)
-        self.assertRaises(BranchLoopError, opener.open, b.base)
-
-    def testCustomOpener(self):
-        # A custom function for opening a control dir can be specified.
-        a = self.make_branch('a')
-        b = self.make_branch('b')
-        b.set_stacked_on_url(a.base)
-
-        TrackingProber.seen_urls = []
-        opener = self.makeBranchOpener(
-            [a.base, b.base], probers=[TrackingProber])
-        opener.open(b.base)
-        self.assertEqual(set(TrackingProber.seen_urls), set([b.base, a.base]))
-
-    def testCustomOpenerWithBranchReference(self):
-        # A custom function for opening a control dir can be specified.
-        a = self.make_branch('a')
-        b_dir = self.make_bzrdir('b')
-        b = BranchReferenceFormat().initialize(b_dir, target_branch=a)
-        TrackingProber.seen_urls = []
-        opener = self.makeBranchOpener(
-            [a.base, b.base], probers=[TrackingProber])
-        opener.open(b.base)
-        self.assertEqual(set(TrackingProber.seen_urls), set([b.base, a.base]))
-
-    def test_ignore_fallbacks(self):
-        """"Cross-format stacking doesn't error with ignore_fallbacks."""
-        stacked, stacked_on = make_cross_format_stacked(self)
-        opener = self.makeBranchOpener([stacked.base, stacked_on.base])
-        opener.open(stacked.base, ignore_fallbacks=True)
-
-
-def make_cross_format_stacked(test_case):
-    test_case.get_transport().mkdir('inside')
-    stacked = test_case.make_branch('inside/stacked', format='1.6')
-    stacked_on = test_case.make_branch('inside/stacked-on', format='2a')
-    force_stacked_on_url(stacked, stacked_on.base)
-    return stacked, stacked_on
-
-
-class TestSafeOpen(TestCaseWithTransport):
-    """Tests for `safe_open`."""
-
-    def setUp(self):
-        super(TestSafeOpen, self).setUp()
-        SafeBranchOpener.install_hook()
-
-    def test_hook_does_not_interfere(self):
-        # The transform_fallback_location hook does not interfere with regular
-        # stacked branch access outside of safe_open.
-        self.make_branch('stacked')
-        self.make_branch('stacked-on')
-        Branch.open('stacked').set_stacked_on_url('../stacked-on')
-        Branch.open('stacked')
-
-    def get_chrooted_scheme(self, relpath):
-        """Create a server that is chrooted to `relpath`.
-
-        :return: ``(scheme, get_url)`` where ``scheme`` is the scheme of the
-            chroot server and ``get_url`` returns URLs on said server.
-        """
-        transport = self.get_transport(relpath)
-        chroot_server = chroot.ChrootServer(transport)
-        chroot_server.start_server()
-        self.addCleanup(chroot_server.stop_server)
-
-        def get_url(relpath):
-            return chroot_server.get_url() + relpath
-
-        return URI(chroot_server.get_url()).scheme, get_url
-
-    def test_stacked_within_scheme(self):
-        # A branch that is stacked on a URL of the same scheme is safe to
-        # open.
-        self.get_transport().mkdir('inside')
-        self.make_branch('inside/stacked')
-        self.make_branch('inside/stacked-on')
-        scheme, get_chrooted_url = self.get_chrooted_scheme('inside')
-        Branch.open(get_chrooted_url('stacked')).set_stacked_on_url(
-            get_chrooted_url('stacked-on'))
-        safe_open(scheme, get_chrooted_url('stacked'))
-
-    def test_stacked_outside_scheme(self):
-        # A branch that is stacked on a URL that is not of the same scheme is
-        # not safe to open.
-        self.get_transport().mkdir('inside')
-        self.get_transport().mkdir('outside')
-        self.make_branch('inside/stacked')
-        self.make_branch('outside/stacked-on')
-        scheme, get_chrooted_url = self.get_chrooted_scheme('inside')
-        Branch.open(get_chrooted_url('stacked')).set_stacked_on_url(
-            self.get_url('outside/stacked-on'))
-        self.assertRaises(
-            BadUrl, safe_open, scheme, get_chrooted_url('stacked'))
-
-    def test_ignore_fallbacks(self):
-        """"Cross-format stacking doesn't error with ignore_fallbacks."""
-        scheme, get_chrooted_url = self.get_chrooted_scheme('inside')
-        stacked, stacked_on = make_cross_format_stacked(self)
-        force_stacked_on_url(stacked, get_chrooted_url('stacked-on'))
-        safe_open(scheme, get_chrooted_url('stacked'), ignore_fallbacks=True)

=== modified file 'lib/lp/codehosting/upgrade.py'
--- lib/lp/codehosting/upgrade.py	2014-01-30 15:04:06 +0000
+++ lib/lp/codehosting/upgrade.py	2019-06-13 10:09:50 +0000
@@ -29,6 +29,10 @@
     )
 from bzrlib.repofmt.groupcompress_repo import RepositoryFormat2aSubtree
 from bzrlib.upgrade import upgrade
+from bzrlib.url_policy_open import (
+    BranchOpener,
+    SingleSchemePolicy,
+    )
 
 from lp.code.bzr import (
     branch_changed,
@@ -36,7 +40,6 @@
     )
 from lp.code.model.branch import Branch
 from lp.codehosting.bzrutils import read_locked
-from lp.codehosting.safe_open import safe_open
 from lp.codehosting.vfs.branchfs import get_real_branch_path
 from lp.services.database.interfaces import IStore
 
@@ -52,9 +55,9 @@
         self.branch = branch
         self.bzr_branch = bzr_branch
         if self.bzr_branch is None:
-            self.bzr_branch = safe_open('lp-internal',
-                                        self.branch.getInternalBzrUrl(),
-                                        ignore_fallbacks=True)
+            opener = BranchOpener(SingleSchemePolicy('lp-internal'))
+            self.bzr_branch = opener.open(
+                self.branch.getInternalBzrUrl(), ignore_fallbacks=True)
         self.target_dir = target_dir
         self.target_subdir = os.path.join(
             self.target_dir, str(self.branch.id))

=== modified file 'scripts/code-import-worker.py'
--- scripts/code-import-worker.py	2016-11-03 17:31:43 +0000
+++ scripts/code-import-worker.py	2019-06-13 10:09:50 +0000
@@ -21,6 +21,7 @@
 import sys
 
 from bzrlib.transport import get_transport
+from bzrlib.url_policy_open import AcceptAnythingPolicy
 
 from lp.codehosting.codeimport.worker import (
     BzrImportWorker,
@@ -32,7 +33,6 @@
     GitImportWorker,
     GitToGitImportWorker,
     )
-from lp.codehosting.safe_open import AcceptAnythingPolicy
 from lp.services import scripts
 from lp.services.config import config
 from lp.services.timeout import set_default_timeout_function


Follow ups