~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/reconfigure.py

Turn completion assertions into separate methods.

Many common assertions used to be expressed as arguments to the complete
method.  This makes the checks more explicit, and the code easier to read.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2010 Canonical Ltd
2
 
#
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.
7
 
#
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.
12
 
#
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
16
 
 
17
 
"""Reconfigure a bzrdir into a new tree/branch/repository layout.
18
 
 
19
 
Various types of reconfiguration operation are available either by
20
 
constructing a class or using a factory method on Reconfigure.
21
 
"""
22
 
 
23
 
 
24
 
from bzrlib import (
25
 
    branch,
26
 
    bzrdir,
27
 
    errors,
28
 
    trace,
29
 
    ui,
30
 
    urlutils,
31
 
    )
32
 
 
33
 
 
34
 
# TODO: common base class for all reconfigure operations, making no
35
 
# assumptions about what kind of change will be done.
36
 
 
37
 
 
38
 
class ReconfigureStackedOn(object):
39
 
    """Reconfigures a branch to be stacked on another branch."""
40
 
 
41
 
    def apply(self, bzrdir, stacked_on_url):
42
 
        branch = bzrdir.open_branch()
43
 
        # it may be a path relative to the cwd or a url; the branch wants
44
 
        # a path relative to itself...
45
 
        on_url = urlutils.relative_url(branch.base,
46
 
            urlutils.normalize_url(stacked_on_url))
47
 
        branch.lock_write()
48
 
        try:
49
 
            branch.set_stacked_on_url(on_url)
50
 
            if not trace.is_quiet():
51
 
                ui.ui_factory.note(
52
 
                    "%s is now stacked on %s\n"
53
 
                    % (branch.base, branch.get_stacked_on_url()))
54
 
        finally:
55
 
            branch.unlock()
56
 
 
57
 
 
58
 
class ReconfigureUnstacked(object):
59
 
 
60
 
    def apply(self, bzrdir):
61
 
        branch = bzrdir.open_branch()
62
 
        branch.lock_write()
63
 
        try:
64
 
            branch.set_stacked_on_url(None)
65
 
            if not trace.is_quiet():
66
 
                ui.ui_factory.note(
67
 
                    "%s is now not stacked\n"
68
 
                    % (branch.base,))
69
 
        finally:
70
 
            branch.unlock()
71
 
 
72
 
 
73
 
class Reconfigure(object):
74
 
 
75
 
    def __init__(self, bzrdir, new_bound_location=None):
76
 
        self.bzrdir = bzrdir
77
 
        self.new_bound_location = new_bound_location
78
 
        self.local_repository = None
79
 
        try:
80
 
            self.repository = self.bzrdir.find_repository()
81
 
        except errors.NoRepositoryPresent:
82
 
            self.repository = None
83
 
            self.local_repository = None
84
 
        else:
85
 
            if (self.repository.user_url == self.bzrdir.user_url):
86
 
                self.local_repository = self.repository
87
 
            else:
88
 
                self.local_repository = None
89
 
        try:
90
 
            branch = self.bzrdir.open_branch()
91
 
            if branch.user_url == bzrdir.user_url:
92
 
                self.local_branch = branch
93
 
                self.referenced_branch = None
94
 
            else:
95
 
                self.local_branch = None
96
 
                self.referenced_branch = branch
97
 
        except errors.NotBranchError:
98
 
            self.local_branch = None
99
 
            self.referenced_branch = None
100
 
        try:
101
 
            self.tree = bzrdir.open_workingtree()
102
 
        except errors.NoWorkingTree:
103
 
            self.tree = None
104
 
        self._unbind = False
105
 
        self._bind = False
106
 
        self._destroy_reference = False
107
 
        self._create_reference = False
108
 
        self._destroy_branch = False
109
 
        self._create_branch = False
110
 
        self._destroy_tree = False
111
 
        self._create_tree = False
112
 
        self._create_repository = False
113
 
        self._destroy_repository = False
114
 
        self._repository_trees = None
115
 
 
116
 
    @staticmethod
117
 
    def to_branch(bzrdir):
