# Copyright (C) 2006-2012, 2016 Canonical Ltd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

"""Tests for control directory implementations - tests a controldir format."""

import breezy.branch
from breezy import branch as _mod_branch
from breezy import (
    check,
    controldir,
    errors,
    gpg,
    osutils,
    transport,
    ui,
    urlutils,
    workingtree,
)
from breezy import repository as _mod_repository
from breezy import revision as _mod_revision
from breezy.bzr import bzrdir as _mod_bzrdir
from breezy.bzr.remote import RemoteBzrDir, RemoteBzrDirFormat, RemoteRepository
from breezy.tests import ChrootedTestCase, TestNotApplicable, TestSkipped, fixtures
from breezy.tests.per_controldir import TestCaseWithControlDir
from breezy.transport.local import LocalTransport
from breezy.ui import CannedInputUIFactory


class TestControlDir(TestCaseWithControlDir):
    def skipIfNoWorkingTree(self, a_controldir):
        """Raises TestSkipped if a_controldir doesn't have a working tree.

        If the bzrdir does have a workingtree, this is a no-op.
        """
        try:
            a_controldir.open_workingtree()
        except (errors.NotLocalUrl, errors.NoWorkingTree):
            raise TestSkipped(
                "bzrdir on transport {!r} has no working tree".format(
                    a_controldir.transport
                )
            )

    def openWorkingTreeIfLocal(self, a_controldir):
        """If a_controldir is on a local transport, call open_workingtree() on it."""
        if not isinstance(a_controldir.root_transport, LocalTransport):
            # it's not local, but that's ok
            return
        a_controldir.open_workingtree()

    def createWorkingTreeOrSkip(self, a_controldir):
        """Create a working tree on a_controldir, or raise TestSkipped.

        A simple wrapper for create_workingtree that translates NotLocalUrl into
        TestSkipped.  Returns the newly created working tree.
        """
        try:
            return a_controldir.create_workingtree()
        except (errors.NotLocalUrl, errors.UnsupportedOperation):
            raise TestSkipped(
                "cannot make working tree with transport {!r}".format(
                    a_controldir.transport
                )
            )

    def sproutOrSkip(
        self,
        from_bzrdir,
        to_url,
        revision_id=None,
        force_new_repo=False,
        accelerator_tree=None,
        create_tree_if_local=True,
    ):
        """Sprout from_bzrdir into to_url, or raise TestSkipped.

        A simple wrapper for from_bzrdir.sprout that translates NotLocalUrl into
        TestSkipped.  Returns the newly sprouted bzrdir.
        """
        to_transport = transport.get_transport(to_url)
        if not isinstance(to_transport, LocalTransport):
            raise TestSkipped("Cannot sprout to remote bzrdirs.")
        target = from_bzrdir.sprout(
            to_url,
            revision_id=revision_id,
            force_new_repo=force_new_repo,
            possible_transports=[to_transport],
            accelerator_tree=accelerator_tree,
            create_tree_if_local=create_tree_if_local,
        )
        return target

    def test_uninitializable(self):
        if self.bzrdir_format.is_initializable():
            raise TestNotApplicable("format is initializable")
        t = self.get_transport()
        self.assertRaises(
            errors.UninitializableFormat, self.bzrdir_format.initialize, t.base
        )

    def test_multiple_initialization(self):
        # loopback test to check the current format initializes to itself.
        if not self.bzrdir_format.is_initializable():
            # unsupported formats are not loopback testable
            # because the default open will not open them and
            # they may not be initializable.
            raise TestNotApplicable("format is not initializable")
        self.bzrdir_format.initialize(".")
        self.assertRaises(
            errors.AlreadyControlDirError, self.bzrdir_format.initialize, "."
        )

    def test_create_null_workingtree(self):
        dir = self.make_controldir("dir1")
        dir.create_repository()
        dir.create_branch()
        try:
            wt = dir.create_workingtree(revision_id=_mod_revision.NULL_REVISION)
        except (errors.NotLocalUrl, errors.UnsupportedOperation):
            raise TestSkipped(
                "cannot make working tree with transport {!r}".format(dir.transport)
            )
        self.assertEqual([], wt.get_parent_ids())

    def test_destroy_workingtree(self):
        tree = self.make_branch_and_tree("tree")
        self.build_tree(["tree/file"])
        tree.add("file")
        tree.commit("first commit")
        bzrdir = tree.controldir
        try:
            bzrdir.destroy_workingtree()
        except errors.UnsupportedOperation:
            raise TestSkipped("Format does not support destroying tree")
        self.assertPathDoesNotExist("tree/file")
        self.assertRaises(errors.NoWorkingTree, bzrdir.open_workingtree)
        bzrdir.create_workingtree()
        self.assertPathExists("tree/file")
        bzrdir.destroy_workingtree_metadata()
        self.assertPathExists("tree/file")
        self.assertRaises(errors.NoWorkingTree, bzrdir.open_workingtree)

    def test_destroy_branch(self):
        branch = self.make_branch("branch")
        bzrdir = branch.controldir
        try:
            bzrdir.destroy_branch()
        except (errors.UnsupportedOperation, errors.TransportNotPossible):
            raise TestNotApplicable("Format does not support destroying branch")
        self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
        bzrdir.create_branch()
        bzrdir.open_branch()

    def test_destroy_branch_no_branch(self):
        branch = self.make_repository("branch")
        bzrdir = branch.controldir
        try:
            self.assertRaises(errors.NotBranchError, bzrdir.destroy_branch)
        except (errors.UnsupportedOperation, errors.TransportNotPossible):
            raise TestNotApplicable("Format does not support destroying branch")

    def test_destroy_repository(self):
        repo = self.make_repository("repository")
        bzrdir = repo.controldir
        try:
            bzrdir.destroy_repository()
        except (errors.UnsupportedOperation, errors.TransportNotPossible):
            raise TestNotApplicable("Format does not support destroying repository")
        self.assertRaises(errors.NoRepositoryPresent, bzrdir.destroy_repository)
        self.assertRaises(errors.NoRepositoryPresent, bzrdir.open_repository)
        bzrdir.create_repository()
        bzrdir.open_repository()

    def test_open_workingtree_raises_no_working_tree(self):
        """ControlDir.open_workingtree() should raise NoWorkingTree (rather than
        e.g. NotLocalUrl) if there is no working tree.
        """
        dir = self.make_controldir("source")
        vfs_dir = controldir.ControlDir.open(self.get_vfs_only_url("source"))
        if vfs_dir.has_workingtree():
            # This ControlDir format doesn't support ControlDirs without
            # working trees, so this test is irrelevant.
            raise TestNotApplicable(
                "format does not support control directories without working tree"
            )
        self.assertRaises(errors.NoWorkingTree, dir.open_workingtree)

    def test_clone_controldir_repository_under_shared(self):
        tree = self.make_branch_and_tree("commit_tree")
        self.build_tree(["foo"], transport=tree.controldir.transport.clone(".."))
        tree.add("foo")
        rev1 = tree.commit("revision 1")
        dir = self.make_controldir("source")
        repo = dir.create_repository()
        if not repo._format.supports_nesting_repositories:
            raise TestNotApplicable("repository format does not support nesting")
        repo.fetch(tree.branch.repository)
        self.assertTrue(repo.has_revision(rev1))
        try:
            self.make_repository("target", shared=True)
        except errors.IncompatibleFormat:
            raise TestNotApplicable(
                "repository format does not support shared repositories"
            )
        target = dir.clone(self.get_url("target/child"))
        self.assertNotEqual(dir.transport.base, target.transport.base)
        self.assertRaises(errors.NoRepositoryPresent, target.open_repository)

    def test_clone_controldir_repository_branch_both_under_shared(self):
        # Create a shared repository
        try:
            shared_repo = self.make_repository("shared", shared=True)
        except errors.IncompatibleFormat:
            raise TestNotApplicable(
                "repository format does not support shared repositories"
            )
        if not shared_repo._format.supports_nesting_repositories:
            raise TestNotApplicable("format does not support nesting repositories")
        # Make a branch, 'commit_tree', and working tree outside of the shared
        # repository, and commit some revisions to it.
        tree = self.make_branch_and_tree("commit_tree")
        self.build_tree(["foo"], transport=tree.controldir.root_transport)
        tree.add("foo")
        rev1 = tree.commit("revision 1")
        tree.controldir.open_branch().generate_revision_history(
            _mod_revision.NULL_REVISION
        )
        tree.set_parent_trees([])
        tree.commit("revision 2")
        # Copy the content (i.e. revisions) from the 'commit_tree' branch's
        # repository into the shared repository.
        tree.branch.repository.copy_content_into(shared_repo)
        # Make a branch 'source' inside the shared repository.
        dir = self.make_controldir("shared/source")
        dir.create_branch()
        # Clone 'source' to 'target', also inside the shared repository.
        target = dir.clone(self.get_url("shared/target"))
        # 'source', 'target', and the shared repo all have distinct controldirs.
        self.assertNotEqual(dir.transport.base, target.transport.base)
        self.assertNotEqual(dir.transport.base, shared_repo.controldir.transport.base)
        # The shared repository will contain revisions from the 'commit_tree'
        # repository, even revisions that are not part of the history of the
        # 'commit_tree' branch.
        self.assertTrue(shared_repo.has_revision(rev1))

    def test_clone_controldir_repository_branch_only_source_under_shared(self):
        try:
            shared_repo = self.make_repository("shared", shared=True)
        except errors.IncompatibleFormat:
            raise TestNotApplicable(
                "repository format does not support shared repositories"
            )
        if not shared_repo._format.supports_nesting_repositories:
            raise TestNotApplicable("format does not support nesting repositories")
        tree = self.make_branch_and_tree("commit_tree")
        self.build_tree(["commit_tree/foo"])
        tree.add("foo")
        rev1 = tree.commit("revision 1")
        tree.branch.controldir.open_branch().generate_revision_history(
            _mod_revision.NULL_REVISION
        )
        tree.set_parent_trees([])
        tree.commit("revision 2")
        tree.branch.repository.copy_content_into(shared_repo)
        if shared_repo.make_working_trees():
            shared_repo.set_make_working_trees(False)
            self.assertFalse(shared_repo.make_working_trees())
        self.assertTrue(shared_repo.has_revision(rev1))
        dir = self.make_controldir("shared/source")
        dir.create_branch()
        target = dir.clone(self.get_url("target"))
        self.assertNotEqual(dir.transport.base, target.transport.base)
        self.assertNotEqual(dir.transport.base, shared_repo.controldir.transport.base)
        branch = target.open_branch()
        self.assertTrue(branch.repository.has_revision(rev1))
        self.assertFalse(branch.repository.make_working_trees())
        self.assertTrue(branch.repository.is_shared())

    def test_clone_controldir_repository_revision(self):
        # test for revision limiting, [smoke test, not corner case checks].
        # make a repository with some revisions,
        # and clone it with a revision limit.
        #
        tree = self.make_branch_and_tree("commit_tree")
        self.build_tree(["commit_tree/foo"])
        tree.add("foo")
        tree.commit("revision 1")
        tree.branch.controldir.open_branch().generate_revision_history(
            _mod_revision.NULL_REVISION
        )
        tree.set_parent_trees([])
        rev2 = tree.commit("revision 2")
        source = self.make_repository("source")
        tree.branch.repository.copy_content_into(source)
        dir = source.controldir
        dir.clone(self.get_url("target"), revision_id=rev2)
        raise TestSkipped("revision limiting not strict yet")

    def test_clone_controldir_branch_and_repo_fixed_user_id(self):
        # Bug #430868 is about an email containing '.sig'
        self.overrideEnv("BRZ_EMAIL", "murphy@host.sighup.org")
        tree = self.make_branch_and_tree("commit_tree")
        self.build_tree(["commit_tree/foo"])
        tree.add("foo")
        rev1 = tree.commit("revision 1")
        tree_repo = tree.branch.repository
        if not tree_repo._format.supports_revision_signatures:
            self.skipTest("repository format does not support signing")
        with tree_repo.lock_write(), _mod_repository.WriteGroup(tree_repo):
            tree_repo.sign_revision(rev1, gpg.LoopbackGPGStrategy(None))
        target = self.make_branch("target")
        tree.branch.repository.copy_content_into(target.repository)
        tree.branch.copy_content_into(target)
        self.assertTrue(target.repository.has_revision(rev1))
        self.assertEqual(
            tree_repo.get_signature_text(rev1),
            target.repository.get_signature_text(rev1),
        )

    def test_clone_controldir_branch_and_repo_into_shared_repo(self):
        # by default cloning into a shared repo uses the shared repo.
        tree = self.make_branch_and_tree("commit_tree")
        self.build_tree(["commit_tree/foo"])
        tree.add("foo")
        tree.commit("revision 1")
        source = self.make_branch("source")
        tree.branch.repository.copy_content_into(source.repository)
        tree.branch.copy_content_into(source)
        try:
            shared_repo = self.make_repository("target", shared=True)
        except errors.IncompatibleFormat:
            raise TestNotApplicable(
                "repository format does not support shared repositories"
            )
        if not shared_repo._format.supports_nesting_repositories:
            raise TestNotApplicable("format does not support nesting repositories")
        dir = source.controldir
        target = dir.clone(self.get_url("target/child"))
        self.assertNotEqual(dir.transport.base, target.transport.base)
        self.assertRaises(errors.NoRepositoryPresent, target.open_repository)
        self.assertEqual(source.last_revision(), target.open_branch().last_revision())

    def test_clone_controldir_branch_revision(self):
        # test for revision limiting, [smoke test, not corner case checks].
        # make a branch with some revisions,
        # and clone it with a revision limit.
        #
        tree = self.make_branch_and_tree("commit_tree")
        self.build_tree(["commit_tree/foo"])
        tree.add("foo")
        rev1 = tree.commit("revision 1")
        tree.commit("revision 2", allow_pointless=True)
        source = self.make_branch("source")
        tree.branch.repository.copy_content_into(source.repository)
        tree.branch.copy_content_into(source)
        dir = source.controldir
        target = dir.clone(self.get_url("target"), revision_id=rev1)
        self.assertEqual(rev1, target.open_branch().last_revision())

    def test_clone_controldir_with_colocated(self):
        if not self.bzrdir_format.colocated_branches:
            raise TestNotApplicable("format does not supported colocated branches")
        tree = self.make_branch_and_tree("commit_tree")
        self.build_tree(["commit_tree/foo"])
        tree.add("foo")
        rev1 = tree.commit("revision 1")
        rev2 = tree.commit("revision 2", allow_pointless=True)
        tree.commit("revision 2", allow_pointless=True)
        dir = tree.branch.controldir
        colo = dir.create_branch(name="colo")
        colo.pull(tree.branch, stop_revision=rev1)
        target = dir.clone(self.get_url("target"), revision_id=rev2)
        self.assertEqual(rev2, target.open_branch().last_revision())
        self.assertEqual(rev1, target.open_branch(name="colo").last_revision())

    def test_clone_on_transport_preserves_repo_format(self):
        if self.bzrdir_format == controldir.format_registry.make_controldir("default"):
            format = "knit"
        else:
            format = None
        source_branch = self.make_branch("source", format=format)
        # Ensure no format data is cached
        a_dir = breezy.branch.Branch.open_from_transport(
            self.get_transport("source")
        ).controldir
        target_transport = self.get_transport("target")
        target_controldir = a_dir.clone_on_transport(target_transport)
        target_repo = target_controldir.open_repository()
        source_branch = breezy.branch.Branch.open(self.get_vfs_only_url("source"))
        if isinstance(target_repo, RemoteRepository):
            target_repo._ensure_real()
            target_repo = target_repo._real_repository
        self.assertEqual(target_repo._format, source_branch.repository._format)

    def test_clone_controldir_tree_revision(self):
        # test for revision limiting, [smoke test, not corner case checks].
        # make a tree with a revision with a last-revision
        # and clone it with a revision limit.
        # This smoke test just checks the revision-id is right. Tree specific
        # tests will check corner cases.
        tree = self.make_branch_and_tree("source")
        self.build_tree(["source/foo"])
        tree.add("foo")
        rev1 = tree.commit("revision 1")
        tree.commit("revision 2", allow_pointless=True)
        dir = tree.controldir
        target = dir.clone(self.get_url("target"), revision_id=rev1)
        self.skipIfNoWorkingTree(target)
        self.assertEqual([rev1], target.open_workingtree().get_parent_ids())

    def test_clone_controldir_into_notrees_repo(self):
        """Cloning into a no-trees repo should not create a working tree."""
        tree = self.make_branch_and_tree("source")
        self.build_tree(["source/foo"])
        tree.add("foo")
        tree.commit("revision 1")

        try:
            repo = self.make_repository("repo", shared=True)
        except errors.IncompatibleFormat:
            raise TestNotApplicable("must support shared repositories")
        if repo.make_working_trees():
            repo.set_make_working_trees(False)
            self.assertFalse(repo.make_working_trees())

        a_dir = tree.controldir.clone(self.get_url("repo/a"))
        a_branch = a_dir.open_branch()
        # If the new control dir actually uses the repository, it should
        # not have a working tree.
        if not a_branch.repository.has_same_location(repo):
            raise TestNotApplicable("new control dir does not use repository")
        self.assertRaises(errors.NoWorkingTree, a_dir.open_workingtree)

    def test_clone_respects_stacked(self):
        branch = self.make_branch("parent")
        child_transport = self.get_transport("child")
        try:
            child = branch.controldir.clone_on_transport(
                child_transport, stacked_on=branch.base
            )
        except (
            _mod_branch.UnstackableBranchFormat,
            errors.UnstackableRepositoryFormat,
        ):
            raise TestNotApplicable(
                "branch or repository format does not support stacking"
            )
        self.assertEqual(child.open_branch().get_stacked_on_url(), branch.base)

    def test_set_branch_reference(self):
        """set_branch_reference creates a branch reference."""
        referenced_branch = self.make_branch("referenced")
        dir = self.make_controldir("source")
        try:
            dir.set_branch_reference(referenced_branch)
        except errors.IncompatibleFormat:
            # this is ok too, not all formats have to support references.
            raise TestNotApplicable(
                "control directory does not support branch references"
            )
        self.assertEqual(referenced_branch.user_url, dir.get_branch_reference())

    def test_set_branch_reference_on_existing_reference(self):
        """set_branch_reference creates a branch reference."""
        referenced_branch1 = self.make_branch("old-referenced")
        referenced_branch2 = self.make_branch("new-referenced")
        dir = self.make_controldir("source")
        try:
            dir.set_branch_reference(referenced_branch1)
        except errors.IncompatibleFormat:
            # this is ok too, not all formats have to support references.
            raise TestNotApplicable(
                "control directory does not support branch references"
            )
        dir.set_branch_reference(referenced_branch2)
        self.assertEqual(referenced_branch2.user_url, dir.get_branch_reference())

    def test_set_branch_reference_on_existing_branch(self):
        """set_branch_reference creates a branch reference."""
        referenced_branch = self.make_branch("referenced")
        dir = self.make_branch("source").controldir
        try:
            dir.set_branch_reference(referenced_branch)
        except errors.IncompatibleFormat:
            # this is ok too, not all formats have to support references.
            raise TestNotApplicable(
                "control directory does not support branch references"
            )
        self.assertEqual(referenced_branch.user_url, dir.get_branch_reference())

    def test_get_branch_reference_on_reference(self):
        """get_branch_reference should return the right url."""
        referenced_branch = self.make_branch("referenced")
        dir = self.make_controldir("source")
        try:
            dir.set_branch_reference(referenced_branch)
        except errors.IncompatibleFormat:
            # this is ok too, not all formats have to support references.
            raise TestNotApplicable(
                "control directory does not support branch references"
            )
        self.assertEqual(referenced_branch.user_url, dir.get_branch_reference())

    def test_get_branch_reference_on_non_reference(self):
        """get_branch_reference should return None for non-reference branches."""
        dir = self.make_controldir("referenced")
        dir.create_repository()
        if dir._format.colocated_branches:
            # The default branch in a controldir might be a reference branch
            # (e.g. for Git), so let's create another one.
            name = "foo"
        else:
            name = None
        branch = dir.create_branch(name)
        self.assertEqual(None, branch.controldir.get_branch_reference(name))

    def test_get_branch_reference_no_branch(self):
        """get_branch_reference should not mask NotBranchErrors."""
        dir = self.make_controldir("source")
        if dir.has_branch():
            # this format does not support branchless controldirs.
            raise TestNotApplicable(
                "format does not support branchless control directories"
            )
        self.assertRaises(errors.NotBranchError, dir.get_branch_reference)

    def test_sprout_controldir_empty(self):
        dir = self.make_controldir("source")
        target = dir.sprout(self.get_url("target"))
        self.assertNotEqual(dir.control_transport.base, target.control_transport.base)
        # creates a new repository branch and tree
        target.open_repository()
        target.open_branch()
        self.openWorkingTreeIfLocal(target)

    def test_sprout_controldir_empty_under_shared_repo(self):
        # sprouting an empty dir into a repo uses the repo
        dir = self.make_controldir("source")
        try:
            self.make_repository("target", shared=True)
        except errors.IncompatibleFormat:
            raise TestNotApplicable("format does not support shared repositories")
        target = dir.sprout(self.get_url("target/child"))
        self.assertRaises(errors.NoRepositoryPresent, target.open_repository)
        target.open_branch()
        try:
            target.open_workingtree()
        except errors.NoWorkingTree:
            # Some controldirs can never have working trees.
            repo = target.find_repository()
            self.assertFalse(repo.controldir._format.supports_workingtrees)

    def test_sprout_controldir_empty_under_shared_repo_force_new(self):
        # the force_new_repo parameter should force use of a new repo in an empty
        # controldir's sprout logic
        dir = self.make_controldir("source")
        try:
            self.make_repository("target", shared=True)
        except errors.IncompatibleFormat:
            raise TestNotApplicable("format does not support shared repositories")
        target = dir.sprout(self.get_url("target/child"), force_new_repo=True)
        target.open_repository()
        target.open_branch()
        self.openWorkingTreeIfLocal(target)

    def test_sprout_controldir_with_repository_to_shared(self):
        tree = self.make_branch_and_tree("commit_tree")
        self.build_tree(["commit_tree/foo"])
        tree.add("foo")
        rev1 = tree.commit("revision 1")
        tree.controldir.open_branch().generate_revision_history(
            _mod_revision.NULL_REVISION
        )
        tree.set_parent_trees([])
        tree.commit("revision 2")
        source = self.make_repository("source")
        tree.branch.repository.copy_content_into(source)
        dir = source.controldir
        try:
            shared_repo = self.make_repository("target", shared=True)
        except errors.IncompatibleFormat:
            raise TestNotApplicable("format does not support shared repositories")
        target = dir.sprout(self.get_url("target/child"))
        self.assertNotEqual(dir.user_transport.base, target.user_transport.base)
        self.assertTrue(shared_repo.has_revision(rev1))

    def test_sprout_controldir_repository_branch_both_under_shared(self):
        try:
            shared_repo = self.make_repository("shared", shared=True)
        except errors.IncompatibleFormat:
            raise TestNotApplicable("format does not support shared repositories")
        if not shared_repo._format.supports_nesting_repositories:
            raise TestNotApplicable("format does not support nesting repositories")
        tree = self.make_branch_and_tree("commit_tree")
        self.build_tree(["commit_tree/foo"])
        tree.add("foo")
        rev1 = tree.commit("revision 1")
        tree.controldir.open_branch().generate_revision_history(
            _mod_revision.NULL_REVISION
        )
        tree.set_parent_trees([])
        tree.commit("revision 2")
        tree.branch.repository.copy_content_into(shared_repo)
        dir = self.make_controldir("shared/source")
        dir.create_branch()
        target = dir.sprout(self.get_url("shared/target"))
        self.assertNotEqual(dir.transport.base, target.transport.base)
        self.assertNotEqual(dir.transport.base, shared_repo.controldir.transport.base)
        self.assertTrue(shared_repo.has_revision(rev1))

    def test_sprout_controldir_repository_branch_only_source_under_shared(self):
        try:
            shared_repo = self.make_repository("shared", shared=True)
        except errors.IncompatibleFormat:
            raise TestNotApplicable("format does not support shared repositories")
        if not shared_repo._format.supports_nesting_repositories:
            raise TestNotApplicable("format does not support nesting repositories")
        tree = self.make_branch_and_tree("commit_tree")
        self.build_tree(["commit_tree/foo"])
        tree.add("foo")
        rev1 = tree.commit("revision 1")
        tree.controldir.open_branch().generate_revision_history(
            _mod_revision.NULL_REVISION
        )
        tree.set_parent_trees([])
        tree.commit("revision 2")
        tree.branch.repository.copy_content_into(shared_repo)
        if shared_repo.make_working_trees():
            shared_repo.set_make_working_trees(False)
            self.assertFalse(shared_repo.make_working_trees())
        self.assertTrue(shared_repo.has_revision(rev1))
        dir = self.make_controldir("shared/source")
        dir.create_branch()
        target = dir.sprout(self.get_url("target"))
        self.assertNotEqual(dir.transport.base, target.transport.base)
        self.assertNotEqual(dir.transport.base, shared_repo.controldir.transport.base)
        branch = target.open_branch()
        # The sprouted controldir has a branch, so only revisions referenced by
        # that branch are copied, rather than the whole repository.  It's an
        # empty branch, so none are copied.
        self.assertEqual([], branch.repository.all_revision_ids())
        if branch.controldir._format.supports_workingtrees:
            self.assertTrue(branch.repository.make_working_trees())
        self.assertFalse(branch.repository.is_shared())

    def test_sprout_controldir_repository_under_shared_force_new_repo(self):
        tree = self.make_branch_and_tree("commit_tree")
        self.build_tree(["commit_tree/foo"])
        tree.add("foo")
        rev1 = tree.commit("revision 1")
        tree.controldir.open_branch().generate_revision_history(
            _mod_revision.NULL_REVISION
        )
        tree.set_parent_trees([])
        tree.commit("revision 2")
        source = self.make_repository("source")
        tree.branch.repository.copy_content_into(source)
        dir = source.controldir
        try:
            shared_repo = self.make_repository("target", shared=True)
        except errors.IncompatibleFormat:
            raise TestNotApplicable("format does not support shared repositories")
        target = dir.sprout(self.get_url("target/child"), force_new_repo=True)
        self.assertNotEqual(dir.control_transport.base, target.control_transport.base)
        self.assertFalse(shared_repo.has_revision(rev1))

    def test_sprout_controldir_repository_revision(self):
        # test for revision limiting, [smoke test, not corner case checks].
        # make a repository with some revisions,
        # and sprout it with a revision limit.
        #
        tree = self.make_branch_and_tree("commit_tree")
        self.build_tree(["commit_tree/foo"])
        tree.add("foo")
        tree.commit("revision 1")
        br = tree.controldir.open_branch()
        br.set_last_revision_info(0, _mod_revision.NULL_REVISION)
        tree.set_parent_trees([])
        rev2 = tree.commit("revision 2")
        source = self.make_repository("source")
        tree.branch.repository.copy_content_into(source)
        dir = source.controldir
        self.sproutOrSkip(dir, self.get_url("target"), revision_id=rev2)
        raise TestSkipped("revision limiting not strict yet")

    def test_sprout_controldir_branch_and_repo_shared(self):
        # sprouting a branch with a repo into a shared repo uses the shared
        # repo
        tree = self.make_branch_and_tree("commit_tree")
        self.build_tree(["commit_tree/foo"])
        tree.add("foo")
        rev1 = tree.commit("revision 1")
        source = self.make_branch("source")
        tree.branch.repository.copy_content_into(source.repository)
        tree.controldir.open_branch().copy_content_into(source)
        dir = source.controldir
        try:
            shared_repo = self.make_repository("target", shared=True)
        except errors.IncompatibleFormat:
            raise TestNotApplicable("format does not support shared repositories")
        dir.sprout(self.get_url("target/child"))
        self.assertTrue(shared_repo.has_revision(rev1))

    def test_sprout_controldir_branch_and_repo_shared_force_new_repo(self):
        # sprouting a branch with a repo into a shared repo uses the shared
        # repo
        tree = self.make_branch_and_tree("commit_tree")
        self.build_tree(["commit_tree/foo"])
        tree.add("foo")
        rev1 = tree.commit("revision 1")
        source = self.make_branch("source")
        tree.branch.repository.copy_content_into(source.repository)
        tree.controldir.open_branch().copy_content_into(source)
        dir = source.controldir
        try:
            shared_repo = self.make_repository("target", shared=True)
        except errors.IncompatibleFormat:
            raise TestNotApplicable("format does not support shared repositories")
        target = dir.sprout(self.get_url("target/child"), force_new_repo=True)
        self.assertNotEqual(dir.control_transport.base, target.control_transport.base)
        self.assertFalse(shared_repo.has_revision(rev1))

    def test_sprout_controldir_branch_reference(self):
        # sprouting should create a repository if needed and a sprouted branch.
        referenced_branch = self.make_branch("referenced")
        dir = self.make_controldir("source")
        try:
            dir.set_branch_reference(referenced_branch)
        except errors.IncompatibleFormat:
            raise TestNotApplicable("format does not support branch references")
        self.assertRaises(errors.NoRepositoryPresent, dir.open_repository)
        target = dir.sprout(self.get_url("target"))
        self.assertNotEqual(dir.transport.base, target.transport.base)
        # we want target to have a branch that is in-place.
        self.assertEqual(target, target.open_branch().controldir)
        # and as we dont support repositories being detached yet, a repo in
        # place
        target.open_repository()

    def test_sprout_controldir_branch_reference_shared(self):
        # sprouting should create a repository if needed and a sprouted branch.
        referenced_tree = self.make_branch_and_tree("referenced")
        rev1 = referenced_tree.commit("1", allow_pointless=True)
        dir = self.make_controldir("source")
        try:
            dir.set_branch_reference(referenced_tree.branch)
        except errors.IncompatibleFormat:
            raise TestNotApplicable("format does not support branch references")
        self.assertRaises(errors.NoRepositoryPresent, dir.open_repository)
        try:
            shared_repo = self.make_repository("target", shared=True)
        except errors.IncompatibleFormat:
            raise TestNotApplicable("format does not support shared repositories")
        target = dir.sprout(self.get_url("target/child"))
        self.assertNotEqual(dir.transport.base, target.transport.base)
        # we want target to have a branch that is in-place.
        self.assertEqual(target, target.open_branch().controldir)
        # and we want no repository as the target is shared
        self.assertRaises(errors.NoRepositoryPresent, target.open_repository)
        # and we want revision '1' in the shared repo
        self.assertTrue(shared_repo.has_revision(rev1))

    def test_sprout_controldir_branch_reference_shared_force_new_repo(self):
        # sprouting should create a repository if needed and a sprouted branch.
        referenced_tree = self.make_branch_and_tree("referenced")
        rev1 = referenced_tree.commit("1", allow_pointless=True)
        dir = self.make_controldir("source")
        try:
            dir.set_branch_reference(referenced_tree.branch)
        except errors.IncompatibleFormat:
            # this is ok too, not all formats have to support references.
            raise TestNotApplicable("format does not support branch references")
        self.assertRaises(errors.NoRepositoryPresent, dir.open_repository)
        try:
            shared_repo = self.make_repository("target", shared=True)
        except errors.IncompatibleFormat:
            raise TestNotApplicable("format does not support shared repositories")
        target = dir.sprout(self.get_url("target/child"), force_new_repo=True)
        self.assertNotEqual(dir.transport.base, target.transport.base)
        # we want target to have a branch that is in-place.
        self.assertEqual(target, target.open_branch().controldir)
        # and we want revision rev1 in the new repo
        self.assertTrue(target.open_repository().has_revision(rev1))
        # but not the shared one
        self.assertFalse(shared_repo.has_revision(rev1))

    def test_sprout_controldir_branch_revision(self):
        # test for revision limiting, [smoke test, not corner case checks].
        # make a repository with some revisions,
        # and sprout it with a revision limit.
        #
        tree = self.make_branch_and_tree("commit_tree")
        self.build_tree(["commit_tree/foo"])
        tree.add("foo")
        rev1 = tree.commit("revision 1")
        tree.commit("revision 2", allow_pointless=True)
        source = self.make_branch("source")
        tree.branch.repository.copy_content_into(source.repository)
        tree.controldir.open_branch().copy_content_into(source)
        dir = source.controldir
        target = dir.sprout(self.get_url("target"), revision_id=rev1)
        self.assertEqual(rev1, target.open_branch().last_revision())

    def test_sprout_controldir_branch_with_tags(self):
        # when sprouting a branch all revisions named in the tags are copied
        # too.
        builder = self.make_branch_builder("source")
        source, rev1, rev2 = fixtures.build_branch_with_non_ancestral_rev(builder)
        try:
            source.tags.set_tag("tag-a", rev2)
        except errors.TagsNotSupported:
            raise TestNotApplicable("Branch format does not support tags.")
        source.get_config_stack().set("branch.fetch_tags", True)
        # Now source has a tag not in its ancestry.  Sprout its controldir.
        dir = source.controldir
        target = dir.sprout(self.get_url("target"))
        # The tag is present, and so is its revision.
        new_branch = target.open_branch()
        self.assertEqual(rev2, new_branch.tags.lookup_tag("tag-a"))
        new_branch.repository.get_revision(rev2)

    def test_sprout_controldir_branch_with_absent_tag(self):
        # tags referencing absent revisions are copied (and those absent
        # revisions do not prevent the sprout.)
        builder = self.make_branch_builder("source")
        builder.build_commit(message="Rev 1")
        source = builder.get_branch()
        try:
            source.tags.set_tag("tag-a", b"missing-rev")
        except (errors.TagsNotSupported, errors.GhostTagsNotSupported):
            raise TestNotApplicable(
                "Branch format does not support tags or tags "
                "referencing ghost revisions."
            )
        # Now source has a tag pointing to an absent revision.  Sprout its
        # controldir.
        dir = source.controldir
        target = dir.sprout(self.get_url("target"))
        # The tag is present in the target
        new_branch = target.open_branch()
        self.assertEqual(b"missing-rev", new_branch.tags.lookup_tag("tag-a"))

    def test_sprout_controldir_passing_source_branch_with_absent_tag(self):
        # tags referencing absent revisions are copied (and those absent
        # revisions do not prevent the sprout.)
        builder = self.make_branch_builder("source")
        builder.build_commit(message="Rev 1")
        source = builder.get_branch()
        try:
            source.tags.set_tag("tag-a", b"missing-rev")
        except (errors.TagsNotSupported, errors.GhostTagsNotSupported):
            raise TestNotApplicable(
                "Branch format does not support tags or tags "
                "referencing missing revisions."
            )
        # Now source has a tag pointing to an absent revision.  Sprout its
        # controldir.
        dir = source.controldir
        target = dir.sprout(self.get_url("target"), source_branch=source)
        # The tag is present in the target
        new_branch = target.open_branch()
        self.assertEqual(b"missing-rev", new_branch.tags.lookup_tag("tag-a"))

    def test_sprout_controldir_passing_rev_not_source_branch_copies_tags(self):
        # dir.sprout(..., revision_id=b'rev1') copies rev1, and all the tags of
        # the branch at that controldir, the ancestry of all of those, but no other
        # revs (not even the tip of the source branch).
        builder = self.make_branch_builder("source")
        base_rev = builder.build_commit(message="Base")
        # Make three parallel lines of ancestry off this base.
        source = builder.get_branch()
        builder.build_commit(message="Rev A1")
        rev_a2 = builder.build_commit(message="Rev A2")
        builder.build_commit(message="Rev A3")
        source.set_last_revision_info(1, base_rev)
        rev_b1 = builder.build_commit(message="Rev B1")
        rev_b2 = builder.build_commit(message="Rev B2")
        builder.build_commit(message="Rev B3")
        source.set_last_revision_info(1, base_rev)
        rev_c1 = builder.build_commit(message="Rev C1")
        rev_c2 = builder.build_commit(message="Rev C2")
        builder.build_commit(message="Rev C3")
        # Set the branch tip to A2
        source.set_last_revision_info(3, rev_a2)
        try:
            # Create a tag for B2, and for an absent rev
            source.tags.set_tag("tag-non-ancestry", rev_b2)
        except errors.TagsNotSupported:
            raise TestNotApplicable("Branch format does not support tags ")
        try:
            source.tags.set_tag("tag-absent", b"absent-rev")
        except errors.GhostTagsNotSupported:
            has_ghost_tag = False
        else:
            has_ghost_tag = True
        source.get_config_stack().set("branch.fetch_tags", True)
        # And ask sprout for C2
        dir = source.controldir
        target = dir.sprout(self.get_url("target"), revision_id=rev_c2)
        # The tags are present
        new_branch = target.open_branch()
        if has_ghost_tag:
            self.assertEqual(
                {"tag-absent": b"absent-rev", "tag-non-ancestry": rev_b2},
                new_branch.tags.get_tag_dict(),
            )
        else:
            self.assertEqual(
                {"tag-non-ancestry": rev_b2}, new_branch.tags.get_tag_dict()
            )
        # And the revs for A2, B2 and C2's ancestries are present, but no
        # others.
        self.assertEqual(
            sorted([base_rev, rev_b1, rev_b2, rev_c1, rev_c2]),
            sorted(new_branch.repository.all_revision_ids()),
        )

    def test_sprout_controldir_tree_branch_reference(self):
        # sprouting should create a repository if needed and a sprouted branch.
        # the tree state should not be copied.
        referenced_branch = self.make_branch("referencced")
        dir = self.make_controldir("source")
        try:
            dir.set_branch_reference(referenced_branch)
        except errors.IncompatibleFormat:
            # this is ok too, not all formats have to support references.
            raise TestNotApplicable("format does not support branch references")
        self.assertRaises(errors.NoRepositoryPresent, dir.open_repository)
        tree = self.createWorkingTreeOrSkip(dir)
        self.build_tree(["source/subdir/"])
        tree.add("subdir")
        target = dir.sprout(self.get_url("target"))
        self.assertNotEqual(dir.transport.base, target.transport.base)
        # we want target to have a branch that is in-place.
        self.assertEqual(target, target.open_branch().controldir)
        # and as we dont support repositories being detached yet, a repo in
        # place
        target.open_repository()
        result_tree = target.open_workingtree()
        self.assertFalse(result_tree.has_filename("subdir"))

    def test_sprout_controldir_tree_branch_reference_revision(self):
        # sprouting should create a repository if needed and a sprouted branch.
        # the tree state should not be copied but the revision changed,
        # and the likewise the new branch should be truncated too
        referenced_branch = self.make_branch("referencced")
        dir = self.make_controldir("source")
        try:
            dir.set_branch_reference(referenced_branch)
        except errors.IncompatibleFormat:
            # this is ok too, not all formats have to support references.
            raise TestNotApplicable("format does not support branch references")
        self.assertRaises(errors.NoRepositoryPresent, dir.open_repository)
        tree = self.createWorkingTreeOrSkip(dir)
        self.build_tree(["source/foo"])
        tree.add("foo")
        rev1 = tree.commit("revision 1")
        tree.commit("revision 2", allow_pointless=True)
        target = dir.sprout(self.get_url("target"), revision_id=rev1)
        self.skipIfNoWorkingTree(target)
        self.assertNotEqual(dir.transport.base, target.transport.base)
        # we want target to have a branch that is in-place.
        self.assertEqual(target, target.open_branch().controldir)
        # and as we dont support repositories being detached yet, a repo in
        # place
        target.open_repository()
        # we trust that the working tree sprouting works via the other tests.
        self.assertEqual([rev1], target.open_workingtree().get_parent_ids())
        self.assertEqual(rev1, target.open_branch().last_revision())

    def test_sprout_controldir_tree_revision(self):
        # test for revision limiting, [smoke test, not corner case checks].
        # make a tree with a revision with a last-revision
        # and sprout it with a revision limit.
        # This smoke test just checks the revision-id is right. Tree specific
        # tests will check corner cases.
        tree = self.make_branch_and_tree("source")
        self.build_tree(["source/foo"])
        tree.add("foo")
        rev1 = tree.commit("revision 1")
        tree.commit("revision 2", allow_pointless=True)
        dir = tree.controldir
        target = self.sproutOrSkip(dir, self.get_url("target"), revision_id=rev1)
        self.assertEqual([rev1], target.open_workingtree().get_parent_ids())

    def test_sprout_takes_accelerator(self):
        tree = self.make_branch_and_tree("source")
        self.build_tree(["source/foo"])
        tree.add("foo")
        tree.commit("revision 1")
        rev2 = tree.commit("revision 2", allow_pointless=True)
        dir = tree.controldir
        target = self.sproutOrSkip(dir, self.get_url("target"), accelerator_tree=tree)
        self.assertEqual([rev2], target.open_workingtree().get_parent_ids())

    def test_sprout_branch_no_tree(self):
        tree = self.make_branch_and_tree("source")
        self.build_tree(["source/foo"])
        tree.add("foo")
        tree.commit("revision 1")
        tree.commit("revision 2", allow_pointless=True)
        dir = tree.controldir
        try:
            target = dir.sprout(self.get_url("target"), create_tree_if_local=False)
        except controldir.MustHaveWorkingTree:
            raise TestNotApplicable("control dir format requires working tree")
        self.assertPathDoesNotExist("target/foo")
        self.assertEqual(
            tree.branch.last_revision(), target.open_branch().last_revision()
        )

    def test_sprout_with_revision_id_uses_default_stack_on(self):
        # Make a branch with three commits to stack on.
        builder = self.make_branch_builder("stack-on")
        builder.start_series()
        rev1 = builder.build_commit(message="Rev 1.")
        builder.build_commit(message="Rev 2.")
        rev3 = builder.build_commit(message="Rev 3.")
        builder.finish_series()
        stack_on = builder.get_branch()
        # Make a controldir with a default stacking policy to stack on that branch.
        config = self.make_controldir("policy-dir").get_config()
        try:
            config.set_default_stack_on(self.get_url("stack-on"))
        except errors.BzrError:
            raise TestNotApplicable("Only relevant for stackable formats.")
        # Sprout the stacked-on branch into the controldir.
        sprouted = stack_on.controldir.sprout(
            self.get_url("policy-dir/sprouted"), revision_id=rev3
        )
        # Not all revisions are copied into the sprouted repository.
        repo = sprouted.open_repository()
        self.addCleanup(repo.lock_read().unlock)
        self.assertEqual(None, repo.get_parent_map([rev1]).get(rev1))

    def test_format_initialize_find_open(self):
        # loopback test to check the current format initializes to itself.
        if not self.bzrdir_format.is_initializable():
            # unsupported formats are not loopback testable
            # because the default open will not open them and
            # they may not be initializable.
            raise TestNotApplicable("format is not initializable")
        # for remote formats, there must be no prior assumption about the
        # network name to use - it's possible that this may somehow have got
        # in through an unisolated test though - see
        # <https://bugs.launchpad.net/bzr/+bug/504102>
        self.assertEqual(getattr(self.bzrdir_format, "_network_name", None), None)
        # supported formats must be able to init and open
        t = self.get_transport()
        readonly_t = self.get_readonly_transport()
        made_control = self.bzrdir_format.initialize(t.base)
        self.assertIsInstance(made_control, controldir.ControlDir)
        if isinstance(self.bzrdir_format, RemoteBzrDirFormat):
            return
        self.assertEqual(
            self.bzrdir_format, controldir.ControlDirFormat.find_format(readonly_t)
        )
        direct_opened_dir = self.bzrdir_format.open(readonly_t)
        opened_dir = controldir.ControlDir.open(t.base)
        self.assertEqual(made_control._format, opened_dir._format)
        self.assertEqual(direct_opened_dir._format, opened_dir._format)
        self.assertIsInstance(opened_dir, controldir.ControlDir)

    def test_format_initialize_on_transport_ex(self):
        t = self.get_transport("dir")
        self.assertInitializeEx(t)

    def test_format_initialize_on_transport_ex_use_existing_dir_True(self):
        t = self.get_transport("dir")
        t.ensure_base()
        self.assertInitializeEx(t, use_existing_dir=True)

    def test_format_initialize_on_transport_ex_use_existing_dir_False(self):
        if not self.bzrdir_format.is_initializable():
            raise TestNotApplicable("format is not initializable")
        t = self.get_transport("dir")
        t.ensure_base()
        self.assertRaises(
            transport.FileExists,
            self.bzrdir_format.initialize_on_transport_ex,
            t,
            use_existing_dir=False,
        )

    def test_format_initialize_on_transport_ex_create_prefix_True(self):
        t = self.get_transport("missing/dir")
        self.assertInitializeEx(t, create_prefix=True)

    def test_format_initialize_on_transport_ex_create_prefix_False(self):
        if not self.bzrdir_format.is_initializable():
            raise TestNotApplicable("format is not initializable")
        t = self.get_transport("missing/dir")
        self.assertRaises(
            transport.NoSuchFile, self.assertInitializeEx, t, create_prefix=False
        )

    def test_format_initialize_on_transport_ex_force_new_repo_True(self):
        t = self.get_transport("repo")
        repo_fmt = controldir.format_registry.make_controldir("1.9")
        repo_name = repo_fmt.repository_format.network_name()
        repo = repo_fmt.initialize_on_transport_ex(
            t, repo_format_name=repo_name, shared_repo=True
        )[0]
        made_repo, control = self.assertInitializeEx(
            t.clone("branch"), force_new_repo=True, repo_format_name=repo_name
        )
        self.assertNotEqual(
            repo.controldir.root_transport.base,
            made_repo.controldir.root_transport.base,
        )

    def test_format_initialize_on_transport_ex_force_new_repo_False(self):
        t = self.get_transport("repo")
        repo_fmt = controldir.format_registry.make_controldir("1.9")
        repo_name = repo_fmt.repository_format.network_name()
        repo = repo_fmt.initialize_on_transport_ex(
            t, repo_format_name=repo_name, shared_repo=True
        )[0]
        made_repo, control = self.assertInitializeEx(
            t.clone("branch"), force_new_repo=False, repo_format_name=repo_name
        )
        if not control._format.fixed_components:
            self.assertEqual(
                repo.controldir.root_transport.base,
                made_repo.controldir.root_transport.base,
            )

    def test_format_initialize_on_transport_ex_repo_fmt_name_None(self):
        t = self.get_transport("dir")
        repo, control = self.assertInitializeEx(t)
        self.assertEqual(None, repo)

    def test_format_initialize_on_transport_ex_repo_fmt_name_followed(self):
        t = self.get_transport("dir")
        # 1.6 is likely to never be default
        fmt = controldir.format_registry.make_controldir("1.6")
        repo_name = fmt.repository_format.network_name()
        repo, control = self.assertInitializeEx(t, repo_format_name=repo_name)
        if self.bzrdir_format.fixed_components:
            # must stay with the all-in-one-format.
            repo_name = self.bzrdir_format.network_name()
        self.assertEqual(repo_name, repo._format.network_name())

    def assertInitializeEx(self, t, **kwargs):
        """Execute initialize_on_transport_ex and check it succeeded correctly.

        This involves checking that the disk objects were created, open with
        the same format returned, and had the expected disk format.

        :param t: The transport to initialize on.
        :param **kwargs: Additional arguments to pass to
            initialize_on_transport_ex.
        :return: the resulting repo, control dir tuple.
        """
        if not self.bzrdir_format.is_initializable():
            raise TestNotApplicable("control dir format is not initializable")
        repo, control, require_stacking, repo_policy = (
            self.bzrdir_format.initialize_on_transport_ex(t, **kwargs)
        )
        if repo is not None:
            # Repositories are open write-locked
            self.assertTrue(repo.is_write_locked())
            self.addCleanup(repo.unlock)
        self.assertIsInstance(control, controldir.ControlDir)
        opened = controldir.ControlDir.open(t.base)
        expected_format = self.bzrdir_format
        if not isinstance(expected_format, RemoteBzrDirFormat):
            self.assertEqual(
                control._format.network_name(), expected_format.network_name()
            )
            self.assertEqual(
                control._format.network_name(), opened._format.network_name()
            )
        self.assertEqual(control.__class__, opened.__class__)
        return repo, control

    def test_format_network_name(self):
        # All control formats must have a network name.
        dir = self.make_controldir(".")
        format = dir._format
        # We want to test that the network_name matches the actual format on
        # disk. For local control dirsthat means that using network_name as a
        # key in the registry gives back the same format. For remote obects
        # we check that the network_name of the RemoteBzrDirFormat we have
        # locally matches the actual format present on disk.
        if isinstance(format, RemoteBzrDirFormat):
            dir._ensure_real()
            real_dir = dir._real_bzrdir
            network_name = format.network_name()
            self.assertEqual(real_dir._format.network_name(), network_name)
        else:
            registry = controldir.network_format_registry
            network_name = format.network_name()
            looked_up_format = registry.get(network_name)
            self.assertTrue(issubclass(format.__class__, looked_up_format.__class__))
        # The network name must be a byte string.
        self.assertIsInstance(network_name, bytes)

    def test_open_not_bzrdir(self):
        # test the formats specific behaviour for no-content or similar dirs.
        self.assertRaises(
            errors.NotBranchError,
            self.bzrdir_format.open,
            transport.get_transport_from_url(self.get_readonly_url()),
        )

    def test_create_branch(self):
        # a bzrdir can construct a branch and repository for itself.
        if not self.bzrdir_format.is_initializable():
            # unsupported formats are not loopback testable
            # because the default open will not open them and
            # they may not be initializable.
            raise TestNotApplicable("format is not initializable")
        t = self.get_transport()
        made_control = self.bzrdir_format.initialize(t.base)
        made_control.create_repository()
        made_branch = made_control.create_branch()
        self.assertIsInstance(made_branch, breezy.branch.Branch)
        self.assertEqual(made_control, made_branch.controldir)

    def test_create_branch_append_revisions_only(self):
        # a bzrdir can construct a branch and repository for itself.
        if not self.bzrdir_format.is_initializable():
            # unsupported formats are not loopback testable
            # because the default open will not open them and
            # they may not be initializable.
            raise TestNotApplicable("format is not initializable")
        t = self.get_transport()
        made_control = self.bzrdir_format.initialize(t.base)
        made_control.create_repository()
        try:
            made_branch = made_control.create_branch(append_revisions_only=True)
        except errors.UpgradeRequired:
            raise TestNotApplicable(
                "format does not support append_revisions_only setting"
            )
        self.assertIsInstance(made_branch, breezy.branch.Branch)
        self.assertEqual(True, made_branch.get_append_revisions_only())
        self.assertEqual(made_control, made_branch.controldir)

    def test_open_branch(self):
        if not self.bzrdir_format.is_initializable():
            # unsupported formats are not loopback testable
            # because the default open will not open them and
            # they may not be initializable.
            raise TestNotApplicable("format is not initializable")
        t = self.get_transport()
        made_control = self.bzrdir_format.initialize(t.base)
        made_control.create_repository()
        made_branch = made_control.create_branch()
        opened_branch = made_control.open_branch()
        self.assertEqual(made_control, opened_branch.controldir)
        self.assertIsInstance(opened_branch, made_branch.__class__)
        self.assertIsInstance(opened_branch._format, made_branch._format.__class__)

    def test_list_branches(self):
        if not self.bzrdir_format.is_initializable():
            raise TestNotApplicable("format is not initializable")
        t = self.get_transport()
        made_control = self.bzrdir_format.initialize(t.base)
        made_control.create_repository()
        made_branch = made_control.create_branch()
        branches = list(made_control.list_branches())
        self.assertEqual(1, len(branches))
        self.assertEqual(made_branch.base, branches[0].base)
        try:
            made_control.destroy_branch()
        except errors.UnsupportedOperation:
            pass  # Not all bzrdirs support destroying directories
        else:
            self.assertEqual([], made_control.list_branches())

    def test_get_branches(self):
        repo = self.make_repository("branch-1")
        repo.controldir.create_branch()
        self.assertEqual([""], list(repo.controldir.get_branches()))

    def test_branch_names(self):
        repo = self.make_repository("branch-1")
        repo.controldir.create_branch()
        self.assertEqual([""], repo.controldir.branch_names())

    def test_create_repository(self):
        # a bzrdir can construct a repository for itself.
        if not self.bzrdir_format.is_initializable():
            # unsupported formats are not loopback testable
            # because the default open will not open them and
            # they may not be initializable.
            raise TestNotApplicable("format is not initializable")
        t = self.get_transport()
        made_control = self.bzrdir_format.initialize(t.base)
        made_repo = made_control.create_repository()
        # Check that we have a repository object.
        made_repo.has_revision(b"foo")
        self.assertEqual(made_control, made_repo.controldir)

    def test_create_repository_shared(self):
        # a bzrdir can create a shared repository or
        # fail appropriately
        if not self.bzrdir_format.is_initializable():
            # unsupported formats are not loopback testable
            # because the default open will not open them and
            # they may not be initializable.
            raise TestNotApplicable("format is not initializable")
        t = self.get_transport()
        made_control = self.bzrdir_format.initialize(t.base)
        try:
            made_repo = made_control.create_repository(shared=True)
        except errors.IncompatibleFormat:
            # Old bzrdir formats don't support shared repositories
            # and should raise IncompatibleFormat
            raise TestNotApplicable("format does not support shared repositories")
        self.assertTrue(made_repo.is_shared())

    def test_create_repository_nonshared(self):
        # a bzrdir can create a non-shared repository
        if not self.bzrdir_format.is_initializable():
            # unsupported formats are not loopback testable
            # because the default open will not open them and
            # they may not be initializable.
            raise TestNotApplicable("format is not initializable")
        t = self.get_transport()
        made_control = self.bzrdir_format.initialize(t.base)
        try:
            made_repo = made_control.create_repository(shared=False)
        except errors.IncompatibleFormat:
            # Some control dir formats don't support non-shared repositories
            # and should raise IncompatibleFormat
            raise TestNotApplicable("format does not support shared repositories")
        self.assertFalse(made_repo.is_shared())

    def test_open_repository(self):
        if not self.bzrdir_format.is_initializable():
            # unsupported formats are not loopback testable
            # because the default open will not open them and
            # they may not be initializable.
            raise TestNotApplicable("format is not initializable")
        t = self.get_transport()
        made_control = self.bzrdir_format.initialize(t.base)
        made_repo = made_control.create_repository()
        opened_repo = made_control.open_repository()
        self.assertEqual(made_control, opened_repo.controldir)
        self.assertIsInstance(opened_repo, made_repo.__class__)
        self.assertIsInstance(opened_repo._format, made_repo._format.__class__)

    def test_create_workingtree(self):
        # a bzrdir can construct a working tree for itself.
        if not self.bzrdir_format.is_initializable():
            # unsupported formats are not loopback testable
            # because the default open will not open them and
            # they may not be initializable.
            raise TestNotApplicable("format is not initializable")
        t = self.get_transport()
        made_control = self.bzrdir_format.initialize(t.base)
        made_control.create_repository()
        made_control.create_branch()
        made_tree = self.createWorkingTreeOrSkip(made_control)
        self.assertIsInstance(made_tree, workingtree.WorkingTree)
        self.assertEqual(made_control, made_tree.controldir)

    def test_create_workingtree_revision(self):
        # a bzrdir can construct a working tree for itself @ a specific
        # revision.
        if not self.bzrdir_format.is_initializable():
            raise TestNotApplicable("format is not initializable")
        t = self.get_transport()
        source = self.make_branch_and_tree("source")
        a = source.commit("a", allow_pointless=True)
        source.commit("b", allow_pointless=True)
        t.mkdir("new")
        t_new = t.clone("new")
        made_control = self.bzrdir_format.initialize_on_transport(t_new)
        source.branch.repository.clone(made_control)
        source.branch.clone(made_control)
        try:
            made_tree = made_control.create_workingtree(revision_id=a)
        except (errors.NotLocalUrl, errors.UnsupportedOperation):
            raise TestSkipped("Can't make working tree on transport {!r}".format(t))
        self.assertEqual([a], made_tree.get_parent_ids())

    def test_open_workingtree(self):
        if not self.bzrdir_format.is_initializable():
            raise TestNotApplicable("format is not initializable")
        # this has to be tested with local access as we still support creating
        # format 6 bzrdirs
        t = self.get_transport()
        try:
            made_control = self.bzrdir_format.initialize(t.base)
            made_control.create_repository()
            made_control.create_branch()
            made_tree = made_control.create_workingtree()
        except (errors.NotLocalUrl, errors.UnsupportedOperation):
            raise TestSkipped(
                "Can't initialize {!r} on transport {!r}".format(self.bzrdir_format, t)
            )
        opened_tree = made_control.open_workingtree()
        self.assertEqual(made_control, opened_tree.controldir)
        self.assertIsInstance(opened_tree, made_tree.__class__)
        self.assertIsInstance(opened_tree._format, made_tree._format.__class__)

    def test_get_selected_branch(self):
        # The segment parameters are accessible from the root transport
        # if a URL with segment parameters is opened.
        if not self.bzrdir_format.is_initializable():
            raise TestNotApplicable("format is not initializable")
        t = self.get_transport()
        try:
            self.bzrdir_format.initialize(t.base)
        except (errors.NotLocalUrl, errors.UnsupportedOperation):
            raise TestSkipped(
                "Can't initialize {!r} on transport {!r}".format(self.bzrdir_format, t)
            )
        dir = controldir.ControlDir.open(t.base + ",branch=foo")
        self.assertEqual({"branch": "foo"}, dir.user_transport.get_segment_parameters())
        self.assertEqual("foo", dir._get_selected_branch())

    def test_get_selected_branch_none_selected(self):
        # _get_selected_branch defaults to None
        if not self.bzrdir_format.is_initializable():
            raise TestNotApplicable("format is not initializable")
        t = self.get_transport()
        try:
            self.bzrdir_format.initialize(t.base)
        except (errors.NotLocalUrl, errors.UnsupportedOperation):
            raise TestSkipped(
                "Can't initialize {!r} on transport {!r}".format(self.bzrdir_format, t)
            )
        dir = controldir.ControlDir.open(t.base)
        self.assertEqual("", dir._get_selected_branch())

    def test_root_transport(self):
        dir = self.make_controldir(".")
        self.assertEqual(dir.root_transport.base, self.get_transport().base)

    def test_find_repository_no_repo_under_standalone_branch(self):
        # finding a repo stops at standalone branches even if there is a
        # higher repository available.
        try:
            repo = self.make_repository(".", shared=True)
        except errors.IncompatibleFormat:
            # need a shared repository to test this.
            raise TestNotApplicable("requires shared repository support")
        if not repo._format.supports_nesting_repositories:
            raise TestNotApplicable("requires nesting repositories")
        url = self.get_url("intermediate")
        t = self.get_transport()
        t.mkdir("intermediate")
        t.mkdir("intermediate/child")
        made_control = self.bzrdir_format.initialize(url)
        made_control.create_repository()
        innermost_control = self.bzrdir_format.initialize(
            self.get_url("intermediate/child")
        )
        try:
            innermost_control.open_repository()
            # if there is a repository, then the format cannot ever hit this
            # code path.
            return
        except errors.NoRepositoryPresent:
            pass
        self.assertRaises(errors.NoRepositoryPresent, innermost_control.find_repository)

    def test_find_repository_containing_shared_repository(self):
        # find repo inside a shared repo with an empty control dir
        # returns the shared repo.
        try:
            repo = self.make_repository(".", shared=True)
        except errors.IncompatibleFormat:
            # need a shared repository to test this.
            raise TestNotApplicable("requires format with shared repository support")
        if not repo._format.supports_nesting_repositories:
            raise TestNotApplicable("requires support for nesting repositories")
        url = self.get_url("childbzrdir")
        self.get_transport().mkdir("childbzrdir")
        made_control = self.bzrdir_format.initialize(url)
        try:
            made_control.open_repository()
            # if there is a repository, then the format cannot ever hit this
            # code path.
            return
        except errors.NoRepositoryPresent:
            pass
        found_repo = made_control.find_repository()
        self.assertEqual(
            repo.controldir.root_transport.base,
            found_repo.controldir.root_transport.base,
        )

    def test_find_repository_standalone_with_containing_shared_repository(self):
        # find repo inside a standalone repo inside a shared repo finds the
        # standalone repo
        try:
            containing_repo = self.make_repository(".", shared=True)
        except errors.IncompatibleFormat:
            # need a shared repository to test this.
            raise TestNotApplicable("requires support for shared repositories")
        if not containing_repo._format.supports_nesting_repositories:
            raise TestNotApplicable("format does not support nesting repositories")
        child_repo = self.make_repository("childrepo")
        opened_control = controldir.ControlDir.open(self.get_url("childrepo"))
        found_repo = opened_control.find_repository()
        self.assertEqual(
            child_repo.controldir.root_transport.base,
            found_repo.controldir.root_transport.base,
        )

    def test_find_repository_shared_within_shared_repository(self):
        # find repo at a shared repo inside a shared repo finds the inner repo
        try:
            containing_repo = self.make_repository(".", shared=True)
        except errors.IncompatibleFormat:
            # need a shared repository to test this.
            raise TestNotApplicable("requires support for shared repositories")
        if not containing_repo._format.supports_nesting_repositories:
            raise TestNotApplicable("requires support for nesting repositories")
        url = self.get_url("childrepo")
        self.get_transport().mkdir("childrepo")
        child_control = self.bzrdir_format.initialize(url)
        child_repo = child_control.create_repository(shared=True)
        opened_control = controldir.ControlDir.open(self.get_url("childrepo"))
        found_repo = opened_control.find_repository()
        self.assertEqual(
            child_repo.controldir.root_transport.base,
            found_repo.controldir.root_transport.base,
        )
        self.assertNotEqual(
            child_repo.controldir.root_transport.base,
            containing_repo.controldir.root_transport.base,
        )

    def test_find_repository_with_nested_dirs_works(self):
        # find repo inside a bzrdir inside a bzrdir inside a shared repo
        # finds the outer shared repo.
        try:
            repo = self.make_repository(".", shared=True)
        except errors.IncompatibleFormat:
            # need a shared repository to test this.
            raise TestNotApplicable("requires support for shared repositories")
        if not repo._format.supports_nesting_repositories:
            raise TestNotApplicable("requires support for nesting repositories")
        url = self.get_url("intermediate")
        t = self.get_transport()
        t.mkdir("intermediate")
        t.mkdir("intermediate/child")
        made_control = self.bzrdir_format.initialize(url)
        try:
            made_control.open_repository()
            # if there is a repository, then the format cannot ever hit this
            # code path.
            return
        except errors.NoRepositoryPresent:
            pass
        innermost_control = self.bzrdir_format.initialize(
            self.get_url("intermediate/child")
        )
        try:
            innermost_control.open_repository()
            # if there is a repository, then the format cannot ever hit this
            # code path.
            return
        except errors.NoRepositoryPresent:
            pass
        found_repo = innermost_control.find_repository()
        self.assertEqual(
            repo.controldir.root_transport.base,
            found_repo.controldir.root_transport.base,
        )

    def test_can_and_needs_format_conversion(self):
        # check that we can ask an instance if its upgradable
        dir = self.make_controldir(".")
        if dir.can_convert_format():
            # if its default updatable there must be an updater
            # (we force the latest known format as downgrades may not be
            # available
            self.assertTrue(
                isinstance(
                    dir._format.get_converter(format=dir._format), controldir.Converter
                )
            )
        dir.needs_format_conversion(controldir.ControlDirFormat.get_default_format())

    def test_backup_copies_existing(self):
        tree = self.make_branch_and_tree("test")
        self.build_tree(["test/a"])
        tree.add(["a"])
        tree.commit("some data to be copied.")
        old_url, new_url = tree.controldir.backup_bzrdir()
        old_path = urlutils.local_path_from_url(old_url)
        new_path = urlutils.local_path_from_url(new_url)
        self.assertPathExists(old_path)
        self.assertPathExists(new_path)
        for ((dir_relpath1, _), entries1), ((dir_relpath2, _), entries2) in zip(
            osutils.walkdirs(old_path), osutils.walkdirs(new_path)
        ):
            self.assertEqual(dir_relpath1, dir_relpath2)
            for f1, f2 in zip(entries1, entries2):
                self.assertEqual(f1[0], f2[0])
                self.assertEqual(f1[2], f2[2])
                if f1[2] == "file":
                    with open(f1[4], "rb") as a, open(f2[4], "rb") as b:
                        osutils.compare_files(a, b)

    def test_upgrade_new_instance(self):
        """Does an available updater work?"""
        dir = self.make_controldir(".")
        # for now, upgrade is not ready for partial bzrdirs.
        dir.create_repository()
        dir.create_branch()
        self.createWorkingTreeOrSkip(dir)
        if dir.can_convert_format():
            # if its default updatable there must be an updater
            # (we force the latest known format as downgrades may not be
            # available
            with ui.ui_factory.nested_progress_bar() as pb:
                dir._format.get_converter(format=dir._format).convert(dir, pb)
            # and it should pass 'check' now.
            check.check_dwim(self.get_url("."), False, True, True)

    def test_format_description(self):
        dir = self.make_controldir(".")
        text = dir._format.get_format_description()
        self.assertTrue(len(text))


