← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~abentley/launchpad/upgrade-all into lp:launchpad

 

Aaron Bentley has proposed merging lp:~abentley/launchpad/upgrade-all into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~abentley/launchpad/upgrade-all/+merge/91894

= Summary =
Provide a script to upgrade all branches to 2a format, per RT 47986

== Proposed fix ==
This branch adds a script that can be used to upgrade all branches that aren't in 2a formats to 2a format.  This will resolve bug 828409.

== Pre-implementation notes ==
Discussed pre-Epic, so can't really remember

== Implementation details ==
This branch changes the way Launchpad is started up, so that Bazaar plugins are always loaded.  Not loading them sometimes was a mess.  This caused config to be loaded more often, which meant that sys.argv wasn't always present when it was loaded, so sys.argv is now optional

== Tests ==
bin/test -t test_get_real_branch_path -t test_upgrade

== Demo and Q/A ==
None

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/code/bzr.py
  lib/lp/codehosting/tests/test_upgrade.py
  lib/lp/codehosting/vfs/branchfs.py
  scripts/upgrade_all_branches.py
  lib/lp/codehosting/bzrutils.py
  lib/lp/codehosting/scripts/tests/test_upgrade_all_branches.py
  lib/lp/codehosting/upgrade.py
  lib/lp/testing/__init__.py
  lib/lp/codehosting/vfs/tests/test_branchfs.py
  lib/lp_sitecustomize.py
  lib/lp/services/config/__init__.py

./scripts/upgrade_all_branches.py
       5: '_pythonpath' imported but unused
