~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/upgrade.py

  • Committer: John Arbash Meinel
  • Date: 2013-05-19 14:29:37 UTC
  • mfrom: (6437.63.9 2.5)
  • mto: (6437.63.10 2.5)
  • mto: This revision was merged to the branch mainline in revision 6575.
  • Revision ID: john@arbash-meinel.com-20130519142937-21ykz2n2y2f22za9
Merge in the actual 2.5 branch. It seems I failed before

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2008-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""bzr upgrade logic."""
18
18
 
19
 
# change upgrade from .bzr to create a '.bzr-new', then do a bait and switch.
20
 
 
21
 
 
22
 
from bzrlib.bzrdir import ConvertBzrDir4To5, ConvertBzrDir5To6, BzrDir, BzrDirFormat4, BzrDirFormat5, BzrDirFormat
23
 
import bzrlib.errors as errors
24
 
from bzrlib.transport import get_transport
25
 
import bzrlib.ui as ui
 
19
from __future__ import absolute_import
 
20
 
 
21
from bzrlib import (
 
22
    errors,
 
23
    trace,
 
24
    ui,
 
25
    urlutils,
 
26
    )
 
27
from bzrlib.controldir import (
 
28
    ControlDir,
 
29
    format_registry,
 
30
    )
 
31
from bzrlib.i18n import gettext
 
32
from bzrlib.remote import RemoteBzrDir
26
33
 
27
34
 
28
35
class Convert(object):
29
36
 
30
 
    def __init__(self, url, format):
 
37
    def __init__(self, url=None, format=None, control_dir=None):
 
38
        """Convert a Bazaar control directory to a given format.
 
39
 
 
40
        Either the url or control_dir parameter must be given.
 
41
 
 
42
        :param url: the URL of the control directory or None if the
 
43
          control_dir is explicitly given instead
 
44
        :param format: the format to convert to or None for the default
 
45
        :param control_dir: the control directory or None if it is
 
46
          specified via the URL parameter instead
 