118
 
        """Return a Reconfiguration to convert this bzrdir into a branch
119
 
 
120
 
        :param bzrdir: The bzrdir to reconfigure
121
 
        :raise errors.AlreadyBranch: if bzrdir is already a branch
122
 
        """
123
 
        reconfiguration = Reconfigure(bzrdir)
124
 
        reconfiguration._plan_changes(want_tree=False, want_branch=True,
125
 
                                      want_bound=False, want_reference=False)
126
 
        if not reconfiguration.changes_planned():
127
 
            raise errors.AlreadyBranch(bzrdir)
128
 
        return reconfiguration
129
 
 
130
 
    @staticmethod
131
 
    def to_tree(bzrdir):
132
 
        """Return a Reconfiguration to convert this bzrdir into a tree
133
 
 
134
 
        :param bzrdir: The bzrdir to reconfigure
135
 
        :raise errors.AlreadyTree: if bzrdir is already a tree
136
 
        """
137
 
        reconfiguration = Reconfigure(bzrdir)
138
 
        reconfiguration._plan_changes(want_tree=True, want_branch=True,
139
 
                                      want_bound=False, want_reference=False)
140
 
        if not reconfiguration.changes_planned():
141
 
            raise errors.AlreadyTree(bzrdir)
142
 
        return reconfiguration
143
 
 
144
 
    @staticmethod
145
 
    def to_checkout(bzrdir, bound_location=None):
146
 
        """Return a Reconfiguration to convert this bzrdir into a checkout
147
 
 
148
 
        :param bzrdir: The bzrdir to reconfigure
149
 
        :param bound_location: The location the checkout should be bound to.
150
 
        :raise errors.AlreadyCheckout: if bzrdir is already a checkout
151
 
        """
152
 
        reconfiguration = Reconfigure(bzrdir, bound_location)
153
 
        reconfiguration._plan_changes(want_tree=True, want_branch=True,
154
 
                                      want_bound=True, want_reference=False)
155
 
        if not reconfiguration.changes_planned():
156
 
            raise errors.AlreadyCheckout(bzrdir)
157
 
        return reconfiguration
158
 
 
159
 
    @classmethod
160
 
    def to_lightweight_checkout(klass, bzrdir, reference_location=None):
161
 
        """Make a Reconfiguration to convert bzrdir into a lightweight checkout
162
 
 
163
 
        :param bzrdir: The bzrdir to reconfigure
164
 
        :param bound_location: The location the checkout should be bound to.
165
 
        :raise errors.AlreadyLightweightCheckout: if bzrdir is already a
166
 
            lightweight checkout
167
 
        """
168
 
        reconfiguration = klass(bzrdir, reference_location)
169
 
        reconfiguration._plan_changes(want_tree=True, want_branch=False,
170
 
                                      want_bound=False, want_reference=True)
171
 
        if not reconfiguration.changes_planned():
172
 
            raise errors.AlreadyLightweightCheckout(bzrdir)
173
 
        return reconfiguration
174
 
 
175
 
    @classmethod
176
 
    def to_use_shared(klass, bzrdir):
177
 
        """Convert a standalone branch into a repository branch"""
178
 
        reconfiguration = klass(bzrdir)
179
 
        reconfiguration._set_use_shared(use_shared=True)
180
 
        if not reconfiguration.changes_planned():
181
 
            raise errors.AlreadyUsingShared(bzrdir)
182
 
        return reconfiguration
183
 
 
184
 
    @classmethod
185
 
    def to_standalone(klass, bzrdir):
186
 
        """Convert a repository branch into a standalone branch"""
187
 
        reconfiguration = klass(bzrdir)
188
 
        reconfiguration._set_use_shared(use_shared=False)
189
 
        if not reconfiguration.changes_planned():
190
 
            raise errors.AlreadyStandalone(bzrdir)
191
 
        return reconfiguration
192
 
 
193
 
    @classmethod
194
 
    def set_repository_trees(klass, bzrdir, with_trees):
195
 
        """Adjust a repository's working tree presence default"""
196
 
        reconfiguration = klass(bzrdir)
197
 
        if not reconfiguration.repository.is_shared():
198
 
            raise errors.ReconfigurationNotSupported(reconfiguration.bzrdir)
199
 
        if with_trees and reconfiguration.repository.make_working_trees():