class TestBreakLock(TestCaseWithControlDir):
    def test_break_lock_empty(self):
        # break lock on an empty bzrdir should work silently.
        dir = self.make_controldir(".")
        try:
            dir.break_lock()
        except NotImplementedError:
            pass

    def test_break_lock_repository(self):
        # break lock with just a repo should unlock the repo.
        repo = self.make_repository(".")
        repo.lock_write()
        lock_repo = repo.controldir.open_repository()
        if not lock_repo.get_physical_lock_status():
            # This bzrdir's default repository does not physically lock things
            # and thus this interaction cannot be tested at the interface
            # level.
            repo.unlock()
            raise TestNotApplicable("format does not physically lock")
        # only one yes needed here: it should only be unlocking
        # the repo
        breezy.ui.ui_factory = CannedInputUIFactory([True])
        try:
            repo.controldir.break_lock()
        except NotImplementedError:
            # this bzrdir does not implement break_lock - so we cant test it.
            repo.unlock()
            raise TestNotApplicable("format does not support breaking locks")
        lock_repo.lock_write()
        lock_repo.unlock()
        self.assertRaises(errors.LockBroken, repo.unlock)

    def test_break_lock_branch(self):
        # break lock with just a repo should unlock the branch.
        # and not directly try the repository.
        # we test this by making a branch reference to a branch
        # and repository in another bzrdir
        # for pre-metadir formats this will fail, thats ok.
        master = self.make_branch("branch")
        thisdir = self.make_controldir("this")
        try:
            thisdir.set_branch_reference(master)
        except errors.IncompatibleFormat:
            raise TestNotApplicable("format does not support branch references")
        unused_repo = thisdir.create_repository()
        master.lock_write()
        with unused_repo.lock_write():
            # two yes's : branch and repository. If the repo in this
            # dir is inappropriately accessed, 3 will be needed, and
            # we'll see that because the stream will be fully consumed
            breezy.ui.ui_factory = CannedInputUIFactory([True, True, True])
            # determine if the repository will have been locked;
            this_repo_locked = thisdir.find_repository().get_physical_lock_status()
            try:
                master.controldir.break_lock()
            except NotImplementedError:
                # bzrdir does not support break_lock
                raise TestNotApplicable("format does not support breaking locks")
            if this_repo_locked:
                # only two ys should have been read
                self.assertEqual([True], breezy.ui.ui_factory.responses)
            else:
                # only one y should have been read
                self.assertEqual([True, True], breezy.ui.ui_factory.responses)
            # we should be able to lock a newly opened branch now
            branch = master.controldir.open_branch()
            branch.lock_write()
            branch.unlock()
            if this_repo_locked:
                # we should not be able to lock the repository in thisdir as
                # its still held by the explicit lock we took, and the break
                # lock should not have touched it.
                repo = thisdir.open_repository()
                self.assertRaises(errors.LockContention, repo.lock_write)
        self.assertRaises(errors.LockBroken, master.unlock)

    def test_break_lock_tree(self):
        # break lock with a tree should unlock the tree but not try the
        # branch explicitly. However this is very hard to test for as we
        # dont have a tree reference class, nor is one needed;
        # the worst case if this code unlocks twice is an extra question
        # being asked.
        tree = self.make_branch_and_tree(".")
        tree.lock_write()
        # three yes's : tree, branch and repository.
        breezy.ui.ui_factory = CannedInputUIFactory([True, True, True])
        try:
            tree.controldir.break_lock()
        except (NotImplementedError, errors.LockActive):
            # bzrdir does not support break_lock
            # or one of the locked objects (currently only tree does this)
            # raised a LockActive because we do still have a live locked
            # object.
            tree.unlock()
            raise TestNotApplicable("format does not support breaking locks")
        self.assertEqual([True], breezy.ui.ui_factory.responses)
        lock_tree = tree.controldir.open_workingtree()
        lock_tree.lock_write()
        lock_tree.unlock()
        self.assertRaises(errors.LockBroken, tree.unlock)


