summaryrefslogtreecommitdiff
path: root/bzrlib/upgrade.py
diff options
context:
space:
mode:
Diffstat (limited to 'bzrlib/upgrade.py')
-rw-r--r--bzrlib/upgrade.py311
1 files changed, 311 insertions, 0 deletions
diff --git a/bzrlib/upgrade.py b/bzrlib/upgrade.py
new file mode 100644
index 0000000..6bd0732
--- /dev/null
+++ b/bzrlib/upgrade.py
@@ -0,0 +1,311 @@
+# Copyright (C) 2005, 2006, 2008-2011 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
+
+"""bzr upgrade logic."""
+
+from __future__ import absolute_import
+
+from bzrlib import (
+ errors,
+ trace,
+ ui,
+ urlutils,
+ )
+from bzrlib.controldir import (
+ ControlDir,
+ format_registry,
+ )
+from bzrlib.i18n import gettext
+from bzrlib.remote import RemoteBzrDir
+
+
+class Convert(object):
+
+ 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
+ # 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 = ControlDir.open_unsupported(url)
+ if isinstance(self.bzrdir, RemoteBzrDir):
+ self.bzrdir._ensure_real()
+ self.bzrdir = self.bzrdir._real_bzrdir
+ if self.bzrdir.root_transport.is_readonly():
+ raise errors.UpgradeReadonly
+ self.transport = self.bzrdir.root_transport
+ ui.ui_factory.suppressed_warnings.add(warning_id)
+ try:
+ self.convert()
+ finally:
+ if not saved_warning:
+ ui.ui_factory.suppressed_warnings.remove(warning_id)
+
+ def convert(self):
+ try:
+ branch = self.bzrdir.open_branch()
+ if branch.user_url != self.bzrdir.user_url:
+ ui.ui_factory.note(gettext(
+ 'This is a checkout. The branch (%s) needs to be upgraded'
+ ' separately.') % (urlutils.unescape_for_display(
+ branch.user_url, 'utf-8')))
+ del branch
+ except (errors.NotBranchError, errors.IncompatibleRepositories):
+ # might not be a format we can open without upgrading; see e.g.
+ # https://bugs.launchpad.net/bzr/+bug/253891
+ pass
+ if self.format is None:
+ try:
+ rich_root = self.bzrdir.find_repository()._format.rich_root_data
+ except errors.NoRepositoryPresent:
+ rich_root = False # assume no rich roots
+ if rich_root:
+ format_name = "default-rich-root"
+ else:
+ format_name = "default"
+ format = format_registry.make_bzrdir(format_name)
+ else:
+ format = self.format
+ if not self.bzrdir.needs_format_conversion(format):
+ raise errors.UpToDateFormat(self.bzrdir._format)
+ if not self.bzrdir.can_convert_format():
+ raise errors.BzrError(gettext("cannot upgrade from bzrdir format %s") %
+ self.bzrdir._format)
+ self.bzrdir.check_conversion_target(format)
+ ui.ui_factory.note(gettext('starting upgrade of %s') %
+ urlutils.unescape_for_display(self.transport.base, 'utf-8'))
+
+ 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(gettext('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(gettext('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 = [ControlDir.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(
+ gettext('\nSUMMARY: {0} upgrades attempted, {1} succeeded,'\
+ ' {2} failed').format(
+ 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(gettext('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(gettext('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(gettext("Upgrading %s") % (type_label), i+1, len(items))
+ ui.ui_factory.note(gettext('Upgrading {0} {1} ...').format(type_label,
+ urlutils.unescape_for_display(location, 'utf-8'),))
+ try:
+ if not dry_run:
+ cv = Convert(control_dir=control_dir, format=format)
+ except errors.UpToDateFormat, ex:
+ ui.ui_factory.note(str(ex))
+ succeeded.append(control_dir)
+ continue
+ 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(gettext('Removing backup ...'))
+ if not dry_run:
+ cv.clean_up()
+ except Exception, ex:
+ trace.warning(gettext('failed to clean-up {0}: {1}') % (location, ex))
+ exceptions.append(ex)
+
+ child_pb.finished()
+
+ # Return the result
+ return succeeded, exceptions