200
 
            raise errors.AlreadyWithTrees(bzrdir)
201
 
        elif (not with_trees
202
 
              and not reconfiguration.repository.make_working_trees()):
203
 
            raise errors.AlreadyWithNoTrees(bzrdir)
204
 
        else:
205
 
            reconfiguration._repository_trees = with_trees
206
 
        return reconfiguration
207
 
 
208
 
    def _plan_changes(self, want_tree, want_branch, want_bound,
209
 
                      want_reference):
210
 
        """Determine which changes are needed to assume the configuration"""
211
 
        if not want_branch and not want_reference:
212
 
            raise errors.ReconfigurationNotSupported(self.bzrdir)
213
 
        if want_branch and want_reference:
214
 
            raise errors.ReconfigurationNotSupported(self.bzrdir)
215
 
        if self.repository is None:
216
 
            if not want_reference:
217
 
                self._create_repository = True
218
 
        else:
219
 
            if want_reference and (
220
 
                self.repository.user_url == self.bzrdir.user_url):
221
 
                if not self.repository.is_shared():
222
 
                    self._destroy_repository = True
223
 
        if self.referenced_branch is None:
224
 
            if want_reference:
225
 
                self._create_reference = True
226
 
                if self.local_branch is not None:
227
 
                    self._destroy_branch = True
228
 
        else:
229
 
            if not want_reference:
230
 
                self._destroy_reference = True
231
 
        if self.local_branch is None:
232
 
            if want_branch is True:
233
 
                self._create_branch = True
234
 
                if want_bound:
235
 
                    self._bind = True
236
 
        else:
237
 
            if want_bound:
238
 
                if self.local_branch.get_bound_location() is None:
239
 
                    self._bind = True
240
 
            else:
241
 
                if self.local_branch.get_bound_location() is not None:
242
 
                    self._unbind = True
243
 
        if not want_tree and self.tree is not None:
244
 
            self._destroy_tree = True
245
 
        if want_tree and self.tree is None:
246
 
            self._create_tree = True
247
 
 
248
 
    def _set_use_shared(self, use_shared=None):
249
 
        if use_shared is None:
250
 
            return
251
 
        if use_shared:
252
 
            if self.local_repository is not None:
253
 
                self._destroy_repository = True
254
 
        else:
255
 
            if self.local_repository is None:
256
 
                self._create_repository = True
257
 
 
258
 
    def changes_planned(self):
259
 
        """Return True if changes are planned, False otherwise"""
260
 
        return (self._unbind or self._bind or self._destroy_tree
261
 
                or self._create_tree or self._destroy_reference
262
 
                or self._create_branch or self._create_repository
263
 
                or self._create_reference or self._destroy_repository)
264
 
 
265
 
    def _check(self):
266
 
        """Raise if reconfiguration would destroy local changes"""
267
 
        if self._destroy_tree and self.tree.has_changes():
268
 
                raise errors.UncommittedChanges(self.tree)
269
 
        if self._create_reference and self.local_branch is not None:
270
 
            reference_branch = branch.Branch.open(self._select_bind_location())
271
 
            if (reference_branch.last_revision() !=
272
 
                self.local_branch.last_revision()):
273
 
                raise errors.UnsyncedBranches(self.bzrdir, reference_branch)
274
 
 
275
 
    def _select_bind_location(self):
276
 
        """Select a location to bind or create a reference to.
277
 
 
278
 
        Preference is:
279
 
        1. user specified location
280
 
        2. branch reference location (it's a kind of bind location)
281
 
        3. current bind location
282
 
        4. previous bind location (it was a good choice once)
283
 
        5. push location (it's writeable, so committable)
284
 
        6. parent location (it's pullable, so update-from-able)
285
 
        """
286
 
        if self.new_bound_location is not None:
287
 
            return self.new_bound_location
288
 
        if self.local_branch is not None:
289
 
            bound = self.local_branch.get_bound_location()
290
 
            if bound is not None:
291
 
                return bound
292
 
            old_bound = self.local_branch.get_old_bound_location()
293
 
            if old_bound is not None:
294
 
                return old_bound
295
 
            push_location = self.local_branch.get_push_location()