-- 
https://code.launchpad.net/~abentley/launchpad/upgrade-all/+merge/91894
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~abentley/launchpad/upgrade-all into lp:launchpad.
=== modified file 'lib/lp/code/bzr.py'
--- lib/lp/code/bzr.py	2011-07-22 12:35:31 +0000
+++ lib/lp/code/bzr.py	2012-02-07 18:29:31 +0000
@@ -5,6 +5,7 @@
 
 __metaclass__ = type
 __all__ = [
+    'branch_changed',
     'BranchFormat',
     'ControlFormat',
     'CURRENT_BRANCH_FORMATS',
@@ -17,6 +18,8 @@
 # FIRST Ensure correct plugins are loaded. Do not delete this comment or the
 # line below this comment.
 import lp.codehosting
+# Silence lint warning.
+lp.codehosting
 
 from bzrlib.branch import (
     BranchReferenceFormat,
@@ -25,6 +28,10 @@
     BzrBranchFormat7,
     )
 from bzrlib.bzrdir import BzrDirMetaFormat1
+from bzrlib.errors import (
+    NotStacked,
+    UnstackableBranchFormat,
+    )
 from bzrlib.plugins.loom.branch import (
     BzrBranchLoomFormat1,
     BzrBranchLoomFormat6,
@@ -291,3 +298,21 @@
     return (ControlFormat.get_enum(control_string),
             BranchFormat.get_enum(branch_string),
             RepositoryFormat.get_enum(repository_string))
+
+
+def branch_changed(db_branch, bzr_branch=None):
+    """Mark a database branch as changed.
+
+    :param db_branch: The branch to mark changed.
+    :param bzr_branch: (optional) The bzr branch to use to mark the branch
+        changed.
+    """
+    if bzr_branch is None:
+        bzr_branch = db_branch.getBzrBranch()
+    try:
+        stacked_on = bzr_branch.get_stacked_on_url()
+    except (NotStacked, UnstackableBranchFormat):
+        stacked_on = None
+    last_revision = bzr_branch.last_revision()
+    formats = get_branch_formats(bzr_branch)
+    db_branch.branchChanged(stacked_on, last_revision, *formats)

=== modified file 'lib/lp/codehosting/bzrutils.py'
--- lib/lp/codehosting/bzrutils.py	2011-12-24 17:49:30 +0000
+++ lib/lp/codehosting/bzrutils.py	2012-02-07 18:29:31 +0000
@@ -329,3 +329,12 @@
         yield
     finally:
         branch.unlock()
+
+
+@contextmanager
+def server(server):
+    server.start_server()
+    try:
+        yield server
+    finally:
+        server.stop_server()

=== added file 'lib/lp/codehosting/scripts/tests/test_upgrade_all_branches.py'
--- lib/lp/codehosting/scripts/tests/test_upgrade_all_branches.py	1970-01-01 00:00:00 +0000
+++ lib/lp/codehosting/scripts/tests/test_upgrade_all_branches.py	2012-02-07 18:29:31 +0000
@@ -0,0 +1,81 @@
+# Copyright 2011-2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+
+import os.path
+import logging
+
+from bzrlib.repofmt.groupcompress_repo import RepositoryFormat2a
+import transaction
+
+from lp.code.bzr import branch_changed
+from lp.codehosting.upgrade import Upgrader
+from lp.testing import (
+    person_logged_in,
+    run_script,
+    TestCaseWithFactory,
+    )
+from lp.testing.layers import AppServerLayer
+
+
+class TestUpgradeAllBranchesScript(TestCaseWithFactory):
+
+    layer = AppServerLayer
+
+    def setUp(self):
+        super(TestUpgradeAllBranchesScript, self).setUp()
+        # useBzrBranches changes cwd
+        self.cwd = os.getcwd()
+
+    def upgrade_all_branches(self, target, finish=False):
+        """Run the script to upgrade all branches."""
+        transaction.commit()
+        if finish:
+            flags = ' --finish '
+        else:
+            flags = ' '
+        return run_script(
+            'scripts/upgrade_all_branches.py' + flags + target, cwd=self.cwd)
+
+    def prepare(self):
+        """Prepare to run the script."""
+        self.useBzrBranches(direct_database=True)
+        branch, tree = self.create_branch_and_tree(format='pack-0.92')
+        tree.commit('foo')
+        with person_logged_in(branch.owner):
+            branch_changed(branch, tree.branch)
+        target = self.makeTemporaryDirectory()
+        upgrader = Upgrader(branch, target, logging.getLogger(), tree.branch)
+        return upgrader
+
+    def test_start_upgrade(self):
+        """Test that starting the upgrade behaves as expected."""
+        upgrader = self.prepare()
+        stdout, stderr, retcode = self.upgrade_all_branches(
+            upgrader.target_dir)
+        self.assertIn(
+            'INFO    Upgrading branch %s' % upgrader.branch.unique_name,
+            stderr)
+        self.assertIn(
+            'INFO    Converting repository with fetch.', stderr)
+        self.assertIn(
+            'INFO    Skipped 0 already-upgraded branches.', stderr)
+        self.assertEqual(0, retcode)
+        upgraded = upgrader.get_bzrdir().open_repository()
+        self.assertIs(RepositoryFormat2a, upgraded._format.__class__)
+
+    def test_finish_upgrade(self):
+        """Test that finishing the upgrade behaves as expected."""
+        upgrader = self.prepare()
+        upgrader.start_upgrade()
+        stdout, stderr, retcode = self.upgrade_all_branches(
+            upgrader.target_dir, finish=True)
+        self.assertIn(
+            'INFO    Upgrading branch %s' % upgrader.branch.unique_name,
+            stderr)
+        self.assertEqual(0, retcode)
+        upgraded = upgrader.branch.getBzrBranch()
+        self.assertIs(
+            RepositoryFormat2a, upgraded.repository._format.__class__)

=== added file 'lib/lp/codehosting/tests/test_upgrade.py'
--- lib/lp/codehosting/tests/test_upgrade.py	1970-01-01 00:00:00 +0000
+++ lib/lp/codehosting/tests/test_upgrade.py	2012-02-07 18:29:31 +0000
@@ -0,0 +1,264 @@
+__metaclass__ = type
+
+import logging
+
+from bzrlib.branch import Branch
+from bzrlib.bzrdir import BzrDir, format_registry
+from bzrlib.plugins.loom.branch import loomify
+from bzrlib.repofmt.groupcompress_repo import (
+    RepositoryFormat2a, RepositoryFormat2aSubtree)
+from bzrlib.revision import NULL_REVISION
+from bzrlib.transport import get_transport
+
+from lp.code.bzr import (
+    BranchFormat,
+    branch_changed,
+    get_branch_formats,
+    RepositoryFormat,
+    )
+from lp.codehosting.bzrutils import read_locked
+from lp.codehosting.upgrade import (
+    Upgrader,
+    )
+from lp.testing import (
+    temp_dir,
+    TestCaseWithFactory,
+    )
+from lp.testing.layers import ZopelessDatabaseLayer
+
+
+class TestUpgrader(TestCaseWithFactory):
+
+    layer = ZopelessDatabaseLayer
+
+    def prepare(self, format='pack-0.92', loomify_branch=False):
+        """Prepare an upgrade test.
+
+        :param format: The branch format to use, as a string.
+        :param loomify_branch: If true, convert the branch to a loom.
+        """
+        self.useBzrBranches(direct_database=True)
+        branch, tree = self.create_branch_and_tree(format=format)
+        tree.commit(
+            'foo', rev_id='prepare-commit', committer='jrandom@xxxxxxxxxxx')
+        if loomify_branch:
+            loomify(tree.branch)
+            bzr_branch = tree.bzrdir.open_branch()
+        else:
+            bzr_branch = tree.branch
+        return self.getUpgrader(bzr_branch, branch)
+
+    def getUpgrader(self, bzr_branch, branch):
+        """Return an upgrader for the specified branches.
+
+        :param bzr_branch: the bzr branch to use.
+        :param branch: The DB branch to use.
+        """
+        target_dir = self.useContext(temp_dir())
+        return Upgrader(
+            branch, target_dir, logging.getLogger(), bzr_branch)
+
+    def addTreeReference(self, tree):
+        """Add a tree reference to a tree and commit.
+
+        :param tree: A Bazaar WorkingTree to add a tree to.
+        """
+        sub_branch = BzrDir.create_branch_convenience(
+            tree.bzrdir.root_transport.clone('sub').base)
+        tree.add_reference(sub_branch.bzrdir.open_workingtree())
+        tree.commit('added tree reference', committer='jrandom@xxxxxxxxxxx')
+
+    def check_branch(self, upgraded, branch_format=BranchFormat.BZR_BRANCH_7,
+                     repository_format=RepositoryFormat.BZR_CHK_2A):
+        """Check that a branch matches expected post-upgrade formats."""
+        control, branch, repository = get_branch_formats(upgraded)
+        self.assertEqual(repository, repository_format)
+        self.assertEqual(branch, branch_format)
+
+    def test_simple_upgrade(self):
+        """Upgrade a pack-0.92 branch."""
+        upgrader = self.prepare()
+        upgrader.start_upgrade()
+        upgrader.finish_upgrade()
+        self.check_branch(
+            upgrader.branch.getBzrBranch())
+
+    def test_subtree_upgrade(self):
+        """Upgrade a pack-0.92-subtree branch."""
+        upgrader = self.prepare('pack-0.92-subtree')
+        upgrader.start_upgrade()
+        upgrader.finish_upgrade()
+        self.check_branch(upgrader.branch.getBzrBranch())
+
+    def test_upgrade_loom(self):
+        """Upgrade a loomified pack-0.92 branch."""
+        upgrader = self.prepare(loomify_branch=True)
+        upgrader.start_upgrade()
+        upgrader.finish_upgrade()
+        upgraded = upgrader.branch.getBzrBranch()
+        self.check_branch(upgraded, BranchFormat.BZR_LOOM_2)
+
+    def test_upgrade_subtree_loom(self):
+        """Upgrade a loomified pack-0.92-subtree branch."""
+        upgrader = self.prepare('pack-0.92-subtree', loomify_branch=True)
+        upgrader.start_upgrade()
+        upgrader.finish_upgrade()
+        upgraded = upgrader.branch.getBzrBranch()
+        self.check_branch(upgraded, BranchFormat.BZR_LOOM_2)
+
+    def test_default_repo_format(self):
+        """By default, the 2a repo format is selected."""
+        upgrader = self.prepare()
+        target_format = upgrader.get_target_format()
+        self.assertIs(
+            target_format._repository_format.__class__, RepositoryFormat2a)
+
+    def test_subtree_format_repo_format(self):
+        """Even subtree formats use 2a if they don't have tree references."""
+        self.useBzrBranches(direct_database=True)
+        format = format_registry.make_bzrdir('pack-0.92-subtree')
+        branch, tree = self.create_branch_and_tree(format=format)
+        upgrader = self.getUpgrader(tree.branch, branch)
+        with read_locked(upgrader.bzr_branch):
+            target_format = upgrader.get_target_format()
+        self.assertIs(
+            target_format._repository_format.__class__, RepositoryFormat2a)
+
+    def test_tree_reference_repo_format(self):
+        """Repos with tree references get 2aSubtree."""
+        self.useBzrBranches(direct_database=True)
+        format = format_registry.make_bzrdir('pack-0.92-subtree')
+        branch, tree = self.create_branch_and_tree(format=format)
+        upgrader = self.getUpgrader(tree.branch, branch)
+        self.addTreeReference(tree)
+        with read_locked(upgrader.bzr_branch):
+            target_format = upgrader.get_target_format()
+        self.assertIs(
+            target_format._repository_format.__class__,
+            RepositoryFormat2aSubtree)
+
+    def test_add_upgraded_branch_preserves_tip(self):
+        """Fetch-based upgrade preserves branch tip."""
+        upgrader = self.prepare('pack-0.92-subtree')
+        with read_locked(upgrader.bzr_branch):
+            upgrader.start_upgrade()
+            upgraded = upgrader.add_upgraded_branch().open_branch()
+        self.assertEqual('prepare-commit', upgraded.last_revision())
+
+    def test_create_upgraded_repository_preserves_dead_heads(self):
+        """Fetch-based upgrade preserves heads in the repository."""
+        upgrader = self.prepare('pack-0.92-subtree')
+        upgrader.bzr_branch.set_last_revision_info(0, NULL_REVISION)
+        with read_locked(upgrader.bzr_branch):
+            upgrader.create_upgraded_repository()
+        upgraded = upgrader.get_bzrdir().open_repository()
+        self.assertEqual(
+            'foo', upgraded.get_revision('prepare-commit').message)
+
+    def test_create_upgraded_repository_uses_target_subdir(self):
+        """The repository is created in the right place."""
+        upgrader = self.prepare()
+        with read_locked(upgrader.bzr_branch):
+            upgrader.create_upgraded_repository()
+        upgrader.get_bzrdir().open_repository()
+
+    def test_add_upgraded_branch_preserves_tags(self):
+        """Fetch-based upgrade preserves heads in the repository."""
+        upgrader = self.prepare('pack-0.92-subtree')
+        upgrader.bzr_branch.tags.set_tag('steve', 'rev-id')
+        with read_locked(upgrader.bzr_branch):
+            upgrader.start_upgrade()
+            upgraded = upgrader.add_upgraded_branch().open_branch()
+        self.assertEqual('rev-id', upgraded.tags.lookup_tag('steve'))
+
+    def test_has_tree_references(self):
+        """Detects whether repo contains actual tree references."""
+        self.useBzrBranches(direct_database=True)
+        format = format_registry.make_bzrdir('pack-0.92-subtree')
+        branch, tree = self.create_branch_and_tree(format=format)
+        upgrader = self.getUpgrader(tree.branch, branch)
+        with read_locked(tree.branch.repository):
+            self.assertFalse(upgrader.has_tree_references())
+        self.addTreeReference(tree)
+        with read_locked(tree.branch.repository):
+            self.assertTrue(upgrader.has_tree_references())
+
+    def test_use_subtree_format_for_tree_references(self):
+        """Subtree references cause RepositoryFormat2aSubtree to be used."""
+        self.useBzrBranches(direct_database=True)
+        format = format_registry.make_bzrdir('pack-0.92-subtree')
+        branch, tree = self.create_branch_and_tree(format=format)
+        sub_branch = BzrDir.create_branch_convenience(
+            tree.bzrdir.root_transport.clone('sub').base, format=format)
+        tree.add_reference(sub_branch.bzrdir.open_workingtree())
+        tree.commit('added tree reference', committer='jrandom@xxxxxxxxxxx')
+        upgrader = self.getUpgrader(tree.branch, branch)
+        with read_locked(tree.branch):
+            upgrader.create_upgraded_repository()
+        upgraded = upgrader.get_bzrdir().open_repository()
+        self.assertIs(RepositoryFormat2aSubtree, upgraded._format.__class__)
+
+    def test_swap_in(self):
+        """Swap in swaps a branch into the original place."""
+        upgrader = self.prepare()
+        upgrader.start_upgrade()
+        upgrader.add_upgraded_branch()
+        upgrader.swap_in()
+        self.check_branch(upgrader.branch.getBzrBranch())
+
+    def test_swap_in_retains_original(self):
+        """Swap in retains the original branch in backup.bzr."""
+        upgrader = self.prepare()
+        upgrader.start_upgrade()
+        upgrader.add_upgraded_branch()
+        upgrader.swap_in()
+        t = get_transport(upgrader.branch.getInternalBzrUrl())
+        t = t.clone('backup.bzr')
+        branch = Branch.open_from_transport(t)
+        self.check_branch(branch, BranchFormat.BZR_BRANCH_6,
+                          RepositoryFormat.BZR_KNITPACK_1)
+
+    def test_start_all_upgrades(self):
+        """Start all upgrades starts upgrading all branches."""
+        upgrader = self.prepare()
+        branch_changed(upgrader.branch, upgrader.bzr_branch)
+        Upgrader.start_all_upgrades(
+            upgrader.target_dir, upgrader.logger)
+        upgraded = upgrader.get_bzrdir().open_repository()
+        self.assertIs(RepositoryFormat2a, upgraded._format.__class__)
+        self.assertEqual(
+            'foo', upgraded.get_revision('prepare-commit').message)
+
+    def test_finish_upgrade_fetches(self):
+        """finish_upgrade fetches new changes into the branch."""
+        upgrader = self.prepare()
+        upgrader.start_upgrade()
+        tree = upgrader.bzr_branch.create_checkout('tree', lightweight=True)
+        bar_id = tree.commit('bar', committer='jrandom@xxxxxxxxxxx')
+        upgrader.finish_upgrade()
+        upgraded = upgrader.branch.getBzrBranch()
+        self.assertEqual(
+            'bar', upgraded.repository.get_revision(bar_id).message)
+
+    def test_finish_upgrade_updates_formats(self):
+        """finish_upgrade updates branch and repository formats."""
+        upgrader = self.prepare()
+        upgrader.start_upgrade()
+        upgrader.finish_upgrade()
+        self.assertEqual(
+            upgrader.branch.branch_format, BranchFormat.BZR_BRANCH_7)
+        self.assertEqual(
+            upgrader.branch.repository_format, RepositoryFormat.BZR_CHK_2A)
+
+    def test_finish_all_upgrades(self):
+        """Finish all upgrades behaves as expected."""
+        upgrader = self.prepare()
+        branch_changed(upgrader.branch, upgrader.bzr_branch)
+        upgrader.start_upgrade()
+        Upgrader.finish_all_upgrades(
+            upgrader.target_dir, upgrader.logger)
+        upgraded = upgrader.branch.getBzrBranch()
+        self.assertIs(RepositoryFormat2a,
+            upgraded.repository._format.__class__)
+        self.assertEqual(
+            'foo', upgraded.repository.get_revision('prepare-commit').message)

=== added file 'lib/lp/codehosting/upgrade.py'
--- lib/lp/codehosting/upgrade.py	1970-01-01 00:00:00 +0000
+++ lib/lp/codehosting/upgrade.py	2012-02-07 18:29:31 +0000
@@ -0,0 +1,200 @@
+# Copyright 2011-2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Provide Upgrader to upgrade any branch to a 2a format.
+
+Provides special support for looms and subtree formats.
+
+Repositories that have no tree references are always upgraded to the standard
+2a format, even if they are in a subtree-supporting format.  Repositories that
+actually have tree references are converted to RepositoryFormat2aSubtree.
+"""
+
+__metaclass__ = type
+
+__all__ = ['Upgrader']
+
+import os
+from shutil import rmtree
+from tempfile import mkdtemp
+
+from bzrlib.bzrdir import BzrDir, format_registry
+from bzrlib.errors import UpToDateFormat
+from bzrlib.plugins.loom.formats import (
+    NotALoom,
+    require_loom_branch,
+    )
+from bzrlib.repofmt.groupcompress_repo import RepositoryFormat2aSubtree
+from bzrlib.upgrade import upgrade
+
+from lp.services.database.lpstorm import IStore
+from lp.code.bzr import (
+    branch_changed,
+    RepositoryFormat,
+    )
+from lp.code.model.branch import Branch
+from lp.codehosting.bzrutils import read_locked
+from lp.codehosting.vfs.branchfs import get_real_branch_path
+
+
+class AlreadyUpgraded(Exception):
+    """Attempted to upgrade a branch that had already been upgraded."""
+
+
+class Upgrader:
+    """Upgrades branches to 2a-based formats if possible."""
+
+    def __init__(self, branch, target_dir, logger, bzr_branch=None):
+        self.branch = branch
+        self.bzr_branch = bzr_branch
+        if self.bzr_branch is None:
+            self.bzr_branch = self.branch.getBzrBranch()
+        self.target_dir = target_dir
+        self.target_subdir = os.path.join(
+            self.target_dir, str(self.branch.id))
+        self.logger = logger
+
+    def get_bzrdir(self):
+        """Return the target_subdir bzrdir."""
+        return BzrDir.open(self.target_subdir)
+
+    def get_target_format(self):
+        """Return the format to upgrade a branch to.
+
+        The repository format is always upgraded to a 2a format, but
+        the branch format is left alone if the branch is a loom.
+        :param branch: The bzr branch to upgrade
+        :return: A Metadir format instance.
+        """
+        format = format_registry.make_bzrdir('2a')
+        try:
+            require_loom_branch(self.bzr_branch)
+        except NotALoom:
+            pass
+        else:
+            format._branch_format = self.bzr_branch._format
+        if getattr(
+            self.bzr_branch.repository._format, 'supports_tree_reference',
+            False):
+            if self.has_tree_references():
+                format._repository_format = RepositoryFormat2aSubtree()
+        return format
+
+    @classmethod
+    def iter_upgraders(cls, target_dir, logger):
+        """Iterate through Upgraders given a target and logger."""
+        store = IStore(Branch)
+        branches = store.find(
+            Branch, Branch.repository_format != RepositoryFormat.BZR_CHK_2A)
+        branches.order_by(Branch.unique_name)
+        for branch in branches:
+            logger.info(
+                'Upgrading branch %s (%d)', branch.unique_name,
+                branch.id)
+            yield cls(branch, target_dir, logger)
+
+    @classmethod
+    def start_all_upgrades(cls, target_dir, logger):
+        """Upgrade listed branches to a target directory.
+
+        :param branches: The Launchpad Branches to upgrade.
+        :param target_dir: The directory to store upgraded versions in.
+        """
+        skipped = 0
+        for upgrader in cls.iter_upgraders(target_dir, logger):
+            try:
+                upgrader.start_upgrade()
+            except AlreadyUpgraded:
+                skipped += 1
+        logger.info('Skipped %d already-upgraded branches.', skipped)
+
+    @classmethod
+    def finish_all_upgrades(cls, target_dir, logger):
+        """Upgrade listed branches to a target directory.
+
+        :param branches: The Launchpad Branches to upgrade.
+        :param target_dir: The directory to store upgraded versions in.
+        """
+        for upgrader in cls.iter_upgraders(target_dir, logger):
+            upgrader.finish_upgrade()
+
+    def finish_upgrade(self):
+        """Create an upgraded version of self.branch in self.target_dir."""
+        with read_locked(self.bzr_branch):
+            repository = self.get_bzrdir().open_repository()
+            self.add_upgraded_branch()
+            repository.fetch(self.bzr_branch.repository)
+        self.swap_in()
+        branch_changed(self.branch)
+
+    def add_upgraded_branch(self):
+        """Add an upgraded branch to the target_subdir.
+
+        self.branch's branch (but not repository) is mirrored to the BzrDir
+        and then the bzrdir is upgraded in the normal way.
+        """
+        bd = self.get_bzrdir()
+        self.mirror_branch(self.bzr_branch, bd)
+        try:
+            exceptions = upgrade(
+                bd.root_transport.base, self.get_target_format())
+            if exceptions:
+                if len(exceptions) == 1:
+                    # Compatibility with historical behavior
+                    raise exceptions[0]
+                else:
+                    return 3
+        except UpToDateFormat:
+            pass
+        return bd
+
+    def start_upgrade(self):
+        """Do the slow part of the upgrade process."""
+        if os.path.exists(self.target_subdir):
+            raise AlreadyUpgraded
+        with read_locked(self.bzr_branch):
+            self.create_upgraded_repository()
+
+    def create_upgraded_repository(self):
+        """Create a repository in an upgraded format.
+
+        :param upgrade_dir: The directory to create the repository in.
+        :return: The created repository.
+        """
+        self.logger.info('Converting repository with fetch.')
+        upgrade_dir = mkdtemp(dir=self.target_dir)
+        try:
+            bzrdir = BzrDir.create(upgrade_dir, self.get_target_format())
+            repository = bzrdir.create_repository()
+            repository.fetch(self.bzr_branch.repository)
+        except:
+            rmtree(upgrade_dir)
+            raise
+        else:
+            os.rename(upgrade_dir, self.target_subdir)
+
+    def swap_in(self):
+        """Swap the upgraded branch into place."""
+        real_location = get_real_branch_path(self.branch.id)
+        backup_dir = os.path.join(self.target_subdir, 'backup.bzr')
+        os.rename(real_location, backup_dir)
+        os.rename(self.target_subdir, real_location)
+
+    def has_tree_references(self):
+        """Determine whether the repository contains tree references.
+
+        :return: True if it contains tree references, False otherwise.
+        """
+        repo = self.bzr_branch.repository
+        revision_ids = repo.all_revision_ids()
+        for tree in repo.revision_trees(revision_ids):
+            for path, entry in tree.iter_entries_by_dir():
+                if entry.kind == 'tree-reference':
+                    return True
+        return False
+
+    def mirror_branch(self, bzr_branch, target_bd):
+        """Mirror the actual branch from a bzr_branch to a target bzrdir."""
+        target = target_bd.get_branch_transport(bzr_branch._format)
+        source = bzr_branch.bzrdir.get_branch_transport(bzr_branch._format)
+        source.copy_tree_to_transport(target)

=== modified file 'lib/lp/codehosting/vfs/branchfs.py'
--- lib/lp/codehosting/vfs/branchfs.py	2012-01-01 02:58:52 +0000
+++ lib/lp/codehosting/vfs/branchfs.py	2012-02-07 18:29:31 +0000
@@ -49,12 +49,14 @@
     'branch_id_to_path',
     'DirectDatabaseLaunchpadServer',
     'get_lp_server',
+    'get_real_branch_path',
     'get_ro_server',
     'get_rw_server',
     'LaunchpadInternalServer',
     'LaunchpadServer',
     ]
 
+import os.path
 import sys
 import xmlrpclib
 
@@ -201,6 +203,17 @@
             'lp-internal:///', codehosting_endpoint, transport)
 
 
+def get_real_branch_path(branch_id):
+    """Return the on-disk location of a branch.
+
+    This should be used only when local filesystem operations are required.
+    For branch access, get_rw_server should be used.
+    :param branch_id: The integer id of the branch in the database.
+    """
+    root = config.codehosting.mirrored_branches_root
+    return os.path.join(root, branch_id_to_path(branch_id))
+
+
 class ITransportDispatch(Interface):
     """Turns descriptions of transports into transports."""
 

=== modified file 'lib/lp/codehosting/vfs/tests/test_branchfs.py'
--- lib/lp/codehosting/vfs/tests/test_branchfs.py	2012-01-23 14:08:18 +0000
+++ lib/lp/codehosting/vfs/tests/test_branchfs.py	2012-02-07 18:29:31 +0000
@@ -64,12 +64,14 @@
     BranchTransportDispatch,
     DirectDatabaseLaunchpadServer,
     get_lp_server,
+    get_real_branch_path,
     LaunchpadInternalServer,
     LaunchpadServer,
     TransportDispatch,
     UnknownTransportType,
     )
 from lp.codehosting.vfs.transport import AsyncVirtualTransport
+from lp.services.config import config
 from lp.services.job.runner import TimeoutError
 from lp.services.webapp import errorlog
 from lp.testing import (
@@ -1196,3 +1198,14 @@
         lp_server = get_lp_server(1, 'http://xmlrpc.example.invalid', '')
         transport = lp_server._transport_dispatch._rw_dispatch.base_transport
         self.assertIsInstance(transport, ChrootTransport)
+
+
+class TestRealBranchLocation(TestCase):
+
+    def test_get_real_branch_path(self):
+        """Correctly calculates the on-disk location of a branch."""
+        path = get_real_branch_path(0x00abcdef)
+        self.assertTrue(path.startswith(
+            config.codehosting.mirrored_branches_root))
+        tail = path[len(config.codehosting.mirrored_branches_root):]
+        self.assertEqual('/00/ab/cd/ef', tail)

=== modified file 'lib/lp/services/config/__init__.py'
--- lib/lp/services/config/__init__.py	2012-02-01 14:30:17 +0000
+++ lib/lp/services/config/__init__.py	2012-02-07 18:29:31 +0000
@@ -105,18 +105,26 @@
         :param instance_name: the configuration instance to use. Defaults to
             the value of the LPCONFIG environment variable.
         :param process_name: the process configuration name to use. Defaults
-            to the basename of sys.argv[0] without any extension.
+            to the basename of sys.argv[0] without any extension, or None if
+            sys.argv is not available.
        """
         self._config = None
         if instance_name is None:
             instance_name = find_instance_name()
 
         if process_name is None:
-            process_name = os.path.splitext(os.path.basename(sys.argv[0]))[0]
+            self._process_name = self._make_process_name()
+        else:
+            self._process_name = process_name
         self._instance_name = instance_name
-        self._process_name = process_name
         self.root = TREE_ROOT
 
+    def _make_process_name(self):
+        if getattr(sys, 'argv', None) is None:
+            return None
+        basename = os.path.basename(sys.argv[0])
+        return os.path.splitext(basename)[0]
+
     @property
     def instance_name(self):
         """Return the config's instance name.
@@ -172,6 +180,8 @@
         LaunchpadConfig loads the conf file named for the process. When
         the conf file does not exist, it loads launchpad-lazr.conf instead.
         """
+        if self._process_name is None:
+            self._process_name = self._make_process_name()
         return self._process_name
 
     def setProcess(self, process_name):

=== modified file 'lib/lp/testing/__init__.py'
--- lib/lp/testing/__init__.py	2012-01-20 15:42:44 +0000
+++ lib/lp/testing/__init__.py	2012-02-07 18:29:31 +0000
@@ -1190,7 +1190,7 @@
         now += delta
 
 
-def run_script(cmd_line, env=None):
+def run_script(cmd_line, env=None, cwd=None):
     """Run the given command line as a subprocess.
 
     :param cmd_line: A command line suitable for passing to
@@ -1206,7 +1206,7 @@
     env.pop('PYTHONPATH', None)
     process = subprocess.Popen(
         cmd_line, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
-        stderr=subprocess.PIPE, env=env)
+        stderr=subprocess.PIPE, env=env, cwd=cwd)
     (out, err) = process.communicate()
     return out, err, process.returncode
 

=== modified file 'lib/lp_sitecustomize.py'
--- lib/lp_sitecustomize.py	2012-01-03 05:05:39 +0000
+++ lib/lp_sitecustomize.py	2012-02-07 18:29:31 +0000
@@ -19,6 +19,12 @@
 import zope.publisher.browser
 from zope.security import checker
 
+# Load bzr plugins
+import lp.codehosting
+lp.codehosting
+# Force LoomBranch classes to be listed as subclasses of Branch
+import bzrlib.plugins.loom.branch
+bzrlib.plugins.loom.branch
 from lp.services.log import loglevels
 from lp.services.log.logger import LaunchpadLogger
 from lp.services.log.mappingfilter import MappingFilter

=== added file 'scripts/upgrade_all_branches.py'
--- scripts/upgrade_all_branches.py	1970-01-01 00:00:00 +0000
+++ scripts/upgrade_all_branches.py	2012-02-07 18:29:31 +0000
@@ -0,0 +1,38 @@
+#!/usr/bin/python -S
+
+__metaclass__ = type
+
+import _pythonpath
+
+import sys
+from lp.codehosting.upgrade import Upgrader
+from lp.codehosting.bzrutils import server
+from lp.codehosting.vfs.branchfs import get_rw_server
+from lp.services.scripts.base import LaunchpadScript, LaunchpadScriptFailure
+
+
+class UpgradeAllBranches(LaunchpadScript):
+
+    def add_my_options(self):
+        self.parser.add_option(
+            '--finish', action="store_true",
+            help=("Finish the upgrade and move the new branches into place."))
+
+    def main(self):
+        if len(self.args) < 1:
+            raise LaunchpadScriptFailure('Please specify a target directory.')
+        if len(self.args) > 1:
+            raise LaunchpadScriptFailure('Too many arguments.')
+        target_dir = self.args[0]
+        with server(get_rw_server()):
+            if self.options.finish:
+                Upgrader.finish_all_upgrades(target_dir, self.logger)
+            else:
+                Upgrader.start_all_upgrades(target_dir, self.logger)
+
+
+if __name__ == "__main__":
+    sys.stderr.write(repr(sys.argv))
+    script = UpgradeAllBranches(
+        "upgrade-all-branches", dbuser='upgrade-branches')
+    script.lock_and_run()


Follow ups