1
# Copyright (C) 2005, 2006, 2008-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""bzr upgrade logic."""
26
from bzrlib.bzrdir import (
30
from bzrlib.remote import RemoteBzrDir
33
class Convert(object):
35
def __init__(self, url=None, format=None, control_dir=None):
36
"""Convert a Bazaar control directory to a given format.
38
Either the url or control_dir parameter must be given.
40
:param url: the URL of the control directory or None if the
41
control_dir is explicitly given instead
42
:param format: the format to convert to or None for the default
43
:param control_dir: the control directory or None if it is
44
specified via the URL parameter instead
47
# XXX: Change to cleanup
48
warning_id = 'cross_format_fetch'
49
saved_warning = warning_id in ui.ui_factory.suppressed_warnings
50
if url is None and control_dir is None:
52
"either the url or control_dir parameter must be set.")
53
if control_dir is not None:
54
self.bzrdir = control_dir
56
self.bzrdir = BzrDir.open_unsupported(url)
57
if isinstance(self.bzrdir, RemoteBzrDir):
58
self.bzrdir._ensure_real()
59
self.bzrdir = self.bzrdir._real_bzrdir
60
if self.bzrdir.root_transport.is_readonly():
61
raise errors.UpgradeReadonly
62
self.transport = self.bzrdir.root_transport
63
ui.ui_factory.suppressed_warnings.add(warning_id)
68
ui.ui_factory.suppressed_warnings.remove(warning_id)
72
branch = self.bzrdir.open_branch()
73
if branch.user_url != self.bzrdir.user_url:
75
'This is a checkout. The branch (%s) needs to be upgraded'
76
' separately.' % (urlutils.unescape_for_display(
77
branch.user_url, 'utf-8')))
79
except (errors.NotBranchError, errors.IncompatibleRepositories):
80
# might not be a format we can open without upgrading; see e.g.
81
# https://bugs.launchpad.net/bzr/+bug/253891
83
if self.format is None:
85
rich_root = self.bzrdir.find_repository()._format.rich_root_data
86
except errors.NoRepositoryPresent:
87
rich_root = False # assume no rich roots
89
format_name = "default-rich-root"
91
format_name = "default"
92
format = format_registry.make_bzrdir(format_name)
95
if not self.bzrdir.needs_format_conversion(format):
96
raise errors.UpToDateFormat(self.bzrdir._format)
97
if not self.bzrdir.can_convert_format():
98
raise errors.BzrError("cannot upgrade from bzrdir format %s" %
100
self.bzrdir.check_conversion_target(format)
101
ui.ui_factory.note('starting upgrade of %s' %
102
urlutils.unescape_for_display(self.transport.base, 'utf-8'))
104
self.backup_oldpath, self.backup_newpath = self.bzrdir.backup_bzrdir()
105
while self.bzrdir.needs_format_conversion(format):
106
converter = self.bzrdir._format.get_converter(format)
107
self.bzrdir = converter.convert(self.bzrdir, None)
108
ui.ui_factory.note('finished')
111
"""Clean-up after a conversion.
113
This removes the backup.bzr directory.
115
transport = self.transport
116
backup_relpath = transport.relpath(self.backup_newpath)
117
child_pb = ui.ui_factory.nested_progress_bar()
118
child_pb.update('Deleting backup.bzr')
120
transport.delete_tree(backup_relpath)
125
def upgrade(url, format=None, clean_up=False, dry_run=False):
126
"""Upgrade locations to format.
128
This routine wraps the smart_upgrade() routine with a nicer UI.
129
In particular, it ensures all URLs can be opened before starting
130
and reports a summary at the end if more than one upgrade was attempted.
131
This routine is useful for command line tools. Other bzrlib clients
132
probably ought to use smart_upgrade() instead.
134
:param url: a URL of the locations to upgrade.
135
:param format: the format to convert to or None for the best default
136
:param clean-up: if True, the backup.bzr directory is removed if the
137
upgrade succeeded for a given repo/branch/tree
138
:param dry_run: show what would happen but don't actually do any upgrades
139
:return: the list of exceptions encountered
141
control_dirs = [BzrDir.open_unsupported(url)]
142
attempted, succeeded, exceptions = smart_upgrade(control_dirs,
143
format, clean_up=clean_up, dry_run=dry_run)
144
if len(attempted) > 1:
145
attempted_count = len(attempted)
146
succeeded_count = len(succeeded)
147
failed_count = attempted_count - succeeded_count
149
'\nSUMMARY: %d upgrades attempted, %d succeeded, %d failed'
150
% (attempted_count, succeeded_count, failed_count))
154
def smart_upgrade(control_dirs, format, clean_up=False,
156
"""Convert control directories to a new format intelligently.
158
If the control directory is a shared repository, dependent branches
159
are also converted provided the repository converted successfully.
160
If the conversion of a branch fails, remaining branches are still tried.
162
:param control_dirs: the BzrDirs to upgrade
163
:param format: the format to convert to or None for the best default
164
:param clean_up: if True, the backup.bzr directory is removed if the
165
upgrade succeeded for a given repo/branch/tree
166
:param dry_run: show what would happen but don't actually do any upgrades
167
:return: attempted-control-dirs, succeeded-control-dirs, exceptions
172
for control_dir in control_dirs:
173
attempted, succeeded, exceptions = _smart_upgrade_one(control_dir,
174
format, clean_up=clean_up, dry_run=dry_run)
175
all_attempted.extend(attempted)
176
all_succeeded.extend(succeeded)
177
all_exceptions.extend(exceptions)
178
return all_attempted, all_succeeded, all_exceptions
181
def _smart_upgrade_one(control_dir, format, clean_up=False,
183
"""Convert a control directory to a new format intelligently.
185
See smart_upgrade for parameter details.
187
# If the URL is a shared repository, find the dependent branches
190
repo = control_dir.open_repository()
191
except errors.NoRepositoryPresent:
192
# A branch or checkout using a shared repository higher up
195
# The URL is a repository. If it successfully upgrades,
196
# then upgrade the dependent branches as well.
198
dependents = repo.find_branches(using=True)
201
attempted = [control_dir]
202
succeeded, exceptions = _convert_items([control_dir], format, clean_up,
204
if succeeded and dependents:
205
ui.ui_factory.note('Found %d dependent branches - upgrading ...'
206
% (len(dependents),))
207
# Convert dependent branches
208
branch_cdirs = [b.bzrdir for b in dependents]
209
successes, problems = _convert_items(branch_cdirs, format, clean_up,
210
dry_run, label="branch")
211
attempted.extend(branch_cdirs)
212
succeeded.extend(successes)
213
exceptions.extend(problems)
216
return attempted, succeeded, exceptions
218
# FIXME: There are several problems below:
219
# - RemoteRepository doesn't support _unsupported (really ?)
220
# - raising AssertionError is rude and may not be necessary
222
# - the only caller uses only the label
223
def _get_object_and_label(control_dir):
224
"""Return the primary object and type label for a control directory.
226
:return: object, label where:
227
* object is a Branch, Repository or WorkingTree and
230
* repository - a repository
231
* tree - a lightweight checkout
235
br = control_dir.open_branch(unsupported=True,
236
ignore_fallbacks=True)
237
except NotImplementedError:
238
# RemoteRepository doesn't support the unsupported parameter
239
br = control_dir.open_branch(ignore_fallbacks=True)
240
except errors.NotBranchError:
245
repo = control_dir.open_repository()
246
except errors.NoRepositoryPresent:
249
return repo, "repository"
251
wt = control_dir.open_workingtree()
252
except (errors.NoWorkingTree, errors.NotLocalUrl):
256
raise AssertionError("unknown type of control directory %s", control_dir)
259
def _convert_items(items, format, clean_up, dry_run, label=None):
260
"""Convert a sequence of control directories to the given format.
262
:param items: the control directories to upgrade
263
:param format: the format to convert to or None for the best default
264
:param clean-up: if True, the backup.bzr directory is removed if the
265
upgrade succeeded for a given repo/branch/tree
266
:param dry_run: show what would happen but don't actually do any upgrades
267
:param label: the label for these items or None to calculate one
268
:return: items successfully upgraded, exceptions
272
child_pb = ui.ui_factory.nested_progress_bar()
273
child_pb.update('Upgrading bzrdirs', 0, len(items))
274
for i, control_dir in enumerate(items):
276
location = control_dir.root_transport.base
277
bzr_object, bzr_label = _get_object_and_label(control_dir)
278
type_label = label or bzr_label
279
child_pb.update("Upgrading %s" % (type_label), i+1, len(items))
280
ui.ui_factory.note('Upgrading %s %s ...' % (type_label,
281
urlutils.unescape_for_display(location, 'utf-8'),))
284
cv = Convert(control_dir=control_dir, format=format)
285
except Exception, ex:
286
trace.warning('conversion error: %s' % ex)
287
exceptions.append(ex)
290
# Do any required post processing
291
succeeded.append(control_dir)
294
ui.ui_factory.note('Removing backup ...')
297
except Exception, ex:
298
trace.warning('failed to clean-up %s: %s' % (location, ex))
299
exceptions.append(ex)
304
return succeeded, exceptions