Rev 5576: Smoother upgrades in http://bazaar.launchpad.net/~vila/bzr/integration/

Vincent Ladeuil v.ladeuil+lp at free.fr
Mon Dec 20 11:39:43 GMT 2010


At http://bazaar.launchpad.net/~vila/bzr/integration/

------------------------------------------------------------
revno: 5576 [merge]
revision-id: v.ladeuil+lp at free.fr-20101220113942-niw8oud3yl7vhi1x
parent: pqm at pqm.ubuntu.com-20101220012020-jir3swsn7nxpp2jk
parent: v.ladeuil+lp at free.fr-20101218183200-t7u3dysnp37v6fkv
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: trunk
timestamp: Mon 2010-12-20 12:39:42 +0100
message:
  Smoother upgrades
modified:
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/bzrdir.py               bzrdir.py-20060131065624-156dfea39c4387cb
  bzrlib/tests/blackbox/test_upgrade.py test_upgrade.py-20060120060132-b41e5ed2f886ad28
  bzrlib/tests/test_upgrade.py   test_upgrade.py-20051004040251-555fe1d2bae1bc71
  bzrlib/upgrade.py              history2weaves.py-20050818063535-e7d319791c19a8b2
  doc/en/release-notes/bzr-2.3.txt NEWS-20050323055033-4e00b5db738777ff
  doc/en/upgrade-guide/data_migration.txt data_migration.txt-20090702082510-q2pocf7uhntljqnl-2
  doc/en/upgrade-guide/index.txt index.txt-20090702082510-q2pocf7uhntljqnl-3
  doc/en/upgrade-guide/overview.txt overview.txt-20090702082510-q2pocf7uhntljqnl-4
  doc/en/whats-new/whats-new-in-2.3.txt whatsnewin2.3.txt-20100818072501-x2h25r7jbnknvy30-1
-------------- next part --------------
=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2010-12-08 10:19:59 +0000
+++ b/bzrlib/builtins.py	2010-12-20 11:39:42 +0000
@@ -3328,27 +3328,62 @@
 
 
 class cmd_upgrade(Command):