47
        """
31
48
        self.format = format
32
 
        self.bzrdir = BzrDir.open_unsupported(url)
 
49
        # XXX: Change to cleanup
 
50
        warning_id = 'cross_format_fetch'
 
51
        saved_warning = warning_id in ui.ui_factory.suppressed_warnings
 
52
        if url is None and control_dir is None:
 
53
            raise AssertionError(
 
54
                "either the url or control_dir parameter must be set.")
 
55
        if control_dir is not None:
 
56
            self.bzrdir = control_dir
 
57
        else:
 
58
            self.bzrdir = ControlDir.open_unsupported(url)
 
59
        if isinstance(self.bzrdir, RemoteBzrDir):
 
60
            self.bzrdir._ensure_real()
 
61
            self.bzrdir = self.bzrdir._real_bzrdir
33
62
        if self.bzrdir.root_transport.is_readonly():
34
63
            raise errors.UpgradeReadonly
35
64
        self.transport = self.bzrdir.root_transport
36
 
        self.pb = ui.ui_factory.nested_progress_bar()
 
65
        ui.ui_factory.suppressed_warnings.add(warning_id)
37
66
        try:
38
67
            self.convert()
39
68
        finally:
40
 
            self.pb.finished()
 
69
            if not saved_warning:
 
70
                ui.ui_factory.suppressed_warnings.remove(warning_id)
41
71
 
42
72
    def convert(self):
43
73
        try:
44
74
            branch = self.bzrdir.open_branch()
45
 
            if branch.bzrdir.root_transport.base != \
46
 
                self.bzrdir.root_transport.base:
47
 
                self.pb.note("This is a checkout. The branch (%s) needs to be "
48
 
                             "upgraded separately.",
49
 
                             branch.bzrdir.root_transport.base)
50
 
        except errors.NotBranchError:
 
75
            if branch.user_url != self.bzrdir.user_url:
 
76
                ui.ui_factory.note(gettext(
 
77
                    'This is a checkout. The branch (%s) needs to be upgraded'
 
78
                    ' separately.') % (urlutils.unescape_for_display(
 
79
                        branch.user_url, 'utf-8')))
 
80
            del branch
 
81
        except (errors.NotBranchError, errors.IncompatibleRepositories):
 
82
            # might not be a format we can open without upgrading; see e.g.
 
83
            # https://bugs.launchpad.net/bzr/+bug/253891
51
84
            pass
52
 
        if not self.bzrdir.needs_format_conversion(self.format):
 
85
        if self.format is None:
 
86
            try:
 
87
                rich_root = self.bzrdir.find_repository()._format.rich_root_data
 
88
            except errors.NoRepositoryPresent:
 
89
                rich_root = False # assume no rich roots
 
90
            if rich_root:
 
91
                format_name = "default-rich-root"
 
92
            else:
 
93
                format_name = "default"
 
94
            format = format_registry.make_bzrdir(format_name)
 
95
        else:
 
96
            format = self.format
 
97
        if not self.bzrdir.needs_format_conversion(format):
53
98
            raise errors.UpToDateFormat(self.bzrdir._format)
54
99
        if not self.bzrdir.can_convert_format():
55
 
            raise errors.BzrError("cannot upgrade from branch format %s" %
 
100
            raise errors.BzrError(gettext("cannot upgrade from bzrdir format %s") %
56
101
                           self.bzrdir._format)
57
 
        if self.format is None:
58
 
            target_format = BzrDirFormat.get_default_format()
59
 
        else:
60
 
            target_format = self.format
61
 
        self.bzrdir.check_conversion_target(target_format)
62
 
        self.pb.note('starting upgrade of %s', self.transport.base)
63
 
        self._backup_control_dir()
64
 
        while self.bzrdir.needs_format_conversion(self.format):
65
 
            converter = self.bzrdir._format.get_converter(self.format)
66
 
            self.bzrdir = converter.convert(self.bzrdir, self.pb)
67
 
        self.pb.note("finished")
68
 
 
69
 
    def _backup_control_dir(self):
70
 
        self.pb.note('making backup of tree history')
71
 
        self.transport.copy_tree('.bzr', '.bzr.backup')
72
 
        self.pb.note('%s.bzr has been backed up to %s.bzr.backup',
73
 
             self.transport.base,
74
 
             self.transport.base)
75
 
        self.pb.note('if conversion fails, you can move this directory back to .bzr')
76
 
        self.pb.note('if it succeeds, you can remove this directory if you wish')
77
 
 
78
 
def upgrade(url, format=None):
79
 
    """Upgrade to format, or the default bzrdir format if not supplied."""
80
 
    Convert(url, format)
 
102
        self.bzrdir.check_conversion_target(format)
 
103
        ui.ui_factory.note(gettext('starting upgrade of %s') % 
 
104
            urlutils.unescape_for_display(self.transport.base, 'utf-8'))
 
105
 
 
106
        self.backup_oldpath, self.backup_newpath = self.bzrdir.backup_bzrdir()
 
107
        while self.bzrdir.needs_format_conversion(format):
 
108
            converter = self.bzrdir._format.get_converter(format)
 
109
            self.bzrdir = converter.convert(self.bzrdir, None)
 
110
        ui.ui_factory.note(gettext('finished'))
 
111
 
 
112
    def clean_up(self):
 
113
        """Clean-up after a conversion.
 
114
 
 
115
        This removes the backup.bzr directory.
 
116
        """
 
117
        transport = self.transport
 
118
        backup_relpath = transport.relpath(self.backup_newpath)
 
119
        child_pb = ui.ui_factory.nested_progress_bar()
 
120
        child_pb.update(gettext('Deleting backup.bzr'))
 
121
        try:
 
122
            transport.delete_tree(backup_relpath)
 
123
        finally:
 
124
            child_pb.finished()
 
125
 
 
126
 
 
127
def upgrade(url, format=None, clean_up=False, dry_run=False):
 
128
    """Upgrade locations to format.
 
129
 
 
130
    This routine wraps the smart_upgrade() routine with a nicer UI.
 
131
    In particular, it ensures all URLs can be opened before starting
 
132
    and reports a summary at the end if more than one upgrade was attempted.
 
133
    This routine is useful for command line tools. Other bzrlib clients
 