class TestTransportConfig(TestCaseWithControlDir):
    def test_get_config(self):
        my_dir = self.make_controldir(".")
        config = my_dir.get_config()
        try:
            config.set_default_stack_on("http://example.com")
        except errors.BzrError as e:
            if "Cannot set config" in str(e):
                self.assertFalse(
                    isinstance(my_dir, (_mod_bzrdir.BzrDirMeta1, RemoteBzrDir)),
                    "{!r} should support configs".format(my_dir),
                )
                raise TestNotApplicable("This BzrDir format does not support configs.")
            else:
                raise
        self.assertEqual("http://example.com", config.get_default_stack_on())
        my_dir2 = controldir.ControlDir.open(self.get_url("."))
        config2 = my_dir2.get_config()
        self.assertEqual("http://example.com", config2.get_default_stack_on())


class ChrootedControlDirTests(ChrootedTestCase):
    def test_find_repository_no_repository(self):
        # loopback test to check the current format fails to find a
        # share repository correctly.
        if not self.bzrdir_format.is_initializable():
            # unsupported formats are not loopback testable
            # because the default open will not open them and
            # they may not be initializable.
            raise TestNotApplicable("format is not initializable")
        # supported formats must be able to init and open
        # - do the vfs initialisation over the basic vfs transport
        # XXX: TODO this should become a 'bzrdirlocation' api call.
        self.get_vfs_only_url("subdir")
        transport.get_transport_from_url(self.get_vfs_only_url()).mkdir("subdir")
        made_control = self.bzrdir_format.initialize(self.get_url("subdir"))
        try:
            made_control.open_repository()
            # if there is a repository, then the format cannot ever hit this
            # code path.
            return
        except errors.NoRepositoryPresent:
            pass
        made_control = controldir.ControlDir.open(self.get_readonly_url("subdir"))
        self.assertRaises(errors.NoRepositoryPresent, made_control.find_repository)


class TestControlDirControlComponent(TestCaseWithControlDir):
    """ControlDir implementations adequately implement ControlComponent."""

    def test_urls(self):
        bd = self.make_controldir("bd")
        self.assertIsInstance(bd.user_url, str)
        self.assertEqual(bd.user_url, bd.user_transport.base)
        # for all current bzrdir implementations the user dir must be
        # above the control dir but we might need to relax that?
        self.assertEqual(bd.control_url.find(bd.user_url), 0)
        self.assertEqual(bd.control_url, bd.control_transport.base)
