diff options
Diffstat (limited to 'bzrlib/upgrade.py')
-rw-r--r-- | bzrlib/upgrade.py | 311 |
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 |