-    __doc__ = """Upgrade branch storage to current format.
-
-    The check command or bzr developers may sometimes advise you to run
-    this command. When the default format has changed you may also be warned
-    during other operations to upgrade.
+    __doc__ = """Upgrade a repository, branch or working tree to a newer format.
+
+    When the default format has changed after a major new release of
+    Bazaar, you may be informed during certain operations that you
+    should upgrade. Upgrading to a newer format may improve performance
+    or make new features available. It may however limit interoperability
+    with older repositories or with older versions of Bazaar.
+
+    If you wish to upgrade to a particular format rather than the
+    current default, that can be specified using the --format option.
+    As a consequence, you can use the upgrade command this way to
+    "downgrade" to an earlier format, though some conversions are
+    a one way process (e.g. changing from the 1.x default to the
+    2.x default) so downgrading is not always possible.
+
+    A backup.bzr.~#~ directory is created at the start of the conversion
+    process (where # is a number). By default, this is left there on
+    completion. If the conversion fails, delete the new .bzr directory
+    and rename this one back in its place. Use the --clean option to ask
+    for the backup.bzr directory to be removed on successful conversion.
+    Alternatively, you can delete it by hand if everything looks good
+    afterwards.
+
+    If the location given is a shared repository, dependent branches
+    are also converted provided the repository converts successfully.
+    If the conversion of a branch fails, remaining branches are still
+    tried.
+
+    For more information on upgrades, see the Bazaar Upgrade Guide,
+    http://doc.bazaar.canonical.com/latest/en/upgrade-guide/.
     """
 
-    _see_also = ['check']
+    _see_also = ['check', 'reconcile', 'formats']
     takes_args = ['url?']
     takes_options = [
-                    RegistryOption('format',
-                        help='Upgrade to a specific format.  See "bzr help'
-                             ' formats" for details.',
-                        lazy_registry=('bzrlib.bzrdir', 'format_registry'),
-                        converter=lambda name: bzrdir.format_registry.make_bzrdir(name),
-                        value_switches=True, title='Branch format'),
-                    ]
+        RegistryOption('format',
+            help='Upgrade to a specific format.  See "bzr help'
+                 ' formats" for details.',
+            lazy_registry=('bzrlib.bzrdir', 'format_registry'),
+            converter=lambda name: bzrdir.format_registry.make_bzrdir(name),
+            value_switches=True, title='Branch format'),
+        Option('clean',
+            help='Remove the backup.bzr directory if successful.'),
+        Option('dry-run',
+            help="Show what would be done, but don't actually do anything."),
+    ]
 
-    def run(self, url='.', format=None):
+    def run(self, url='.', format=None, clean=False, dry_run=False):
         from bzrlib.upgrade import upgrade
-        upgrade(url, format)
+        exceptions = upgrade(url, format, clean_up=clean, dry_run=dry_run)
+        if exceptions:
+            if len(exceptions) == 1:
+                # Compatibility with historical behavior
+                raise exceptions[0]
+            else:
+                return 3
 
 
 class cmd_whoami(Command):

=== modified file 'bzrlib/bzrdir.py'
--- a/bzrlib/bzrdir.py	2010-12-14 23:14:44 +0000
+++ b/bzrlib/bzrdir.py	2010-12-20 11:39:42 +0000
@@ -927,7 +927,6 @@
         return format.initialize_on_transport(t)
 
 
-
 class BzrDirHooks(hooks.Hooks):
     """Hooks for BzrDir operations."""
 

=== modified file 'bzrlib/tests/blackbox/test_upgrade.py'
--- a/bzrlib/tests/blackbox/test_upgrade.py	2010-08-02 14:09:16 +0000
+++ b/bzrlib/tests/blackbox/test_upgrade.py	2010-12-17 11:35:28 +0000
@@ -58,32 +58,39 @@
         self.make_format_5_branch()
         (out, err) = self.run_bzr(
             ['upgrade', self.get_readonly_url('format_5_branch')], retcode=3)
-        self.assertEqual(out, "")
-        self.assertEqual(err, "bzr: ERROR: Upgrade URL cannot work with readonly URLs.\n")
+        err_msg = 'Upgrade URL cannot work with readonly URLs.'
+        self.assertEqualDiff('conversion error: %s\nbzr: ERROR: %s\n'
+                             % (err_msg, err_msg),
+                             err)
 
     def test_upgrade_up_to_date(self):
         self.make_current_format_branch_and_checkout()
         # when up to date we should get a message to that effect
         (out, err) = self.run_bzr('upgrade current_format_branch', retcode=3)
-        self.assertEqual("", out)
-        self.assertEqualDiff("bzr: ERROR: The branch format Meta "
-                             "directory format 1 is already at the most "
-                             "recent format.\n", err)
+        err_msg = ('The branch format %s is already at the most recent format.'
+                   % ('Meta directory format 1'))
+        self.assertEqualDiff('conversion error: %s\nbzr: ERROR: %s\n'
+                             % (err_msg, err_msg),
+                             err)
 
     def test_upgrade_up_to_date_checkout_warns_branch_left_alone(self):
         self.make_current_format_branch_and_checkout()
         # when upgrading a checkout, the branch location and a suggestion
         # to upgrade it should be emitted even if the checkout is up to
         # date
+        burl = self.get_transport('current_format_branch').base
+        curl = self.get_transport('current_format_checkout').base
         (out, err) = self.run_bzr('upgrade current_format_checkout', retcode=3)
-        self.assertEqual("This is a checkout. The branch (%s) needs to be "
-                         "upgraded separately.\n"
-                         % transport.get_transport(
-                self.get_url('current_format_branch')).base,
-                         out)
-        self.assertEqualDiff("bzr: ERROR: The branch format Meta "
-                             "directory format 1 is already at the most "
-                             "recent format.\n", err)
+        self.assertEqual(
+            'Upgrading branch %s ...\nThis is a checkout.'
+            ' The branch (%s) needs to be upgraded separately.\n'
+            % (curl, burl),
+            out)
+        msg = 'The branch format %s is already at the most recent format.' % (
+            'Meta directory format 1')
+        self.assertEqualDiff('conversion error: %s\nbzr: ERROR: %s\n'
+                             % (msg, msg),
+                             err)
 
     def test_upgrade_checkout(self):
         # upgrading a checkout should work
@@ -107,7 +114,8 @@
         backup_dir = 'backup.bzr.~1~'
         (out, err) = self.run_bzr(
             ['upgrade', '--format=metaweave', url])
-        self.assertEqualDiff("""starting upgrade of %s
+        self.assertEqualDiff("""Upgrading branch %s ...
+starting upgrade of %s
 making backup of %s.bzr
   to %s%s
 starting upgrade from format 5 to 6
@@ -115,7 +123,7 @@
 adding prefixes to revision-store
 starting upgrade from format 6 to metadir
 finished
-""" % (url, url, url, backup_dir), out)
+""" % (url, url, url, url, backup_dir), out)
         self.assertEqualDiff("", err)
         self.assertTrue(isinstance(
             bzrdir.BzrDir.open(self.get_url('format_5_branch'))._format,
@@ -131,13 +139,15 @@
         backup_dir = 'backup.bzr.~1~'
         (out, err) = self.run_bzr(
             ['upgrade', '--format=knit', url])
-        self.assertEqualDiff("""starting upgrade of %s
+        self.assertEqualDiff("""Upgrading branch %s ...
+starting upgrade of %s
 making backup of %s.bzr
   to %s%s
 starting repository conversion
 repository converted
 finished
-""" % (url, url, url, backup_dir), out)
+""" % (url, url, url, url, backup_dir),
+                             out)
         self.assertEqualDiff("", err)
         converted_dir = bzrdir.BzrDir.open(self.get_url('metadir_weave_branch'))
         self.assertTrue(isinstance(converted_dir._format,
@@ -149,6 +159,29 @@
         self.run_bzr('init-repository --format=metaweave repo')
         self.run_bzr('upgrade --format=knit repo')
 
+    def assertLegalOption(self, option_str):
+        # Confirm that an option is legal. (Lower level tests are
+        # expected to validate the actual functionality.)
+        self.run_bzr('init --format=pack-0.92 branch-foo')
+        self.run_bzr('upgrade --format=2a branch-foo %s' % (option_str,))
+
+    def assertBranchFormat(self, dir, format):
+        branch = bzrdir.BzrDir.open_tree_or_branch(self.get_url(dir))[1]
+        branch_format = branch._format
+        meta_format = bzrdir.format_registry.make_bzrdir(format)
+        expected_format = meta_format.get_branch_format()
+        self.assertEqual(expected_format, branch_format)
+
+    def test_upgrade_clean_supported(self):
+        self.assertLegalOption('--clean')
+        self.assertBranchFormat('branch-foo', '2a')
+        backup_bzr_dir = os.path.join("branch-foo", "backup.bzr.~1~")
+        self.assertFalse(os.path.exists(backup_bzr_dir))
+
+    def test_upgrade_dry_run_supported(self):
+        self.assertLegalOption('--dry-run')
+        self.assertBranchFormat('branch-foo', 'pack-0.92')
+
     def test_upgrade_permission_check(self):
         """'backup.bzr' should retain permissions of .bzr. Bug #262450"""
         self.requireFeature(features.posix_permissions_feature)
@@ -173,7 +206,8 @@
         t.mkdir(backup_dir1)
         (out, err) = self.run_bzr(
             ['upgrade', '--format=metaweave', url])
-        self.assertEqualDiff("""starting upgrade of %s
+        self.assertEqualDiff("""Upgrading branch %s ...
+starting upgrade of %s
 making backup of %s.bzr
   to %s%s
 starting upgrade from format 5 to 6
@@ -181,7 +215,7 @@
 adding prefixes to revision-store
 starting upgrade from format 6 to metadir
 finished
-""" % (url, url, url, backup_dir2), out)
+""" % (url, url, url, url, backup_dir2), out)
         self.assertEqualDiff("", err)
         self.assertTrue(isinstance(
             bzrdir.BzrDir.open(self.get_url('format_5_branch'))._format,
@@ -197,14 +231,15 @@
         url = t.base
         out, err = self.run_bzr(['upgrade', '--format=knit', url])
         backup_dir = 'backup.bzr.~1~'
-        self.assertEqualDiff("""starting upgrade of %s
+        self.assertEqualDiff("""Upgrading branch %s ...
+starting upgrade of %s
 making backup of %s.bzr
   to %s%s
 starting upgrade from format 6 to metadir
 starting repository conversion
 repository converted
 finished
-""" % (url, url, url,backup_dir), out)
+""" % (url, url, url, url,backup_dir), out)
         self.assertEqual('', err)
 
 

=== modified file 'bzrlib/tests/test_upgrade.py'
--- a/bzrlib/tests/test_upgrade.py	2010-12-07 09:06:39 +0000
+++ b/bzrlib/tests/test_upgrade.py	2010-12-20 11:39:42 +0000
@@ -22,32 +22,24 @@
 # TODO queue for upgrade:
 # test the error message when upgrading an unknown BzrDir format.
 
-import base64
-import os
-import sys
-
 from bzrlib import (
-    branch as _mod_branch,
+    branch,
     bzrdir,
-    progress,
     repository,
-    transport,
+    tests,
+    upgrade,
     workingtree,
     workingtree_4,
     )
-import bzrlib.branch
-from bzrlib.branch import Branch
-from bzrlib.tests import TestCaseWithTransport
-from bzrlib.upgrade import upgrade
-
-
-class TestUpgrade(TestCaseWithTransport):
+
+
+class TestUpgrade(tests.TestCaseWithTransport):
 
     def test_upgrade_simple(self):
         """Upgrade simple v0.0.4 format to latest format"""
         eq = self.assertEquals
         self.build_tree_contents(_upgrade1_template)
-        upgrade(u'.')
+        upgrade.upgrade(u'.')
         control = bzrdir.BzrDir.open('.')
         b = control.open_branch()
         # tsk, peeking under the covers.
@@ -74,7 +66,7 @@
             rt.unlock()
         # check a backup was made:
         backup_dir = 'backup.bzr.~1~'
-        t = transport.get_transport(b.base)
+        t = self.get_transport('.')
         t.stat(backup_dir)
         t.stat(backup_dir + '/README')
         t.stat(backup_dir + '/branch-format')
@@ -108,8 +100,8 @@
         its contents."""
         eq = self.assertEquals
         self.build_tree_contents(_ghost_template)
-        upgrade(u'.')
-        b = Branch.open(u'.')
+        upgrade.upgrade(u'.')
+        b = branch.Branch.open(u'.')
         revision_id = b.revision_history()[1]
         rev = b.repository.get_revision(revision_id)
         eq(len(rev.parent_ids), 2)
@@ -117,13 +109,13 @@
 
     def test_upgrade_makes_dir_weaves(self):
         self.build_tree_contents(_upgrade_dir_template)
-        old_repodir = bzrlib.bzrdir.BzrDir.open_unsupported('.')
+        old_repodir = bzrdir.BzrDir.open_unsupported('.')
         old_repo_format = old_repodir.open_repository()._format
-        upgrade('.')
+        upgrade.upgrade('.')
         # this is the path to the literal file. As format changes
         # occur it needs to be updated. FIXME: ask the store for the
         # path.
-        repo = bzrlib.repository.Repository.open('.')
+        repo = repository.Repository.open('.')
         # it should have changed the format
         self.assertNotEqual(old_repo_format.__class__, repo._format.__class__)
         # and we should be able to read the names for the file id
@@ -137,7 +129,7 @@
 
     def test_upgrade_to_meta_sets_workingtree_last_revision(self):
         self.build_tree_contents(_upgrade_dir_template)
-        upgrade('.', bzrdir.BzrDirMetaFormat1())
+        upgrade.upgrade('.', bzrdir.BzrDirMetaFormat1())
         tree = workingtree.WorkingTree.open('.')
         self.assertEqual([tree.branch.revision_history()[-1]],
             tree.get_parent_ids())
@@ -146,15 +138,15 @@
         # Some format6 branches do not have checkout files. Upgrading
         # such a branch to metadir must not setup a working tree.
         self.build_tree_contents(_upgrade1_template)
-        upgrade('.', bzrdir.BzrDirFormat6())
-        t = transport.get_transport('.')
+        upgrade.upgrade('.', bzrdir.BzrDirFormat6())
+        t = self.get_transport('.')
         t.delete_multi(['.bzr/pending-merges', '.bzr/inventory'])
         self.assertFalse(t.has('.bzr/stat-cache'))
         # XXX: upgrade fails if a backup.bzr is already present
         # -- David Allouche 2006-08-11
         t.delete_tree('backup.bzr.~1~')
         # At this point, we have a format6 branch without checkout files.
-        upgrade('.', bzrdir.BzrDirMetaFormat1())
+        upgrade.upgrade('.', bzrdir.BzrDirMetaFormat1())
         # The upgrade should not have set up a working tree.
         control = bzrdir.BzrDir.open('.')
         self.assertFalse(control.has_workingtree())
@@ -162,27 +154,27 @@
         # upgrade has not eaten our data, even if it's a bit redundant with
         # other tests.
         self.failUnless(isinstance(control._format, bzrdir.BzrDirMetaFormat1))
-        branch = control.open_branch()
-        self.assertEquals(branch.revision_history(),
+        b = control.open_branch()
+        self.assertEquals(b.revision_history(),
            ['mbp at sourcefrog.net-20051004035611-176b16534b086b3c',
             'mbp at sourcefrog.net-20051004035756-235f2b7dcdddd8dd'])
 
     def test_upgrade_rich_root(self):
         tree = self.make_branch_and_tree('tree', format='rich-root')
         rev_id = tree.commit('first post')
-        upgrade('tree')
+        upgrade.upgrade('tree')
 
     def test_convert_branch5_branch6(self):
-        branch = self.make_branch('branch', format='knit')
-        branch.set_revision_history(['AB', 'CD'])
-        branch.set_parent('file:///EF')
-        branch.set_bound_location('file:///GH')
-        branch.set_push_location('file:///IJ')
+        b = self.make_branch('branch', format='knit')
+        b.set_revision_history(['AB', 'CD'])
+        b.set_parent('file:///EF')
+        b.set_bound_location('file:///GH')
+        b.set_push_location('file:///IJ')
         target = bzrdir.format_registry.make_bzrdir('dirstate-with-subtree')
-        converter = branch.bzrdir._format.get_converter(target)
-        converter.convert(branch.bzrdir, None)
-        new_branch = _mod_branch.Branch.open(self.get_url('branch'))
-        self.assertIs(new_branch.__class__, _mod_branch.BzrBranch6)
+        converter = b.bzrdir._format.get_converter(target)
+        converter.convert(b.bzrdir, None)
+        new_branch = branch.Branch.open(self.get_url('branch'))
+        self.assertIs(new_branch.__class__, branch.BzrBranch6)
         self.assertEqual('CD', new_branch.last_revision())
         self.assertEqual('file:///EF', new_branch.get_parent())
         self.assertEqual('file:///GH', new_branch.get_bound_location())
@@ -190,21 +182,21 @@
         self.assertEqual('file:///IJ',
             branch_config.get_user_option('push_location'))
 
-        branch2 = self.make_branch('branch2', format='knit')
-        converter = branch2.bzrdir._format.get_converter(target)
-        converter.convert(branch2.bzrdir, None)
-        branch2 = _mod_branch.Branch.open(self.get_url('branch'))
-        self.assertIs(branch2.__class__, _mod_branch.BzrBranch6)
+        b2 = self.make_branch('branch2', format='knit')
+        converter = b2.bzrdir._format.get_converter(target)
+        converter.convert(b2.bzrdir, None)
+        b2 = branch.Branch.open(self.get_url('branch'))
+        self.assertIs(b2.__class__, branch.BzrBranch6)
 
     def test_convert_branch7_branch8(self):
-        branch = self.make_branch('branch', format='1.9')
+        b = self.make_branch('branch', format='1.9')
         target = bzrdir.format_registry.make_bzrdir('1.9')
-        target.set_branch_format(_mod_branch.BzrBranchFormat8())
-        converter = branch.bzrdir._format.get_converter(target)
-        converter.convert(branch.bzrdir, None)
-        branch = _mod_branch.Branch.open(self.get_url('branch'))
-        self.assertIs(branch.__class__, _mod_branch.BzrBranch8)
-        self.assertEqual({}, branch._get_all_reference_info())
+        target.set_branch_format(branch.BzrBranchFormat8())
+        converter = b.bzrdir._format.get_converter(target)
+        converter.convert(b.bzrdir, None)
+        b = branch.Branch.open(self.get_url('branch'))
+        self.assertIs(b.__class__, branch.BzrBranch8)
+        self.assertEqual({}, b._get_all_reference_info())
 
     def test_convert_knit_dirstate_empty(self):
         # test that asking for an upgrade from knit to dirstate works.
@@ -423,3 +415,88 @@
     ),
     ( './dir/', ),
 ]
+
+
+class TestSmartUpgrade(tests.TestCaseWithTransport):
+
+    from_format = bzrdir.format_registry.make_bzrdir("pack-0.92")
+    to_format = bzrdir.format_registry.make_bzrdir("2a")
+
+    def make_standalone_branch(self):
+        wt = self.make_branch_and_tree("branch1", format=self.from_format)
+        return wt.bzrdir
+
+    def test_upgrade_standalone_branch(self):
+        control = self.make_standalone_branch()
+        tried, worked, issues = upgrade.smart_upgrade(
+            [control], format=self.to_format)
+        self.assertLength(1, tried)
+        self.assertEqual(tried[0], control)
+        self.assertLength(1, worked)
+        self.assertEqual(worked[0], control)
+        self.assertLength(0, issues)
+        self.failUnlessExists('branch1/backup.bzr.~1~')
+        self.assertEqual(control.open_repository()._format,
+                         self.to_format._repository_format)
+
+    def test_upgrade_standalone_branch_cleanup(self):
+        control = self.make_standalone_branch()
+        tried, worked, issues = upgrade.smart_upgrade(
+            [control], format=self.to_format, clean_up=True)
+        self.assertLength(1, tried)
+        self.assertEqual(tried[0], control)
+        self.assertLength(1, worked)
+        self.assertEqual(worked[0], control)
+        self.assertLength(0, issues)
+        self.failUnlessExists('branch1')
+        self.failUnlessExists('branch1/.bzr')
+        self.failIfExists('branch1/backup.bzr.~1~')
+        self.assertEqual(control.open_repository()._format,
+                         self.to_format._repository_format)
+
+    def make_repo_with_branches(self):
+        repo = self.make_repository('repo', shared=True,
+            format=self.from_format)
+        # Note: self.make_branch() always creates a new repo at the location
+        # so we need to avoid using that here ...
+        b1 = bzrdir.BzrDir.create_branch_convenience("repo/branch1",
+            format=self.from_format)
+        b2 = bzrdir.BzrDir.create_branch_convenience("repo/branch2",
+            format=self.from_format)
+        return repo.bzrdir
+
+    def test_upgrade_repo_with_branches(self):
+        control = self.make_repo_with_branches()
+        tried, worked, issues = upgrade.smart_upgrade(
+            [control], format=self.to_format)
+        self.assertLength(3, tried)
+        self.assertEqual(tried[0], control)
+        self.assertLength(3, worked)
+        self.assertEqual(worked[0], control)
+        self.assertLength(0, issues)
+        self.failUnlessExists('repo/backup.bzr.~1~')
+        self.failUnlessExists('repo/branch1/backup.bzr.~1~')
+        self.failUnlessExists('repo/branch2/backup.bzr.~1~')
+        self.assertEqual(control.open_repository()._format,
+                         self.to_format._repository_format)
+        b1 = branch.Branch.open('repo/branch1')
+        self.assertEqual(b1._format, self.to_format._branch_format)
+
+    def test_upgrade_repo_with_branches_cleanup(self):
+        control = self.make_repo_with_branches()
+        tried, worked, issues = upgrade.smart_upgrade(
+            [control], format=self.to_format, clean_up=True)
+        self.assertLength(3, tried)
+        self.assertEqual(tried[0], control)
+        self.assertLength(3, worked)
+        self.assertEqual(worked[0], control)
+        self.assertLength(0, issues)
+        self.failUnlessExists('repo')
+        self.failUnlessExists('repo/.bzr')
+        self.failIfExists('repo/backup.bzr.~1~')
+        self.failIfExists('repo/branch1/backup.bzr.~1~')
+        self.failIfExists('repo/branch2/backup.bzr.~1~')
+        self.assertEqual(control.open_repository()._format,
+                         self.to_format._repository_format)
+        b1 = branch.Branch.open('repo/branch1')
+        self.assertEqual(b1._format, self.to_format._branch_format)

=== modified file 'bzrlib/upgrade.py'
--- a/bzrlib/upgrade.py	2010-04-21 11:27:04 +0000
+++ b/bzrlib/upgrade.py	2010-12-18 18:32:00 +0000
@@ -17,20 +17,41 @@
 """bzr upgrade logic."""
 
 
+from bzrlib import (
+    errors,
+    osutils,
+    repository,
+    trace,
+    ui,
+    )
 from bzrlib.bzrdir import BzrDir, format_registry
-import bzrlib.errors as errors
 from bzrlib.remote import RemoteBzrDir
-import bzrlib.ui as ui
 
 
 class Convert(object):
 
-    def __init__(self, url, format=None):
+    def __init__(self, url=None, format=None, control_dir=None):
+        """Convert a Bazaar control directory to a given format.
+
+        Either the url or control_dir parameter must be given.
+
+        :param url: the URL of the control directory or None if the
+          control_dir is explicitly given instead
+        :param format: the format to convert to or None for the default
+        :param control_dir: the control directory or None if it is
+          specified via the URL parameter instead
+        """
         self.format = format
-        self.bzrdir = BzrDir.open_unsupported(url)
         # XXX: Change to cleanup
         warning_id = 'cross_format_fetch'
         saved_warning = warning_id in ui.ui_factory.suppressed_warnings
+        if url is None and control_dir is None:
+            raise AssertionError(
+                "either the url or control_dir parameter must be set.")
+        if control_dir is not None:
+            self.bzrdir = control_dir
+        else:
+            self.bzrdir = BzrDir.open_unsupported(url)
         if isinstance(self.bzrdir, RemoteBzrDir):
             self.bzrdir._ensure_real()
             self.bzrdir = self.bzrdir._real_bzrdir
@@ -48,9 +69,9 @@
         try:
             branch = self.bzrdir.open_branch()
             if branch.user_url != self.bzrdir.user_url:
-                ui.ui_factory.note("This is a checkout. The branch (%s) needs to be "
-                             "upgraded separately." %
-                             branch.user_url)
+                ui.ui_factory.note(
+                    'This is a checkout. The branch (%s) needs to be upgraded'
+                    ' separately.' % (branch.user_url,))
             del branch
         except (errors.NotBranchError, errors.IncompatibleRepositories):
             # might not be a format we can open without upgrading; see e.g.
@@ -76,13 +97,203 @@
         self.bzrdir.check_conversion_target(format)
         ui.ui_factory.note('starting upgrade of %s' % self.transport.base)
 
-        self.bzrdir.backup_bzrdir()
+        self.backup_oldpath, self.backup_newpath = self.bzrdir.backup_bzrdir()
         while self.bzrdir.needs_format_conversion(format):
             converter = self.bzrdir._format.get_converter(format)
             self.bzrdir = converter.convert(self.bzrdir, None)
-        ui.ui_factory.note("finished")
-
-
-def upgrade(url, format=None):
-    """Upgrade to format, or the default bzrdir format if not supplied."""
-    Convert(url, format)
+        ui.ui_factory.note('finished')
+
+    def clean_up(self):
+        """Clean-up after a conversion.
+
+        This removes the backup.bzr directory.
+        """
+        transport = self.transport
+        backup_relpath = transport.relpath(self.backup_newpath)
+        child_pb = ui.ui_factory.nested_progress_bar()
+        child_pb.update('Deleting backup.bzr')
+        try:
+            transport.delete_tree(backup_relpath)
+        finally:
+            child_pb.finished()
+
+
+def upgrade(url, format=None, clean_up=False, dry_run=False):
+    """Upgrade locations to format.
+ 
+    This routine wraps the smart_upgrade() routine with a nicer UI.
+    In particular, it ensures all URLs can be opened before starting
+    and reports a summary at the end if more than one upgrade was attempted.
+    This routine is useful for command line tools. Other bzrlib clients
+    probably ought to use smart_upgrade() instead.
+
+    :param url: a URL of the locations to upgrade.
+    :param format: the format to convert to or None for the best default
+    :param clean-up: if True, the backup.bzr directory is removed if the
+      upgrade succeeded for a given repo/branch/tree
+    :param dry_run: show what would happen but don't actually do any upgrades
+    :return: the list of exceptions encountered
+    """
+    control_dirs = [BzrDir.open_unsupported(url)]
+    attempted, succeeded, exceptions = smart_upgrade(control_dirs,
+        format, clean_up=clean_up, dry_run=dry_run)
+    if len(attempted) > 1:
+        attempted_count = len(attempted)
+        succeeded_count = len(succeeded)
+        failed_count = attempted_count - succeeded_count
+        ui.ui_factory.note(
+            '\nSUMMARY: %d upgrades attempted, %d succeeded, %d failed'
+            % (attempted_count, succeeded_count, failed_count))
+    return exceptions
+
+
+def smart_upgrade(control_dirs, format, clean_up=False,
+    dry_run=False):
+    """Convert control directories to a new format intelligently.
+
+    If the control directory is a shared repository, dependent branches
+    are also converted provided the repository converted successfully.
+    If the conversion of a branch fails, remaining branches are still tried.
+
+    :param control_dirs: the BzrDirs to upgrade
+    :param format: the format to convert to or None for the best default
+    :param clean_up: if True, the backup.bzr directory is removed if the
+      upgrade succeeded for a given repo/branch/tree
+    :param dry_run: show what would happen but don't actually do any upgrades
+    :return: attempted-control-dirs, succeeded-control-dirs, exceptions
+    """
+    all_attempted = []
+    all_succeeded = []
+    all_exceptions = []
+    for control_dir in control_dirs:
+        attempted, succeeded, exceptions = _smart_upgrade_one(control_dir,
+            format, clean_up=clean_up, dry_run=dry_run)
+        all_attempted.extend(attempted)
+        all_succeeded.extend(succeeded)
+        all_exceptions.extend(exceptions)
+    return all_attempted, all_succeeded, all_exceptions
+
+
+def _smart_upgrade_one(control_dir, format, clean_up=False,
+    dry_run=False):
+    """Convert a control directory to a new format intelligently.
+
+    See smart_upgrade for parameter details.
+    """
+    # If the URL is a shared repository, find the dependent branches
+    dependents = None
+    try:
+        repo = control_dir.open_repository()
+    except errors.NoRepositoryPresent:
+        # A branch or checkout using a shared repository higher up
+        pass
+    else:
+        # The URL is a repository. If it successfully upgrades,
+        # then upgrade the dependent branches as well.
+        if repo.is_shared():
+            dependents = repo.find_branches(using=True)
+
+    # Do the conversions
+    attempted = [control_dir]
+    succeeded, exceptions = _convert_items([control_dir], format, clean_up,
+                                           dry_run)
+    if succeeded and dependents:
+        ui.ui_factory.note('Found %d dependent branches - upgrading ...'
+                           % (len(dependents),))
+        # Convert dependent branches
+        branch_cdirs = [b.bzrdir for b in dependents]
+        successes, problems = _convert_items(branch_cdirs, format, clean_up,
+            dry_run, label="branch")
+        attempted.extend(branch_cdirs)
+        succeeded.extend(successes)
+        exceptions.extend(problems)
+
+    # Return the result
+    return attempted, succeeded, exceptions
+
+# FIXME: There are several problems below:
+# - RemoteRepository doesn't support _unsupported (really ?)
+# - raising AssertionError is rude and may not be necessary
+# - no tests
+# - the only caller uses only the label
+def _get_object_and_label(control_dir):
+    """Return the primary object and type label for a control directory.
+
+    :return: object, label where
+      object is a Branch, Repository or WorkingTree and
+      label is one of:
+        branch            - a branch
+        repository        - a repository
+        tree              - a lightweight checkout
+    """
+    try:
+        try:
+            br = control_dir.open_branch(unsupported=True,
+                                         ignore_fallbacks=True)
+        except NotImplementedError:
+            # RemoteRepository doesn't support the unsupported parameter
+            br = control_dir.open_branch(ignore_fallbacks=True)
+    except errors.NotBranchError:
+        pass
+    else:
+        return br, "branch"
+    try:
+        repo = control_dir.open_repository()
+    except errors.NoRepositoryPresent:
+        pass
+    else:
+        return repo, "repository"
+    try:
+        wt = control_dir.open_workingtree()
+    except (errors.NoWorkingTree, errors.NotLocalUrl):
+        pass
+    else:
+        return wt, "tree"
+    raise AssertionError("unknown type of control directory %s", control_dir)
+
+
+def _convert_items(items, format, clean_up, dry_run, label=None):
+    """Convert a sequence of control directories to the given format.
+ 
+    :param items: the control directories to upgrade
+    :param format: the format to convert to or None for the best default
+    :param clean-up: if True, the backup.bzr directory is removed if the
+      upgrade succeeded for a given repo/branch/tree
+    :param dry_run: show what would happen but don't actually do any upgrades
+    :param label: the label for these items or None to calculate one
+    :return: items successfully upgraded, exceptions
+    """
+    succeeded = []
+    exceptions = []
+    child_pb = ui.ui_factory.nested_progress_bar()
+    child_pb.update('Upgrading bzrdirs', 0, len(items))
+    for i, control_dir in enumerate(items):
+        # Do the conversion
+        location = control_dir.root_transport.base
+        bzr_object, bzr_label = _get_object_and_label(control_dir)
+        type_label = label or bzr_label
+        child_pb.update("Upgrading %s" % (type_label), i+1, len(items))
+        ui.ui_factory.note('Upgrading %s %s ...' % (type_label, location,))
+        try:
+            if not dry_run:
+                cv = Convert(control_dir=control_dir, format=format)
+        except Exception, ex:
+            trace.warning('conversion error: %s' % ex)
+            exceptions.append(ex)
+            continue
+
+        # Do any required post processing
+        succeeded.append(control_dir)
+        if clean_up:
+            try:
+                ui.ui_factory.note('Removing backup ...')
+                if not dry_run:
+                    cv.clean_up()
+            except Exception, ex:
+                trace.warning('failed to clean-up %s: %s' % (location, ex))
+                exceptions.append(ex)
+
+    child_pb.finished()
+
+    # Return the result
+    return succeeded, exceptions

=== modified file 'doc/en/release-notes/bzr-2.3.txt'
--- a/doc/en/release-notes/bzr-2.3.txt	2010-12-17 08:35:38 +0000
+++ b/doc/en/release-notes/bzr-2.3.txt	2010-12-20 11:39:42 +0000
@@ -44,6 +44,11 @@
   ``TestCase`` leading to ``bt.test_import_tariff`` failures.
   (Vincent Ladeuil, #690563)
 
+* ``upgrade`` now upgrades dependent branches when a shared repository is
+  specified. It also supports new options: ``--dry-run`` for showing what
+  will happen and ``--clean`` to remove the backup directory on successful
+  completion. (Ian Clatworthy, Matthew Fuller, #89830, #374734, #422450)
+
 Bug Fixes
 *********
 

=== modified file 'doc/en/upgrade-guide/data_migration.txt'
--- a/doc/en/upgrade-guide/data_migration.txt	2010-11-12 22:43:38 +0000
+++ b/doc/en/upgrade-guide/data_migration.txt	2010-12-20 11:39:42 +0000
@@ -202,11 +202,15 @@
    name then **merge** your changes from the matching branch in the
    old repository.
 
-The first method will give you a branch which is identical (in terms
-of revision history) to the old branch, but it's parent branch will
-be set to the old branch, not your new trunk. If you use this method,
-you'll probably want to edit your branch.conf file to update the
-parent branch setting.
+The first method will give you a branch which is identical (in terms of
+revision history) to the old branch, but it's parent branch will be set to the
+old branch, not your new trunk. If you use this method, you'll probably update
+the ``parent_location`` configuration variable in the ``branch.conf`` file
+with::
+
+    bzr config parent_location=XXX
+
+``XXX` being the URL to your new trunk.
 
 In contrast, the second approach sets up the parent branch correctly.
 However, it isn't ideal if you're not ready to include all the latest

=== modified file 'doc/en/upgrade-guide/index.txt'
--- a/doc/en/upgrade-guide/index.txt	2009-09-02 16:03:51 +0000
+++ b/doc/en/upgrade-guide/index.txt	2010-12-17 10:16:48 +0000
@@ -1,6 +1,6 @@
-########################
-Bazaar 2.0 Upgrade Guide
-########################
+####################
+Bazaar Upgrade Guide
+####################
 
 .. Please mark sections in included files as following:
 ..   level 1 ========

=== modified file 'doc/en/upgrade-guide/overview.txt'
--- a/doc/en/upgrade-guide/overview.txt	2010-08-13 19:08:57 +0000
+++ b/doc/en/upgrade-guide/overview.txt	2010-12-17 10:16:48 +0000
@@ -4,7 +4,7 @@
 High level upgrade process
 --------------------------
 
-In broad terms, there are 3 steps involved in upgrading to Bazaar 2.x:
+In broad terms, there are 3 steps involved in upgrading a new format:
 
 1. Upgrade the core software
 
@@ -12,34 +12,29 @@
 
 3. Migrate data to the new default format.
 
-Bazaar 2.x supports branches in earlier formats so the third step is
-strictly not required. However, the new default format in Bazaar 2.x
-is more space efficient, faster on large projects and provides a range
-of new features, so it is recommended that most projects migrate to it
-at a convenient time.
+Bazaar supports branches in earlier formats so the third step is strictly not
+required. However, when new default formats are introduced, they are more
+space efficient, faster on large projects and/or provide new features. So it
+is recommended that most projects migrate to it at a convenient time.
 
-For most users, upgrading to 2.x and migrating to the new format is
-straight forward. For projects with a large community of developers
-though, things become more complex. In these cases, careful planning
-and good communications become essential. This document provides
-general advice which aims to assist in this regard. If in doubt,
-please contact us on our mailing list or IRC channel with any
-questions or concerns you have.
+For most users, upgrading and migrating to the new format is straight
+forward. For projects with a large community of developers though, things
+become more complex. In these cases, careful planning and good communications
+become essential. This document provides general advice which aims to assist
+in this regard. If in doubt, please contact us on our mailing list or IRC
+channel with any questions or concerns you have.
 
 
 Upgrading the core software
 ---------------------------
 
-The steps required to upgrade the core software vary from operating
-system to operating system. There is nothing special about upgrading
-from Bazaar 1.x to Bazaar 2.0 compared to upgrading from Bazaar 1.x
-to Bazaar 1.y. In either case, a brief outline of the steps is given
-below.
+The steps required to upgrade the core software vary from operating system to
+operating system.  A brief outline of the steps is given below.
 
 To upgrade Bazaar on Ubuntu:
 
-1. Ensure your package manager is configured with the required
-   software sources, e.g. the official release PPA for Ubuntu:
+1. Ensure your package manager is configured with the required software
+   sources, e.g. the official stable release PPA for Ubuntu:
    https://launchpad.net/~bzr/+archive
 
 2. Use your package manager to upgrade to the latest version.

=== modified file 'doc/en/whats-new/whats-new-in-2.3.txt'
--- a/doc/en/whats-new/whats-new-in-2.3.txt	2010-12-14 23:32:28 +0000
+++ b/doc/en/whats-new/whats-new-in-2.3.txt	2010-12-20 11:39:42 +0000
@@ -45,6 +45,11 @@
   ~/.bazaar/plugins.  To use a different directory for plugins, use the
   environment variable BZR_PLUGIN_PATH.  (Neil Martinsen-Burrell, #195397)
 
+* ``bzr upgrade`` now operates recursively when run on a shared
+  repository, automatically upgrading the branches within it, and has
+  grown additional options for showing what it will do and cleaning up
+  after itself.  (Ian Clatworthy, Matthew Fuller, #89830, #374734, #422450)
+
 Launchpad integration
 *********************
 



More information about the bazaar-commits mailing list