134
    probably ought to use smart_upgrade() instead.
 
135
 
 
136
    :param url: a URL of the locations to upgrade.
 
137
    :param format: the format to convert to or None for the best default
 
138
    :param clean-up: if True, the backup.bzr directory is removed if the
 
139
      upgrade succeeded for a given repo/branch/tree
 
140
    :param dry_run: show what would happen but don't actually do any upgrades
 
141
    :return: the list of exceptions encountered
 
142
    """
 
143
    control_dirs = [ControlDir.open_unsupported(url)]
 
144
    attempted, succeeded, exceptions = smart_upgrade(control_dirs,
 
145
        format, clean_up=clean_up, dry_run=dry_run)
 
146
    if len(attempted) > 1:
 
147
        attempted_count = len(attempted)
 
148
        succeeded_count = len(succeeded)
 
149
        failed_count = attempted_count - succeeded_count
 
150
        ui.ui_factory.note(
 
151
            gettext('\nSUMMARY: {0} upgrades attempted, {1} succeeded,'\
 
152
                    ' {2} failed').format(
 
153
                     attempted_count, succeeded_count, failed_count))
 
154
    return exceptions
 
155
 
 
156
 
 
157
def smart_upgrade(control_dirs, format, clean_up=False,
 
158
    dry_run=False):
 
159
    """Convert control directories to a new format intelligently.
 
160
 
 
161
    If the control directory is a shared repository, dependent branches
 
162
    are also converted provided the repository converted successfully.
 
163
    If the conversion of a branch fails, remaining branches are still tried.
 
164
 
 
165
    :param control_dirs: the BzrDirs to upgrade
 
166
    :param format: the format to convert to or None for the best default
 
167
    :param clean_up: if True, the backup.bzr directory is removed if the
 
168
      upgrade succeeded for a given repo/branch/tree
 
169
    :param dry_run: show what would happen but don't actually do any upgrades
 
170
    :return: attempted-control-dirs, succeeded-control-dirs, exceptions
 
171
    """
 
172
    all_attempted = []
 
173
    all_succeeded = []
 
174
    all_exceptions = []
 
175
    for control_dir in control_dirs:
 
176
        attempted, succeeded, exceptions = _smart_upgrade_one(control_dir,
 
177
            format, clean_up=clean_up, dry_run=dry_run)
 
178
        all_attempted.extend(attempted)
 
179
        all_succeeded.extend(succeeded)
 
180
        all_exceptions.extend(exceptions)
 
181
    return all_attempted, all_succeeded, all_exceptions
 
182
 
 
183
 
 
184
def _smart_upgrade_one(control_dir, format, clean_up=False,
 
185
    dry_run=False):
 
186
    """Convert a control directory to a new format intelligently.
 
187
 
 
188
    See smart_upgrade for parameter details.
 
189
    """
 
190
    # If the URL is a shared repository, find the dependent branches
 
191
    dependents = None
 
192
    try:
 
193
        repo = control_dir.open_repository()
 
194
    except errors.NoRepositoryPresent:
 
195
        # A branch or checkout using a shared repository higher up
 
196
        pass
 
197
    else:
 
198
        # The URL is a repository. If it successfully upgrades,
 
199
        # then upgrade the dependent branches as well.
 
200
        if repo.is_shared():
 
201
            dependents = repo.find_branches(using=True)
 
202
 
 
203
    # Do the conversions
 
204
    attempted = [control_dir]
 
205
    succeeded, exceptions = _convert_items([control_dir], format, clean_up,
 
206
                                           dry_run)
 
207
    if succeeded and dependents:
 
208
        ui.ui_factory.note(gettext('Found %d dependent branches - upgrading ...')
 
209
                           % (len(dependents),))
 
210
        # Convert dependent branches
 
211
        branch_cdirs = [b.bzrdir for b in dependents]
 
212
        successes, problems = _convert_items(branch_cdirs, format, clean_up,
 
213
            dry_run, label="branch")
 
214
        attempted.extend(branch_cdirs)
 
215
        succeeded.extend(successes)
 
216
        exceptions.extend(problems)
 
217
 
 
218
    # Return the result
 
219
    return attempted, succeeded, exceptions
 
220
 
 
221
# FIXME: There are several problems below:
 
222
# - RemoteRepository doesn't support _unsupported (really ?)
 
223
# - raising AssertionError is rude and may not be necessary
 
224
# - no tests
 
225
# - the only caller uses only the label
 
226
def _get_object_and_label(control_dir):
 
227
    """Return the primary object and type label for a control directory.
 
