1
# Copyright (C) 2007 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Reconfigure a bzrdir into a new tree/branch/repository layout"""
25
class Reconfigure(object):
27
def __init__(self, bzrdir, new_bound_location=None):
29
self.new_bound_location = new_bound_location
31
self.repository = self.bzrdir.find_repository()
32
except errors.NoRepositoryPresent:
33
self.repository = None
35
if (self.repository.bzrdir.root_transport.base ==
36
self.bzrdir.root_transport.base):
37
self.local_repository = self.repository
39
self.local_repository = None
41
branch = self.bzrdir.open_branch()
42
if branch.bzrdir.root_transport.base == bzrdir.root_transport.base:
43
self.local_branch = branch
44
self.referenced_branch = None
46
self.local_branch = None
47
self.referenced_branch = branch
48
except errors.NotBranchError:
49
self.local_branch = None
50
self.referenced_branch = None
52
self.tree = bzrdir.open_workingtree()
53
except errors.NoWorkingTree:
57
self._destroy_reference = False
58
self._create_reference = False
59
self._destroy_branch = False
60
self._create_branch = False
61
self._destroy_tree = False
62
self._create_tree = False
63
self._create_repository = False
64
self._destroy_repository = False
65
self._repository_trees = None
68
def to_branch(bzrdir):
69
"""Return a Reconfiguration to convert this bzrdir into a branch
71
:param bzrdir: The bzrdir to reconfigure
72
:raise errors.AlreadyBranch: if bzrdir is already a branch
74
reconfiguration = Reconfigure(bzrdir)
75
reconfiguration._plan_changes(want_tree=False, want_branch=True,
76
want_bound=False, want_reference=False)
77
if not reconfiguration.changes_planned():
78
raise errors.AlreadyBranch(bzrdir)
79
return reconfiguration
83
"""Return a Reconfiguration to convert this bzrdir into a tree
85
:param bzrdir: The bzrdir to reconfigure
86
:raise errors.AlreadyTree: if bzrdir is already a tree
88
reconfiguration = Reconfigure(bzrdir)
89
reconfiguration._plan_changes(want_tree=True, want_branch=True,
90
want_bound=False, want_reference=False)
91
if not reconfiguration.changes_planned():
92
raise errors.AlreadyTree(bzrdir)
93
return reconfiguration
96
def to_checkout(bzrdir, bound_location=None):
97
"""Return a Reconfiguration to convert this bzrdir into a checkout
99
:param bzrdir: The bzrdir to reconfigure
100
:param bound_location: The location the checkout should be bound to.
101
:raise errors.AlreadyCheckout: if bzrdir is already a checkout
103
reconfiguration = Reconfigure(bzrdir, bound_location)
104
reconfiguration._plan_changes(want_tree=True, want_branch=True,
105
want_bound=True, want_reference=False)
106
if not reconfiguration.changes_planned():
107
raise errors.AlreadyCheckout(bzrdir)
108
return reconfiguration
111
def to_lightweight_checkout(klass, bzrdir, reference_location=None):
112
"""Make a Reconfiguration to convert bzrdir into a lightweight checkout
114
:param bzrdir: The bzrdir to reconfigure
115
:param bound_location: The location the checkout should be bound to.
116
:raise errors.AlreadyLightweightCheckout: if bzrdir is already a
119
reconfiguration = klass(bzrdir, reference_location)
120
reconfiguration._plan_changes(want_tree=True, want_branch=False,
121
want_bound=False, want_reference=True)
122
if not reconfiguration.changes_planned():
123
raise errors.AlreadyLightweightCheckout(bzrdir)
124
return reconfiguration
127
def to_use_shared(klass, bzrdir):
128
"""Convert a standalone branch into a repository branch"""
129
reconfiguration = klass(bzrdir)
130
reconfiguration._set_use_shared(use_shared=True)
131
if not reconfiguration.changes_planned():
132
raise errors.AlreadyUsingShared(bzrdir)
133
return reconfiguration
136
def to_standalone(klass, bzrdir):
137
"""Convert a repository branch into a standalone branch"""
138
reconfiguration = klass(bzrdir)
139
reconfiguration._set_use_shared(use_shared=False)
140
if not reconfiguration.changes_planned():
141
raise errors.AlreadyStandalone(bzrdir)
142
return reconfiguration
145
def set_repository_trees(klass, bzrdir, with_trees):
146
"""Adjust a repository's working tree presence default"""
147
reconfiguration = klass(bzrdir)
148
if not reconfiguration.repository.is_shared():
149
raise errors.ReconfigurationNotSupported(reconfiguration.bzrdir)
150
if with_trees and reconfiguration.repository.make_working_trees():
151
raise errors.AlreadyWithTrees(bzrdir)
153
and not reconfiguration.repository.make_working_trees()):
154
raise errors.AlreadyWithNoTrees(bzrdir)
156
reconfiguration._repository_trees = with_trees
157
return reconfiguration
159
def _plan_changes(self, want_tree, want_branch, want_bound,
161
"""Determine which changes are needed to assume the configuration"""
162
if not want_branch and not want_reference:
163
raise errors.ReconfigurationNotSupported(self.bzrdir)
164
if want_branch and want_reference:
165
raise errors.ReconfigurationNotSupported(self.bzrdir)
166
if self.repository is None:
167
if not want_reference:
168
self._create_repository = True
170
if want_reference and (self.repository.bzrdir.root_transport.base
171
== self.bzrdir.root_transport.base):
172
if not self.repository.is_shared():
173
self._destroy_repository = True
174
if self.referenced_branch is None:
176
self._create_reference = True
177
if self.local_branch is not None:
178
self._destroy_branch = True
180
if not want_reference:
181
self._destroy_reference = True
182
if self.local_branch is None:
183
if want_branch is True:
184
self._create_branch = True
189
if self.local_branch.get_bound_location() is None:
192
if self.local_branch.get_bound_location() is not None:
194
if not want_tree and self.tree is not None:
195
self._destroy_tree = True
196
if want_tree and self.tree is None:
197
self._create_tree = True
199
def _set_use_shared(self, use_shared=None):
200
if use_shared is None:
203
if self.local_repository is not None:
204
self._destroy_repository = True
206
if self.local_repository is None:
207
self._create_repository = True
209
def changes_planned(self):
210
"""Return True if changes are planned, False otherwise"""
211
return (self._unbind or self._bind or self._destroy_tree
212
or self._create_tree or self._destroy_reference
213
or self._create_branch or self._create_repository
214
or self._create_reference or self._destroy_repository)
217
"""Raise if reconfiguration would destroy local changes"""
218
if self._destroy_tree:
219
changes = self.tree.changes_from(self.tree.basis_tree())
220
if changes.has_changed():
221
raise errors.UncommittedChanges(self.tree)
222
if self._create_reference and self.local_branch is not None:
223
reference_branch = branch.Branch.open(self._select_bind_location())
224
if (reference_branch.last_revision() !=
225
self.local_branch.last_revision()):
226
raise errors.UnsyncedBranches(self.bzrdir, reference_branch)
228
def _select_bind_location(self):
229
"""Select a location to bind or create a reference to.
232
1. user specified location
233
2. branch reference location (it's a kind of bind location)
234
3. current bind location
235
4. previous bind location (it was a good choice once)
236
5. push location (it's writeable, so committable)
237
6. parent location (it's pullable, so update-from-able)
239
if self.new_bound_location is not None:
240
return self.new_bound_location
241
if self.local_branch is not None:
242
bound = self.local_branch.get_bound_location()
243
if bound is not None:
245
old_bound = self.local_branch.get_old_bound_location()
246
if old_bound is not None:
248
push_location = self.local_branch.get_push_location()
249
if push_location is not None:
251
parent = self.local_branch.get_parent()
252
if parent is not None:
254
elif self.referenced_branch is not None:
255
return self.referenced_branch.base
256
raise errors.NoBindLocation(self.bzrdir)
258
def apply(self, force=False):
259
"""Apply the reconfiguration
261
:param force: If true, the reconfiguration is applied even if it will
262
destroy local changes.
263
:raise errors.UncommittedChanges: if the local tree is to be destroyed
264
but contains uncommitted changes.
265
:raise errors.NoBindLocation: if no bind location was specified and
266
none could be autodetected.
270
if self._create_repository:
271
repo = self.bzrdir.create_repository()
272
if self.local_branch and not self._destroy_branch:
273
repo.fetch(self.local_branch.repository,
274
self.local_branch.last_revision())
276
repo = self.repository
277
if self._create_branch and self.referenced_branch is not None:
278
repo.fetch(self.referenced_branch.repository,
279
self.referenced_branch.last_revision())
280
if self._create_reference:
281
reference_branch = branch.Branch.open(self._select_bind_location())
282
if self._destroy_repository:
283
if self._create_reference:
284
reference_branch.repository.fetch(self.repository)
285
elif self.local_branch is not None and not self._destroy_branch:
286
up = self.local_branch.bzrdir.root_transport.clone('..')
287
up_bzrdir = bzrdir.BzrDir.open_containing_from_transport(up)[0]
288
new_repo = up_bzrdir.find_repository()
289
new_repo.fetch(self.repository)
290
last_revision_info = None
291
if self._destroy_reference:
292
last_revision_info = self.referenced_branch.last_revision_info()
293
self.bzrdir.destroy_branch()
294
if self._destroy_branch:
295
last_revision_info = self.local_branch.last_revision_info()
296
if self._create_reference:
297
self.local_branch.tags.merge_to(reference_branch.tags)
298
self.bzrdir.destroy_branch()
299
if self._create_branch:
300
local_branch = self.bzrdir.create_branch()
301
if last_revision_info is not None:
302
local_branch.set_last_revision_info(*last_revision_info)
303
if self._destroy_reference:
304
self.referenced_branch.tags.merge_to(local_branch.tags)
306
local_branch = self.local_branch
307
if self._create_reference:
308
format = branch.BranchReferenceFormat().initialize(self.bzrdir,
310
if self._destroy_tree:
311
self.bzrdir.destroy_workingtree()
312
if self._create_tree:
313
self.bzrdir.create_workingtree()
315
self.local_branch.unbind()
317
bind_location = self._select_bind_location()
318
local_branch.bind(branch.Branch.open(bind_location))
319
if self._destroy_repository:
320
self.bzrdir.destroy_repository()
321
if self._repository_trees is not None:
322
repo.set_make_working_trees(self._repository_trees)