/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/reconfigure.py

  • Committer: Vincent Ladeuil
  • Date: 2012-01-18 14:09:19 UTC
  • mto: This revision was merged to the branch mainline in revision 6468.
  • Revision ID: v.ladeuil+lp@free.fr-20120118140919-rlvdrhpc0nq1lbwi
Change set/remove to require a lock for the branch config files.

This means that tests (or any plugin for that matter) do not requires an
explicit lock on the branch anymore to change a single option. This also
means the optimisation becomes "opt-in" and as such won't be as
spectacular as it may be and/or harder to get right (nothing fails
anymore).

This reduces the diff by ~300 lines.

Code/tests that were updating more than one config option is still taking
a lock to at least avoid some IOs and demonstrate the benefits through
the decreased number of hpss calls.

The duplication between BranchStack and BranchOnlyStack will be removed
once the same sharing is in place for local config files, at which point
the Stack class itself may be able to host the changes.

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