228
 
 
229
    :return: object, label where:
 
230
      * object is a Branch, Repository or WorkingTree and
 
231
      * label is one of:
 
232
        * branch            - a branch
 
233
        * repository        - a repository
 
234
        * tree              - a lightweight checkout
 
235
    """
 
236
    try:
 
237
        try:
 
238
            br = control_dir.open_branch(unsupported=True,
 
239
                                         ignore_fallbacks=True)
 
240
        except NotImplementedError:
 
241
            # RemoteRepository doesn't support the unsupported parameter
 
242
            br = control_dir.open_branch(ignore_fallbacks=True)
 
243
    except errors.NotBranchError:
 
244
        pass
 
245
    else:
 
246
        return br, "branch"
 
247
    try:
 
248
        repo = control_dir.open_repository()
 
249
    except errors.NoRepositoryPresent:
 
250
        pass
 
251
    else:
 
252
        return repo, "repository"
 
253
    try:
 
254
        wt = control_dir.open_workingtree()
 
255
    except (errors.NoWorkingTree, errors.NotLocalUrl):
 
256
        pass
 
257
    else:
 
258
        return wt, "tree"
 
259
    raise AssertionError("unknown type of control directory %s", control_dir)
 
260
 
 
261
 
 
262
def _convert_items(items, format, clean_up, dry_run, label=None):
 
263
    """Convert a sequence of control directories to the given format.
 
264
 
 
265
    :param items: the control directories to upgrade
 
266
    :param format: the format to convert to or None for the best default
 
267
    :param clean-up: if True, the backup.bzr directory is removed if the
 
268
      upgrade succeeded for a given repo/branch/tree
 
269
    :param dry_run: show what would happen but don't actually do any upgrades
 
270
    :param label: the label for these items or None to calculate one
 
271
    :return: items successfully upgraded, exceptions
 
272
    """
 
273
    succeeded = []
 
274
    exceptions = []
 
275
    child_pb = ui.ui_factory.nested_progress_bar()
 
276
    child_pb.update(gettext('Upgrading bzrdirs'), 0, len(items))
 
277
    for i, control_dir in enumerate(items):
 
278
        # Do the conversion
 
279
        location = control_dir.root_transport.base
 
280
        bzr_object, bzr_label = _get_object_and_label(control_dir)
 
281
        type_label = label or bzr_label
 
282
        child_pb.update(gettext("Upgrading %s") % (type_label), i+1, len(items))
 
283
        ui.ui_factory.note(gettext('Upgrading {0} {1} ...').format(type_label, 
 
284
            urlutils.unescape_for_display(location, 'utf-8'),))
 
285
        try:
 
286
            if not dry_run:
 
287
                cv = Convert(control_dir=control_dir, format=format)
 
288
        except errors.UpToDateFormat, ex:
 
289
            ui.ui_factory.note(str(ex))
 
290
            succeeded.append(control_dir)
 
291
            continue
 
292
        except Exception, ex:
 
293
            trace.warning('conversion error: %s' % ex)
 
294
            exceptions.append(ex)
 
295
            continue
 
296
 
 
297
        # Do any required post processing
 
298
        succeeded.append(control_dir)
 
299
        if clean_up:
 
300
            try:
 
301
                ui.ui_factory.note(gettext('Removing backup ...'))
 
302
                if not dry_run:
 
303
                    cv.clean_up()
 
304
            except Exception, ex:
 
305
                trace.warning(gettext('failed to clean-up {0}: {1}') % (location, ex))
 
306
                exceptions.append(ex)
 
307
 
 
308
    child_pb.finished()
 
309
 
 
310
    # Return the result
 
311
    return succeeded, exceptions