296
 
            if push_location is not None:
297
 
                return push_location
298
 
            parent = self.local_branch.get_parent()
299
 
            if parent is not None:
300
 
                return parent
301
 
        elif self.referenced_branch is not None:
302
 
            return self.referenced_branch.base
303
 
        raise errors.NoBindLocation(self.bzrdir)
304
 
 
305
 
    def apply(self, force=False):
306
 
        """Apply the reconfiguration
307
 
 
308
 
        :param force: If true, the reconfiguration is applied even if it will
309
 
            destroy local changes.
310
 
        :raise errors.UncommittedChanges: if the local tree is to be destroyed
311
 
            but contains uncommitted changes.
312
 
        :raise errors.NoBindLocation: if no bind location was specified and
313
 
            none could be autodetected.
314
 
        """
315
 
        if not force:
316
 
            self._check()
317
 
        if self._create_repository:
318
 
            if self.local_branch and not self._destroy_branch:
319
 
                old_repo = self.local_branch.repository
320
 
            elif self._create_branch and self.referenced_branch is not None:
321
 
                old_repo = self.referenced_branch.repository
322
 
            else:
323
 
                old_repo = None
324
 
            if old_repo is not None:
325
 
                repository_format = old_repo._format
326
 
            else:
327
 
                repository_format = None
328
 
            if repository_format is not None:
329
 
                repo = repository_format.initialize(self.bzrdir)
330
 
            else:
331
 
                repo = self.bzrdir.create_repository()
332
 
            if self.local_branch and not self._destroy_branch:
333
 
                repo.fetch(self.local_branch.repository,
334
 
                           self.local_branch.last_revision())
335
 
        else:
336
 
            repo = self.repository
337
 
        if self._create_branch and self.referenced_branch is not None:
338
 
            repo.fetch(self.referenced_branch.repository,
339
 
                       self.referenced_branch.last_revision())
340
 
        if self._create_reference:
341
 
            reference_branch = branch.Branch.open(self._select_bind_location())
342
 
        if self._destroy_repository:
343
 
            if self._create_reference:
344
 
                reference_branch.repository.fetch(self.repository)
345
 
            elif self.local_branch is not None and not self._destroy_branch:
346
 
                up = self.local_branch.user_transport.clone('..')
347
 
                up_bzrdir = bzrdir.BzrDir.open_containing_from_transport(up)[0]
348
 
                new_repo = up_bzrdir.find_repository()
349
 
                new_repo.fetch(self.repository)
350
 
        last_revision_info = None
351
 
        if self._destroy_reference:
352
 
            last_revision_info = self.referenced_branch.last_revision_info()
353
 
            self.bzrdir.destroy_branch()
354
 
        if self._destroy_branch:
355
 
            last_revision_info = self.local_branch.last_revision_info()
356
 
            if self._create_reference:
357
 
                self.local_branch.tags.merge_to(reference_branch.tags)
358
 
            self.bzrdir.destroy_branch()
359
 
        if self._create_branch:
360
 
            local_branch = self.bzrdir.create_branch()
361
 
            if last_revision_info is not None:
362
 
                local_branch.set_last_revision_info(*last_revision_info)
363
 
            if self._destroy_reference:
364
 
                self.referenced_branch.tags.merge_to(local_branch.tags)
365
 
                self.referenced_branch.update_references(local_branch)
366
 
        else:
367
 
            local_branch = self.local_branch
368
 
        if self._create_reference:
369
 
            format = branch.BranchReferenceFormat().initialize(self.bzrdir,
370
 
                target_branch=reference_branch)
371
 
        if self._destroy_tree:
372
 
            self.bzrdir.destroy_workingtree()
373
 
        if self._create_tree:
374
 
            self.bzrdir.create_workingtree()
375
 
        if self._unbind:
376
 
            self.local_branch.unbind()
377
 
        if self._bind:
378
 
            bind_location = self._select_bind_location()
379
 
            local_branch.bind(branch.Branch.open(bind_location))
380
 
        if self._destroy_repository:
381
 
            self.bzrdir.destroy_repository()
382
 
        if self._repository_trees is not None:
383
 
            repo.set_make_working_trees(self._repository_trees)