1
# Copyright (C) 2005, 2006 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
"""BzrDir logic. The BzrDir is the basic control directory used by bzr.
19
At format 7 this was split out into Branch, Repository and Checkout control
23
# TODO: remove unittest dependency; put that stuff inside the test suite
25
# TODO: Can we move specific formats into separate modules to make this file
28
from cStringIO import StringIO
31
from bzrlib.lazy_import import lazy_import
32
lazy_import(globals(), """
33
from copy import deepcopy
34
from stat import S_ISDIR
43
revision as _mod_revision,
48
from bzrlib.osutils import (
53
from bzrlib.store.revision.text import TextRevisionStore
54
from bzrlib.store.text import TextStore
55
from bzrlib.store.versioned import WeaveStore
56
from bzrlib.transactions import WriteTransaction
57
from bzrlib.transport import get_transport
58
from bzrlib.weave import Weave
61
from bzrlib.trace import mutter
62
from bzrlib.transport.local import LocalTransport
66
"""A .bzr control diretory.
68
BzrDir instances let you create or open any of the things that can be
69
found within .bzr - checkouts, branches and repositories.
72
the transport which this bzr dir is rooted at (i.e. file:///.../.bzr/)
74
a transport connected to the directory this bzr was opened from.
78
"""Invoke break_lock on the first object in the bzrdir.
80
If there is a tree, the tree is opened and break_lock() called.
81
Otherwise, branch is tried, and finally repository.
83
# XXX: This seems more like a UI function than something that really
84
# belongs in this class.
86
thing_to_unlock = self.open_workingtree()
87
except (errors.NotLocalUrl, errors.NoWorkingTree):
89
thing_to_unlock = self.open_branch()
90
except errors.NotBranchError:
92
thing_to_unlock = self.open_repository()
93
except errors.NoRepositoryPresent:
95
thing_to_unlock.break_lock()
97
def can_convert_format(self):
98
"""Return true if this bzrdir is one whose format we can convert from."""
101
def check_conversion_target(self, target_format):
102
target_repo_format = target_format.repository_format
103
source_repo_format = self._format.repository_format
104
source_repo_format.check_conversion_target(target_repo_format)
107
def _check_supported(format, allow_unsupported):
108
"""Check whether format is a supported format.
110
If allow_unsupported is True, this is a no-op.
112
if not allow_unsupported and not format.is_supported():
113
# see open_downlevel to open legacy branches.
114
raise errors.UnsupportedFormatError(format=format)
116
def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
117
"""Clone this bzrdir and its contents to url verbatim.
119
If urls last component does not exist, it will be created.
121
if revision_id is not None, then the clone operation may tune
122
itself to download less data.
123
:param force_new_repo: Do not use a shared repository for the target
124
even if one is available.
127
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
128
result = self._format.initialize(url)
130
local_repo = self.find_repository()
131
except errors.NoRepositoryPresent:
134
# may need to copy content in
136
result_repo = local_repo.clone(
138
revision_id=revision_id,
140
result_repo.set_make_working_trees(local_repo.make_working_trees())
143
result_repo = result.find_repository()
144
# fetch content this dir needs.
146
# XXX FIXME RBC 20060214 need tests for this when the basis
148
result_repo.fetch(basis_repo, revision_id=revision_id)
149
result_repo.fetch(local_repo, revision_id=revision_id)
150
except errors.NoRepositoryPresent:
151
# needed to make one anyway.
152
result_repo = local_repo.clone(
154
revision_id=revision_id,
156
result_repo.set_make_working_trees(local_repo.make_working_trees())
157
# 1 if there is a branch present
158
# make sure its content is available in the target repository
161
self.open_branch().clone(result, revision_id=revision_id)
162
except errors.NotBranchError:
165
self.open_workingtree().clone(result, basis=basis_tree)
166
except (errors.NoWorkingTree, errors.NotLocalUrl):
170
def _get_basis_components(self, basis):
171
"""Retrieve the basis components that are available at basis."""
173
return None, None, None
175
basis_tree = basis.open_workingtree()
176
basis_branch = basis_tree.branch
177
basis_repo = basis_branch.repository
178
except (errors.NoWorkingTree, errors.NotLocalUrl):
181
basis_branch = basis.open_branch()
182
basis_repo = basis_branch.repository
183
except errors.NotBranchError:
186
basis_repo = basis.open_repository()
187
except errors.NoRepositoryPresent:
189
return basis_repo, basis_branch, basis_tree
191
# TODO: This should be given a Transport, and should chdir up; otherwise
192
# this will open a new connection.
193
def _make_tail(self, url):
194
head, tail = urlutils.split(url)
195
if tail and tail != '.':
196
t = get_transport(head)
199
except errors.FileExists:
202
# TODO: Should take a Transport
204
def create(cls, base):
205
"""Create a new BzrDir at the url 'base'.
207
This will call the current default formats initialize with base
208
as the only parameter.
210
If you need a specific format, consider creating an instance
211
of that and calling initialize().
213
if cls is not BzrDir:
214
raise AssertionError("BzrDir.create always creates the default format, "
215
"not one of %r" % cls)
216
head, tail = urlutils.split(base)
217
if tail and tail != '.':
218
t = get_transport(head)
221
except errors.FileExists:
223
return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
225
def create_branch(self):
226
"""Create a branch in this BzrDir.
228
The bzrdirs format will control what branch format is created.
229
For more control see BranchFormatXX.create(a_bzrdir).
231
raise NotImplementedError(self.create_branch)
234
def create_branch_and_repo(base, force_new_repo=False):
235
"""Create a new BzrDir, Branch and Repository at the url 'base'.
237
This will use the current default BzrDirFormat, and use whatever
238
repository format that that uses via bzrdir.create_branch and
239
create_repository. If a shared repository is available that is used
242
The created Branch object is returned.
244
:param base: The URL to create the branch at.
245
:param force_new_repo: If True a new repository is always created.
247
bzrdir = BzrDir.create(base)
248
bzrdir._find_or_create_repository(force_new_repo)
249
return bzrdir.create_branch()
251
def _find_or_create_repository(self, force_new_repo):
252
"""Create a new repository if needed, returning the repository."""
254
return self.create_repository()
256
return self.find_repository()
257
except errors.NoRepositoryPresent:
258
return self.create_repository()
261
def create_branch_convenience(base, force_new_repo=False,
262
force_new_tree=None, format=None):
263
"""Create a new BzrDir, Branch and Repository at the url 'base'.
265
This is a convenience function - it will use an existing repository
266
if possible, can be told explicitly whether to create a working tree or
269
This will use the current default BzrDirFormat, and use whatever
270
repository format that that uses via bzrdir.create_branch and
271
create_repository. If a shared repository is available that is used
272
preferentially. Whatever repository is used, its tree creation policy
275
The created Branch object is returned.
276
If a working tree cannot be made due to base not being a file:// url,
277
no error is raised unless force_new_tree is True, in which case no
278
data is created on disk and NotLocalUrl is raised.
280
:param base: The URL to create the branch at.
281
:param force_new_repo: If True a new repository is always created.
282
:param force_new_tree: If True or False force creation of a tree or
283
prevent such creation respectively.
284
:param format: Override for the for the bzrdir format to create
287
# check for non local urls
288
t = get_transport(safe_unicode(base))
289
if not isinstance(t, LocalTransport):
290
raise errors.NotLocalUrl(base)
292
bzrdir = BzrDir.create(base)
294
bzrdir = format.initialize(base)
295
repo = bzrdir._find_or_create_repository(force_new_repo)
296
result = bzrdir.create_branch()
297
if force_new_tree or (repo.make_working_trees() and
298
force_new_tree is None):
300
bzrdir.create_workingtree()
301
except errors.NotLocalUrl:
306
def create_repository(base, shared=False):
307
"""Create a new BzrDir and Repository at the url 'base'.
309
This will use the current default BzrDirFormat, and use whatever
310
repository format that that uses for bzrdirformat.create_repository.
312
:param shared: Create a shared repository rather than a standalone
314
The Repository object is returned.
316
This must be overridden as an instance method in child classes, where
317
it should take no parameters and construct whatever repository format
318
that child class desires.
320
bzrdir = BzrDir.create(base)
321
return bzrdir.create_repository(shared)
324
def create_standalone_workingtree(base):
325
"""Create a new BzrDir, WorkingTree, Branch and Repository at 'base'.
327
'base' must be a local path or a file:// url.
329
This will use the current default BzrDirFormat, and use whatever
330
repository format that that uses for bzrdirformat.create_workingtree,
331
create_branch and create_repository.
333
:return: The WorkingTree object.
335
t = get_transport(safe_unicode(base))
336
if not isinstance(t, LocalTransport):
337
raise errors.NotLocalUrl(base)
338
bzrdir = BzrDir.create_branch_and_repo(safe_unicode(base),
339
force_new_repo=True).bzrdir
340
return bzrdir.create_workingtree()
342
def create_workingtree(self, revision_id=None):
343
"""Create a working tree at this BzrDir.
345
revision_id: create it as of this revision id.
347
raise NotImplementedError(self.create_workingtree)
349
def destroy_workingtree(self):
350
"""Destroy the working tree at this BzrDir.
352
Formats that do not support this may raise UnsupportedOperation.
354
raise NotImplementedError(self.destroy_workingtree)
356
def destroy_workingtree_metadata(self):
357
"""Destroy the control files for the working tree at this BzrDir.
359
The contents of working tree files are not affected.
360
Formats that do not support this may raise UnsupportedOperation.
362
raise NotImplementedError(self.destroy_workingtree_metadata)
364
def find_repository(self):
365
"""Find the repository that should be used for a_bzrdir.
367
This does not require a branch as we use it to find the repo for
368
new branches as well as to hook existing branches up to their
372
return self.open_repository()
373
except errors.NoRepositoryPresent:
375
next_transport = self.root_transport.clone('..')
377
# find the next containing bzrdir
379
found_bzrdir = BzrDir.open_containing_from_transport(
381
except errors.NotBranchError:
383
raise errors.NoRepositoryPresent(self)
384
# does it have a repository ?
386
repository = found_bzrdir.open_repository()
387
except errors.NoRepositoryPresent:
388
next_transport = found_bzrdir.root_transport.clone('..')
389
if (found_bzrdir.root_transport.base == next_transport.base):
390
# top of the file system
394
if ((found_bzrdir.root_transport.base ==
395
self.root_transport.base) or repository.is_shared()):
398
raise errors.NoRepositoryPresent(self)
399
raise errors.NoRepositoryPresent(self)
401
def get_branch_reference(self):
402
"""Return the referenced URL for the branch in this bzrdir.
404
:raises NotBranchError: If there is no Branch.
405
:return: The URL the branch in this bzrdir references if it is a
406
reference branch, or None for regular branches.
410
def get_branch_transport(self, branch_format):
411
"""Get the transport for use by branch format in this BzrDir.
413
Note that bzr dirs that do not support format strings will raise
414
IncompatibleFormat if the branch format they are given has
415
a format string, and vice versa.
417
If branch_format is None, the transport is returned with no
418
checking. if it is not None, then the returned transport is
419
guaranteed to point to an existing directory ready for use.
421
raise NotImplementedError(self.get_branch_transport)
423
def get_repository_transport(self, repository_format):
424
"""Get the transport for use by repository format in this BzrDir.
426
Note that bzr dirs that do not support format strings will raise
427
IncompatibleFormat if the repository format they are given has
428
a format string, and vice versa.
430
If repository_format is None, the transport is returned with no
431
checking. if it is not None, then the returned transport is
432
guaranteed to point to an existing directory ready for use.
434
raise NotImplementedError(self.get_repository_transport)
436
def get_workingtree_transport(self, tree_format):
437
"""Get the transport for use by workingtree format in this BzrDir.
439
Note that bzr dirs that do not support format strings will raise
440
IncompatibleFormat if the workingtree format they are given has
441
a format string, and vice versa.
443
If workingtree_format is None, the transport is returned with no
444
checking. if it is not None, then the returned transport is
445
guaranteed to point to an existing directory ready for use.
447
raise NotImplementedError(self.get_workingtree_transport)
449
def __init__(self, _transport, _format):
450
"""Initialize a Bzr control dir object.
452
Only really common logic should reside here, concrete classes should be
453
made with varying behaviours.
455
:param _format: the format that is creating this BzrDir instance.
456
:param _transport: the transport this dir is based at.
458
self._format = _format
459
self.transport = _transport.clone('.bzr')
460
self.root_transport = _transport
462
def is_control_filename(self, filename):
463
"""True if filename is the name of a path which is reserved for bzrdir's.
465
:param filename: A filename within the root transport of this bzrdir.
467
This is true IF and ONLY IF the filename is part of the namespace reserved
468
for bzr control dirs. Currently this is the '.bzr' directory in the root
469
of the root_transport. it is expected that plugins will need to extend
470
this in the future - for instance to make bzr talk with svn working
473
# this might be better on the BzrDirFormat class because it refers to
474
# all the possible bzrdir disk formats.
475
# This method is tested via the workingtree is_control_filename tests-
476
# it was extracted from WorkingTree.is_control_filename. If the methods
477
# contract is extended beyond the current trivial implementation please
478
# add new tests for it to the appropriate place.
479
return filename == '.bzr' or filename.startswith('.bzr/')
481
def needs_format_conversion(self, format=None):
482
"""Return true if this bzrdir needs convert_format run on it.
484
For instance, if the repository format is out of date but the
485
branch and working tree are not, this should return True.
487
:param format: Optional parameter indicating a specific desired
488
format we plan to arrive at.
490
raise NotImplementedError(self.needs_format_conversion)
493
def open_unsupported(base):
494
"""Open a branch which is not supported."""
495
return BzrDir.open(base, _unsupported=True)
498
def open(base, _unsupported=False):
499
"""Open an existing bzrdir, rooted at 'base' (url)
501
_unsupported is a private parameter to the BzrDir class.
503
t = get_transport(base)
504
return BzrDir.open_from_transport(t, _unsupported=_unsupported)
507
def open_from_transport(transport, _unsupported=False):
508
"""Open a bzrdir within a particular directory.
510
:param transport: Transport containing the bzrdir.
511
:param _unsupported: private.
513
format = BzrDirFormat.find_format(transport)
514
BzrDir._check_supported(format, _unsupported)
515
return format.open(transport, _found=True)
517
def open_branch(self, unsupported=False):
518
"""Open the branch object at this BzrDir if one is present.
520
If unsupported is True, then no longer supported branch formats can
523
TODO: static convenience version of this?
525
raise NotImplementedError(self.open_branch)
528
def open_containing(url):
529
"""Open an existing branch which contains url.
531
:param url: url to search from.
532
See open_containing_from_transport for more detail.
534
return BzrDir.open_containing_from_transport(get_transport(url))
537
def open_containing_from_transport(a_transport):
538
"""Open an existing branch which contains a_transport.base
540
This probes for a branch at a_transport, and searches upwards from there.
542
Basically we keep looking up until we find the control directory or
543
run into the root. If there isn't one, raises NotBranchError.
544
If there is one and it is either an unrecognised format or an unsupported
545
format, UnknownFormatError or UnsupportedFormatError are raised.
546
If there is one, it is returned, along with the unused portion of url.
548
:return: The BzrDir that contains the path, and a Unicode path
549
for the rest of the URL.
551
# this gets the normalised url back. I.e. '.' -> the full path.
552
url = a_transport.base
555
result = BzrDir.open_from_transport(a_transport)
556
return result, urlutils.unescape(a_transport.relpath(url))
557
except errors.NotBranchError, e:
559
new_t = a_transport.clone('..')
560
if new_t.base == a_transport.base:
561
# reached the root, whatever that may be
562
raise errors.NotBranchError(path=url)
565
def open_repository(self, _unsupported=False):
566
"""Open the repository object at this BzrDir if one is present.
568
This will not follow the Branch object pointer - its strictly a direct
569
open facility. Most client code should use open_branch().repository to
572
_unsupported is a private parameter, not part of the api.
573
TODO: static convenience version of this?
575
raise NotImplementedError(self.open_repository)
577
def open_workingtree(self, _unsupported=False):
578
"""Open the workingtree object at this BzrDir if one is present.
580
TODO: static convenience version of this?
582
raise NotImplementedError(self.open_workingtree)
584
def has_branch(self):
585
"""Tell if this bzrdir contains a branch.
587
Note: if you're going to open the branch, you should just go ahead
588
and try, and not ask permission first. (This method just opens the
589
branch and discards it, and that's somewhat expensive.)
594
except errors.NotBranchError:
597
def has_workingtree(self):
598
"""Tell if this bzrdir contains a working tree.
600
This will still raise an exception if the bzrdir has a workingtree that
601
is remote & inaccessible.
603
Note: if you're going to open the working tree, you should just go ahead
604
and try, and not ask permission first. (This method just opens the
605
workingtree and discards it, and that's somewhat expensive.)
608
self.open_workingtree()
610
except errors.NoWorkingTree:
613
def cloning_metadir(self, basis=None):
614
"""Produce a metadir suitable for cloning with"""
615
def related_repository(bzrdir):
617
branch = bzrdir.open_branch()
618
return branch.repository
619
except errors.NotBranchError:
621
return bzrdir.open_repository()
622
result_format = self._format.__class__()
625
source_repository = related_repository(self)
626
except errors.NoRepositoryPresent:
629
source_repository = related_repository(self)
630
result_format.repository_format = source_repository._format
631
except errors.NoRepositoryPresent:
635
def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
636
"""Create a copy of this bzrdir prepared for use as a new line of
639
If urls last component does not exist, it will be created.
641
Attributes related to the identity of the source branch like
642
branch nickname will be cleaned, a working tree is created
643
whether one existed before or not; and a local branch is always
646
if revision_id is not None, then the clone operation may tune
647
itself to download less data.
650
cloning_format = self.cloning_metadir(basis)
651
result = cloning_format.initialize(url)
652
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
654
source_branch = self.open_branch()
655
source_repository = source_branch.repository
656
except errors.NotBranchError:
659
source_repository = self.open_repository()
660
except errors.NoRepositoryPresent:
661
# copy the entire basis one if there is one
662
# but there is no repository.
663
source_repository = basis_repo
668
result_repo = result.find_repository()
669
except errors.NoRepositoryPresent:
671
if source_repository is None and result_repo is not None:
673
elif source_repository is None and result_repo is None:
674
# no repo available, make a new one
675
result.create_repository()
676
elif source_repository is not None and result_repo is None:
677
# have source, and want to make a new target repo
678
# we don't clone the repo because that preserves attributes
679
# like is_shared(), and we have not yet implemented a
680
# repository sprout().
681
result_repo = result.create_repository()
682
if result_repo is not None:
683
# fetch needed content into target.
685
# XXX FIXME RBC 20060214 need tests for this when the basis
687
result_repo.fetch(basis_repo, revision_id=revision_id)
688
if source_repository is not None:
689
result_repo.fetch(source_repository, revision_id=revision_id)
690
if source_branch is not None:
691
source_branch.sprout(result, revision_id=revision_id)
693
result.create_branch()
694
# TODO: jam 20060426 we probably need a test in here in the
695
# case that the newly sprouted branch is a remote one
696
if result_repo is None or result_repo.make_working_trees():
697
wt = result.create_workingtree()
698
if wt.inventory.root is None:
700
wt.set_root_id(self.open_workingtree.get_root_id())
701
except errors.NoWorkingTree:
706
class BzrDirPreSplitOut(BzrDir):
707
"""A common class for the all-in-one formats."""
709
def __init__(self, _transport, _format):
710
"""See BzrDir.__init__."""
711
super(BzrDirPreSplitOut, self).__init__(_transport, _format)
712
assert self._format._lock_class == lockable_files.TransportLock
713
assert self._format._lock_file_name == 'branch-lock'
714
self._control_files = lockable_files.LockableFiles(
715
self.get_branch_transport(None),
716
self._format._lock_file_name,
717
self._format._lock_class)
719
def break_lock(self):
720
"""Pre-splitout bzrdirs do not suffer from stale locks."""
721
raise NotImplementedError(self.break_lock)
723
def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
724
"""See BzrDir.clone()."""
725
from bzrlib.workingtree import WorkingTreeFormat2
727
result = self._format._initialize_for_clone(url)
728
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
729
self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
730
from_branch = self.open_branch()
731
from_branch.clone(result, revision_id=revision_id)
733
self.open_workingtree().clone(result, basis=basis_tree)
734
except errors.NotLocalUrl:
735
# make a new one, this format always has to have one.
737
WorkingTreeFormat2().initialize(result)
738
except errors.NotLocalUrl:
739
# but we cannot do it for remote trees.
740
to_branch = result.open_branch()
741
WorkingTreeFormat2().stub_initialize_remote(to_branch.control_files)
744
def create_branch(self):
745
"""See BzrDir.create_branch."""
746
return self.open_branch()
748
def create_repository(self, shared=False):
749
"""See BzrDir.create_repository."""
751
raise errors.IncompatibleFormat('shared repository', self._format)
752
return self.open_repository()
754
def create_workingtree(self, revision_id=None):
755
"""See BzrDir.create_workingtree."""
756
# this looks buggy but is not -really-
757
# clone and sprout will have set the revision_id
758
# and that will have set it for us, its only
759
# specific uses of create_workingtree in isolation
760
# that can do wonky stuff here, and that only
761
# happens for creating checkouts, which cannot be
762
# done on this format anyway. So - acceptable wart.
763
result = self.open_workingtree()
764
if revision_id is not None:
765
if revision_id == _mod_revision.NULL_REVISION:
766
result.set_parent_ids([])
768
result.set_parent_ids([revision_id])
771
def destroy_workingtree(self):
772
"""See BzrDir.destroy_workingtree."""
773
raise errors.UnsupportedOperation(self.destroy_workingtree, self)
775
def destroy_workingtree_metadata(self):
776
"""See BzrDir.destroy_workingtree_metadata."""
777
raise errors.UnsupportedOperation(self.destroy_workingtree_metadata,
780
def get_branch_transport(self, branch_format):
781
"""See BzrDir.get_branch_transport()."""
782
if branch_format is None:
783
return self.transport
785
branch_format.get_format_string()
786
except NotImplementedError:
787
return self.transport
788
raise errors.IncompatibleFormat(branch_format, self._format)
790
def get_repository_transport(self, repository_format):
791
"""See BzrDir.get_repository_transport()."""
792
if repository_format is None:
793
return self.transport
795
repository_format.get_format_string()
796
except NotImplementedError:
797
return self.transport
798
raise errors.IncompatibleFormat(repository_format, self._format)
800
def get_workingtree_transport(self, workingtree_format):
801
"""See BzrDir.get_workingtree_transport()."""
802
if workingtree_format is None:
803
return self.transport
805
workingtree_format.get_format_string()
806
except NotImplementedError:
807
return self.transport
808
raise errors.IncompatibleFormat(workingtree_format, self._format)
810
def needs_format_conversion(self, format=None):
811
"""See BzrDir.needs_format_conversion()."""
812
# if the format is not the same as the system default,
813
# an upgrade is needed.
815
format = BzrDirFormat.get_default_format()
816
return not isinstance(self._format, format.__class__)
818
def open_branch(self, unsupported=False):
819
"""See BzrDir.open_branch."""
820
from bzrlib.branch import BzrBranchFormat4
821
format = BzrBranchFormat4()
822
self._check_supported(format, unsupported)
823
return format.open(self, _found=True)
825
def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
826
"""See BzrDir.sprout()."""
827
from bzrlib.workingtree import WorkingTreeFormat2
829
result = self._format._initialize_for_clone(url)
830
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
832
self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
833
except errors.NoRepositoryPresent:
836
self.open_branch().sprout(result, revision_id=revision_id)
837
except errors.NotBranchError:
839
# we always want a working tree
840
WorkingTreeFormat2().initialize(result)
844
class BzrDir4(BzrDirPreSplitOut):
845
"""A .bzr version 4 control object.
847
This is a deprecated format and may be removed after sept 2006.
850
def create_repository(self, shared=False):
851
"""See BzrDir.create_repository."""
852
return self._format.repository_format.initialize(self, shared)
854
def needs_format_conversion(self, format=None):
855
"""Format 4 dirs are always in need of conversion."""
858
def open_repository(self):
859
"""See BzrDir.open_repository."""
860
from bzrlib.repository import RepositoryFormat4
861
return RepositoryFormat4().open(self, _found=True)
864
class BzrDir5(BzrDirPreSplitOut):
865
"""A .bzr version 5 control object.
867
This is a deprecated format and may be removed after sept 2006.
870
def open_repository(self):
871
"""See BzrDir.open_repository."""
872
from bzrlib.repository import RepositoryFormat5
873
return RepositoryFormat5().open(self, _found=True)
875
def open_workingtree(self, _unsupported=False):
876
"""See BzrDir.create_workingtree."""
877
from bzrlib.workingtree import WorkingTreeFormat2
878
return WorkingTreeFormat2().open(self, _found=True)
881
class BzrDir6(BzrDirPreSplitOut):
882
"""A .bzr version 6 control object.
884
This is a deprecated format and may be removed after sept 2006.
887
def open_repository(self):
888
"""See BzrDir.open_repository."""
889
from bzrlib.repository import RepositoryFormat6
890
return RepositoryFormat6().open(self, _found=True)
892
def open_workingtree(self, _unsupported=False):
893
"""See BzrDir.create_workingtree."""
894
from bzrlib.workingtree import WorkingTreeFormat2
895
return WorkingTreeFormat2().open(self, _found=True)
898
class BzrDirMeta1(BzrDir):
899
"""A .bzr meta version 1 control object.
901
This is the first control object where the
902
individual aspects are really split out: there are separate repository,
903
workingtree and branch subdirectories and any subset of the three can be
904
present within a BzrDir.
907
def can_convert_format(self):
908
"""See BzrDir.can_convert_format()."""
911
def create_branch(self):
912
"""See BzrDir.create_branch."""
913
from bzrlib.branch import BranchFormat
914
return BranchFormat.get_default_format().initialize(self)
916
def create_repository(self, shared=False):
917
"""See BzrDir.create_repository."""
918
return self._format.repository_format.initialize(self, shared)
920
def create_workingtree(self, revision_id=None):
921
"""See BzrDir.create_workingtree."""
922
from bzrlib.workingtree import WorkingTreeFormat
923
return WorkingTreeFormat.get_default_format().initialize(self, revision_id)
925
def destroy_workingtree(self):
926
"""See BzrDir.destroy_workingtree."""
927
wt = self.open_workingtree()
928
repository = wt.branch.repository
929
empty = repository.revision_tree(bzrlib.revision.NULL_REVISION)
930
wt.revert([], old_tree=empty)
931
self.destroy_workingtree_metadata()
933
def destroy_workingtree_metadata(self):
934
self.transport.delete_tree('checkout')
936
def _get_mkdir_mode(self):
937
"""Figure out the mode to use when creating a bzrdir subdir."""
938
temp_control = lockable_files.LockableFiles(self.transport, '',
939
lockable_files.TransportLock)
940
return temp_control._dir_mode
942
def get_branch_reference(self):
943
"""See BzrDir.get_branch_reference()."""
944
from bzrlib.branch import BranchFormat
945
format = BranchFormat.find_format(self)
946
return format.get_reference(self)
948
def get_branch_transport(self, branch_format):
949
"""See BzrDir.get_branch_transport()."""
950
if branch_format is None:
951
return self.transport.clone('branch')
953
branch_format.get_format_string()
954
except NotImplementedError:
955
raise errors.IncompatibleFormat(branch_format, self._format)
957
self.transport.mkdir('branch', mode=self._get_mkdir_mode())
958
except errors.FileExists:
960
return self.transport.clone('branch')
962
def get_repository_transport(self, repository_format):
963
"""See BzrDir.get_repository_transport()."""
964
if repository_format is None:
965
return self.transport.clone('repository')
967
repository_format.get_format_string()
968
except NotImplementedError:
969
raise errors.IncompatibleFormat(repository_format, self._format)
971
self.transport.mkdir('repository', mode=self._get_mkdir_mode())
972
except errors.FileExists:
974
return self.transport.clone('repository')
976
def get_workingtree_transport(self, workingtree_format):
977
"""See BzrDir.get_workingtree_transport()."""
978
if workingtree_format is None:
979
return self.transport.clone('checkout')
981
workingtree_format.get_format_string()
982
except NotImplementedError:
983
raise errors.IncompatibleFormat(workingtree_format, self._format)
985
self.transport.mkdir('checkout', mode=self._get_mkdir_mode())
986
except errors.FileExists:
988
return self.transport.clone('checkout')
990
def needs_format_conversion(self, format=None):
991
"""See BzrDir.needs_format_conversion()."""
993
format = BzrDirFormat.get_default_format()
994
if not isinstance(self._format, format.__class__):
995
# it is not a meta dir format, conversion is needed.
997
# we might want to push this down to the repository?
999
if not isinstance(self.open_repository()._format,
1000
format.repository_format.__class__):
1001
# the repository needs an upgrade.
1003
except errors.NoRepositoryPresent:
1005
# currently there are no other possible conversions for meta1 formats.
1008
def open_branch(self, unsupported=False):
1009
"""See BzrDir.open_branch."""
1010
from bzrlib.branch import BranchFormat
1011
format = BranchFormat.find_format(self)
1012
self._check_supported(format, unsupported)
1013
return format.open(self, _found=True)
1015
def open_repository(self, unsupported=False):
1016
"""See BzrDir.open_repository."""
1017
from bzrlib.repository import RepositoryFormat
1018
format = RepositoryFormat.find_format(self)
1019
self._check_supported(format, unsupported)
1020
return format.open(self, _found=True)
1022
def open_workingtree(self, unsupported=False):
1023
"""See BzrDir.open_workingtree."""
1024
from bzrlib.workingtree import WorkingTreeFormat
1025
format = WorkingTreeFormat.find_format(self)
1026
self._check_supported(format, unsupported)
1027
return format.open(self, _found=True)
1030
class BzrDirFormat(object):
1031
"""An encapsulation of the initialization and open routines for a format.
1033
Formats provide three things:
1034
* An initialization routine,
1038
Formats are placed in an dict by their format string for reference
1039
during bzrdir opening. These should be subclasses of BzrDirFormat
1042
Once a format is deprecated, just deprecate the initialize and open
1043
methods on the format class. Do not deprecate the object, as the
1044
object will be created every system load.
1047
_default_format = None
1048
"""The default format used for new .bzr dirs."""
1051
"""The known formats."""
1053
_control_formats = []
1054
"""The registered control formats - .bzr, ....
1056
This is a list of BzrDirFormat objects.
1059
_lock_file_name = 'branch-lock'
1061
# _lock_class must be set in subclasses to the lock type, typ.
1062
# TransportLock or LockDir
1065
def find_format(klass, transport):
1066
"""Return the format present at transport."""
1067
for format in klass._control_formats:
1069
return format.probe_transport(transport)
1070
except errors.NotBranchError:
1071
# this format does not find a control dir here.
1073
raise errors.NotBranchError(path=transport.base)
1076
def probe_transport(klass, transport):
1077
"""Return the .bzrdir style format present in a directory."""
1079
format_string = transport.get(".bzr/branch-format").read()
1080
except errors.NoSuchFile:
1081
raise errors.NotBranchError(path=transport.base)
1084
return klass._formats[format_string]
1086
raise errors.UnknownFormatError(format=format_string)
1089
def get_default_format(klass):
1090
"""Return the current default format."""
1091
return klass._default_format
1093
def get_format_string(self):
1094
"""Return the ASCII format string that identifies this format."""
1095
raise NotImplementedError(self.get_format_string)
1097
def get_format_description(self):
1098
"""Return the short description for this format."""
1099
raise NotImplementedError(self.get_format_description)
1101
def get_converter(self, format=None):
1102
"""Return the converter to use to convert bzrdirs needing converts.
1104
This returns a bzrlib.bzrdir.Converter object.
1106
This should return the best upgrader to step this format towards the
1107
current default format. In the case of plugins we can/should provide
1108
some means for them to extend the range of returnable converters.
1110
:param format: Optional format to override the default format of the
1113
raise NotImplementedError(self.get_converter)
1115
def initialize(self, url):
1116
"""Create a bzr control dir at this url and return an opened copy.
1118
Subclasses should typically override initialize_on_transport
1119
instead of this method.
1121
return self.initialize_on_transport(get_transport(url))
1123
def initialize_on_transport(self, transport):
1124
"""Initialize a new bzrdir in the base directory of a Transport."""
1125
# Since we don't have a .bzr directory, inherit the
1126
# mode from the root directory
1127
temp_control = lockable_files.LockableFiles(transport,
1128
'', lockable_files.TransportLock)
1129
temp_control._transport.mkdir('.bzr',
1130
# FIXME: RBC 20060121 don't peek under
1132
mode=temp_control._dir_mode)
1133
file_mode = temp_control._file_mode
1135
mutter('created control directory in ' + transport.base)
1136
control = transport.clone('.bzr')
1137
utf8_files = [('README',
1138
"This is a Bazaar-NG control directory.\n"
1139
"Do not change any files in this directory.\n"),
1140
('branch-format', self.get_format_string()),
1142
# NB: no need to escape relative paths that are url safe.
1143
control_files = lockable_files.LockableFiles(control,
1144
self._lock_file_name, self._lock_class)
1145
control_files.create_lock()
1146
control_files.lock_write()
1148
for file, content in utf8_files:
1149
control_files.put_utf8(file, content)
1151
control_files.unlock()
1152
return self.open(transport, _found=True)
1154
def is_supported(self):
1155
"""Is this format supported?
1157
Supported formats must be initializable and openable.
1158
Unsupported formats may not support initialization or committing or
1159
some other features depending on the reason for not being supported.
1163
def same_model(self, target_format):
1164
return (self.repository_format.rich_root_data ==
1165
target_format.rich_root_data)
1168
def known_formats(klass):
1169
"""Return all the known formats.
1171
Concrete formats should override _known_formats.
1173
# There is double indirection here to make sure that control
1174
# formats used by more than one dir format will only be probed
1175
# once. This can otherwise be quite expensive for remote connections.
1177
for format in klass._control_formats:
1178
result.update(format._known_formats())
1182
def _known_formats(klass):
1183
"""Return the known format instances for this control format."""
1184
return set(klass._formats.values())
1186
def open(self, transport, _found=False):
1187
"""Return an instance of this format for the dir transport points at.
1189
_found is a private parameter, do not use it.
1192
found_format = BzrDirFormat.find_format(transport)
1193
if not isinstance(found_format, self.__class__):
1194
raise AssertionError("%s was asked to open %s, but it seems to need "
1196
% (self, transport, found_format))
1197
return self._open(transport)
1199
def _open(self, transport):
1200
"""Template method helper for opening BzrDirectories.
1202
This performs the actual open and any additional logic or parameter
1205
raise NotImplementedError(self._open)
1208
def register_format(klass, format):
1209
klass._formats[format.get_format_string()] = format
1212
def register_control_format(klass, format):
1213
"""Register a format that does not use '.bzrdir' for its control dir.
1215
TODO: This should be pulled up into a 'ControlDirFormat' base class
1216
which BzrDirFormat can inherit from, and renamed to register_format
1217
there. It has been done without that for now for simplicity of
1220
klass._control_formats.append(format)
1223
def set_default_format(klass, format):
1224
klass._default_format = format
1227
return self.get_format_string()[:-1]
1230
def unregister_format(klass, format):
1231
assert klass._formats[format.get_format_string()] is format
1232
del klass._formats[format.get_format_string()]
1235
def unregister_control_format(klass, format):
1236
klass._control_formats.remove(format)
1239
# register BzrDirFormat as a control format
1240
BzrDirFormat.register_control_format(BzrDirFormat)
1243
class BzrDirFormat4(BzrDirFormat):
1244
"""Bzr dir format 4.
1246
This format is a combined format for working tree, branch and repository.
1248
- Format 1 working trees [always]
1249
- Format 4 branches [always]
1250
- Format 4 repositories [always]
1252
This format is deprecated: it indexes texts using a text it which is
1253
removed in format 5; write support for this format has been removed.
1256
_lock_class = lockable_files.TransportLock
1258
def get_format_string(self):
1259
"""See BzrDirFormat.get_format_string()."""
1260
return "Bazaar-NG branch, format 0.0.4\n"
1262
def get_format_description(self):
1263
"""See BzrDirFormat.get_format_description()."""
1264
return "All-in-one format 4"
1266
def get_converter(self, format=None):
1267
"""See BzrDirFormat.get_converter()."""
1268
# there is one and only one upgrade path here.
1269
return ConvertBzrDir4To5()
1271
def initialize_on_transport(self, transport):
1272
"""Format 4 branches cannot be created."""
1273
raise errors.UninitializableFormat(self)
1275
def is_supported(self):
1276
"""Format 4 is not supported.
1278
It is not supported because the model changed from 4 to 5 and the
1279
conversion logic is expensive - so doing it on the fly was not
1284
def _open(self, transport):
1285
"""See BzrDirFormat._open."""
1286
return BzrDir4(transport, self)
1288
def __return_repository_format(self):
1289
"""Circular import protection."""
1290
from bzrlib.repository import RepositoryFormat4
1291
return RepositoryFormat4()
1292
repository_format = property(__return_repository_format)
1295
class BzrDirFormat5(BzrDirFormat):
1296
"""Bzr control format 5.
1298
This format is a combined format for working tree, branch and repository.
1300
- Format 2 working trees [always]
1301
- Format 4 branches [always]
1302
- Format 5 repositories [always]
1303
Unhashed stores in the repository.
1306
_lock_class = lockable_files.TransportLock
1308
def get_format_string(self):
1309
"""See BzrDirFormat.get_format_string()."""
1310
return "Bazaar-NG branch, format 5\n"
1312
def get_format_description(self):
1313
"""See BzrDirFormat.get_format_description()."""
1314
return "All-in-one format 5"
1316
def get_converter(self, format=None):
1317
"""See BzrDirFormat.get_converter()."""
1318
# there is one and only one upgrade path here.
1319
return ConvertBzrDir5To6()
1321
def _initialize_for_clone(self, url):
1322
return self.initialize_on_transport(get_transport(url), _cloning=True)
1324
def initialize_on_transport(self, transport, _cloning=False):
1325
"""Format 5 dirs always have working tree, branch and repository.
1327
Except when they are being cloned.
1329
from bzrlib.branch import BzrBranchFormat4
1330
from bzrlib.repository import RepositoryFormat5
1331
from bzrlib.workingtree import WorkingTreeFormat2
1332
result = (super(BzrDirFormat5, self).initialize_on_transport(transport))
1333
RepositoryFormat5().initialize(result, _internal=True)
1335
branch = BzrBranchFormat4().initialize(result)
1337
WorkingTreeFormat2().initialize(result)
1338
except errors.NotLocalUrl:
1339
# Even though we can't access the working tree, we need to
1340
# create its control files.
1341
WorkingTreeFormat2().stub_initialize_remote(branch.control_files)
1344
def _open(self, transport):
1345
"""See BzrDirFormat._open."""
1346
return BzrDir5(transport, self)
1348
def __return_repository_format(self):
1349
"""Circular import protection."""
1350
from bzrlib.repository import RepositoryFormat5
1351
return RepositoryFormat5()
1352
repository_format = property(__return_repository_format)
1355
class BzrDirFormat6(BzrDirFormat):
1356
"""Bzr control format 6.
1358
This format is a combined format for working tree, branch and repository.
1360
- Format 2 working trees [always]
1361
- Format 4 branches [always]
1362
- Format 6 repositories [always]
1365
_lock_class = lockable_files.TransportLock
1367
def get_format_string(self):
1368
"""See BzrDirFormat.get_format_string()."""
1369
return "Bazaar-NG branch, format 6\n"
1371
def get_format_description(self):
1372
"""See BzrDirFormat.get_format_description()."""
1373
return "All-in-one format 6"
1375
def get_converter(self, format=None):
1376
"""See BzrDirFormat.get_converter()."""
1377
# there is one and only one upgrade path here.
1378
return ConvertBzrDir6ToMeta()
1380
def _initialize_for_clone(self, url):
1381
return self.initialize_on_transport(get_transport(url), _cloning=True)
1383
def initialize_on_transport(self, transport, _cloning=False):
1384
"""Format 6 dirs always have working tree, branch and repository.
1386
Except when they are being cloned.
1388
from bzrlib.branch import BzrBranchFormat4
1389
from bzrlib.repository import RepositoryFormat6
1390
from bzrlib.workingtree import WorkingTreeFormat2
1391
result = super(BzrDirFormat6, self).initialize_on_transport(transport)
1392
RepositoryFormat6().initialize(result, _internal=True)
1394
branch = BzrBranchFormat4().initialize(result)
1396
WorkingTreeFormat2().initialize(result)
1397
except errors.NotLocalUrl:
1398
# Even though we can't access the working tree, we need to
1399
# create its control files.
1400
WorkingTreeFormat2().stub_initialize_remote(branch.control_files)
1403
def _open(self, transport):
1404
"""See BzrDirFormat._open."""
1405
return BzrDir6(transport, self)
1407
def __return_repository_format(self):
1408
"""Circular import protection."""
1409
from bzrlib.repository import RepositoryFormat6
1410
return RepositoryFormat6()
1411
repository_format = property(__return_repository_format)
1414
class BzrDirMetaFormat1(BzrDirFormat):
1415
"""Bzr meta control format 1
1417
This is the first format with split out working tree, branch and repository
1420
- Format 3 working trees [optional]
1421
- Format 5 branches [optional]
1422
- Format 7 repositories [optional]
1425
_lock_class = lockdir.LockDir
1427
def get_converter(self, format=None):
1428
"""See BzrDirFormat.get_converter()."""
1430
format = BzrDirFormat.get_default_format()
1431
if not isinstance(self, format.__class__):
1432
# converting away from metadir is not implemented
1433
raise NotImplementedError(self.get_converter)
1434
return ConvertMetaToMeta(format)
1436
def get_format_string(self):
1437
"""See BzrDirFormat.get_format_string()."""
1438
return "Bazaar-NG meta directory, format 1\n"
1440
def get_format_description(self):
1441
"""See BzrDirFormat.get_format_description()."""
1442
return "Meta directory format 1"
1444
def _open(self, transport):
1445
"""See BzrDirFormat._open."""
1446
return BzrDirMeta1(transport, self)
1448
def __return_repository_format(self):
1449
"""Circular import protection."""
1450
if getattr(self, '_repository_format', None):
1451
return self._repository_format
1452
from bzrlib.repository import RepositoryFormat
1453
return RepositoryFormat.get_default_format()
1455
def __set_repository_format(self, value):
1456
"""Allow changint the repository format for metadir formats."""
1457
self._repository_format = value
1459
repository_format = property(__return_repository_format, __set_repository_format)
1462
BzrDirFormat.register_format(BzrDirFormat4())
1463
BzrDirFormat.register_format(BzrDirFormat5())
1464
BzrDirFormat.register_format(BzrDirFormat6())
1465
__default_format = BzrDirMetaFormat1()
1466
BzrDirFormat.register_format(__default_format)
1467
BzrDirFormat.set_default_format(__default_format)
1470
class BzrDirTestProviderAdapter(object):
1471
"""A tool to generate a suite testing multiple bzrdir formats at once.
1473
This is done by copying the test once for each transport and injecting
1474
the transport_server, transport_readonly_server, and bzrdir_format
1475
classes into each copy. Each copy is also given a new id() to make it
1479
def __init__(self, transport_server, transport_readonly_server, formats):
1480
self._transport_server = transport_server
1481
self._transport_readonly_server = transport_readonly_server
1482
self._formats = formats
1484
def adapt(self, test):
1485
result = unittest.TestSuite()
1486
for format in self._formats:
1487
new_test = deepcopy(test)
1488
new_test.transport_server = self._transport_server
1489
new_test.transport_readonly_server = self._transport_readonly_server
1490
new_test.bzrdir_format = format
1491
def make_new_test_id():
1492
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1493
return lambda: new_id
1494
new_test.id = make_new_test_id()
1495
result.addTest(new_test)
1499
class Converter(object):
1500
"""Converts a disk format object from one format to another."""
1502
def convert(self, to_convert, pb):
1503
"""Perform the conversion of to_convert, giving feedback via pb.
1505
:param to_convert: The disk object to convert.
1506
:param pb: a progress bar to use for progress information.
1509
def step(self, message):
1510
"""Update the pb by a step."""
1512
self.pb.update(message, self.count, self.total)
1515
class ConvertBzrDir4To5(Converter):
1516
"""Converts format 4 bzr dirs to format 5."""
1519
super(ConvertBzrDir4To5, self).__init__()
1520
self.converted_revs = set()
1521
self.absent_revisions = set()
1525
def convert(self, to_convert, pb):
1526
"""See Converter.convert()."""
1527
self.bzrdir = to_convert
1529
self.pb.note('starting upgrade from format 4 to 5')
1530
if isinstance(self.bzrdir.transport, LocalTransport):
1531
self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
1532
self._convert_to_weaves()
1533
return BzrDir.open(self.bzrdir.root_transport.base)
1535
def _convert_to_weaves(self):
1536
self.pb.note('note: upgrade may be faster if all store files are ungzipped first')
1539
stat = self.bzrdir.transport.stat('weaves')
1540
if not S_ISDIR(stat.st_mode):
1541
self.bzrdir.transport.delete('weaves')
1542
self.bzrdir.transport.mkdir('weaves')
1543
except errors.NoSuchFile:
1544
self.bzrdir.transport.mkdir('weaves')
1545
# deliberately not a WeaveFile as we want to build it up slowly.
1546
self.inv_weave = Weave('inventory')
1547
# holds in-memory weaves for all files
1548
self.text_weaves = {}
1549
self.bzrdir.transport.delete('branch-format')
1550
self.branch = self.bzrdir.open_branch()
1551
self._convert_working_inv()
1552
rev_history = self.branch.revision_history()
1553
# to_read is a stack holding the revisions we still need to process;
1554
# appending to it adds new highest-priority revisions
1555
self.known_revisions = set(rev_history)
1556
self.to_read = rev_history[-1:]
1558
rev_id = self.to_read.pop()
1559
if (rev_id not in self.revisions
1560
and rev_id not in self.absent_revisions):
1561
self._load_one_rev(rev_id)
1563
to_import = self._make_order()
1564
for i, rev_id in enumerate(to_import):
1565
self.pb.update('converting revision', i, len(to_import))
1566
self._convert_one_rev(rev_id)
1568
self._write_all_weaves()
1569
self._write_all_revs()
1570
self.pb.note('upgraded to weaves:')
1571
self.pb.note(' %6d revisions and inventories', len(self.revisions))
1572
self.pb.note(' %6d revisions not present', len(self.absent_revisions))
1573
self.pb.note(' %6d texts', self.text_count)
1574
self._cleanup_spare_files_after_format4()
1575
self.branch.control_files.put_utf8('branch-format', BzrDirFormat5().get_format_string())
1577
def _cleanup_spare_files_after_format4(self):
1578
# FIXME working tree upgrade foo.
1579
for n in 'merged-patches', 'pending-merged-patches':
1581
## assert os.path.getsize(p) == 0
1582
self.bzrdir.transport.delete(n)
1583
except errors.NoSuchFile:
1585
self.bzrdir.transport.delete_tree('inventory-store')
1586
self.bzrdir.transport.delete_tree('text-store')
1588
def _convert_working_inv(self):
1589
inv = xml4.serializer_v4.read_inventory(
1590
self.branch.control_files.get('inventory'))
1591
new_inv_xml = xml5.serializer_v5.write_inventory_to_string(inv)
1592
# FIXME inventory is a working tree change.
1593
self.branch.control_files.put('inventory', StringIO(new_inv_xml))
1595
def _write_all_weaves(self):
1596
controlweaves = WeaveStore(self.bzrdir.transport, prefixed=False)
1597
weave_transport = self.bzrdir.transport.clone('weaves')
1598
weaves = WeaveStore(weave_transport, prefixed=False)
1599
transaction = WriteTransaction()
1603
for file_id, file_weave in self.text_weaves.items():
1604
self.pb.update('writing weave', i, len(self.text_weaves))
1605
weaves._put_weave(file_id, file_weave, transaction)
1607
self.pb.update('inventory', 0, 1)
1608
controlweaves._put_weave('inventory', self.inv_weave, transaction)
1609
self.pb.update('inventory', 1, 1)
1613
def _write_all_revs(self):
1614
"""Write all revisions out in new form."""
1615
self.bzrdir.transport.delete_tree('revision-store')
1616
self.bzrdir.transport.mkdir('revision-store')
1617
revision_transport = self.bzrdir.transport.clone('revision-store')
1619
_revision_store = TextRevisionStore(TextStore(revision_transport,
1623
transaction = WriteTransaction()
1624
for i, rev_id in enumerate(self.converted_revs):
1625
self.pb.update('write revision', i, len(self.converted_revs))
1626
_revision_store.add_revision(self.revisions[rev_id], transaction)
1630
def _load_one_rev(self, rev_id):
1631
"""Load a revision object into memory.
1633
Any parents not either loaded or abandoned get queued to be
1635
self.pb.update('loading revision',
1636
len(self.revisions),
1637
len(self.known_revisions))
1638
if not self.branch.repository.has_revision(rev_id):
1640
self.pb.note('revision {%s} not present in branch; '
1641
'will be converted as a ghost',
1643
self.absent_revisions.add(rev_id)
1645
rev = self.branch.repository._revision_store.get_revision(rev_id,
1646
self.branch.repository.get_transaction())
1647
for parent_id in rev.parent_ids:
1648
self.known_revisions.add(parent_id)
1649
self.to_read.append(parent_id)
1650
self.revisions[rev_id] = rev
1652
def _load_old_inventory(self, rev_id):
1653
assert rev_id not in self.converted_revs
1654
old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
1655
inv = xml4.serializer_v4.read_inventory_from_string(old_inv_xml)
1656
inv.revision_id = rev_id
1657
rev = self.revisions[rev_id]
1658
if rev.inventory_sha1:
1659
assert rev.inventory_sha1 == sha_string(old_inv_xml), \
1660
'inventory sha mismatch for {%s}' % rev_id
1663
def _load_updated_inventory(self, rev_id):
1664
assert rev_id in self.converted_revs
1665
inv_xml = self.inv_weave.get_text(rev_id)
1666
inv = xml5.serializer_v5.read_inventory_from_string(inv_xml)
1669
def _convert_one_rev(self, rev_id):
1670
"""Convert revision and all referenced objects to new format."""
1671
rev = self.revisions[rev_id]
1672
inv = self._load_old_inventory(rev_id)
1673
present_parents = [p for p in rev.parent_ids
1674
if p not in self.absent_revisions]
1675
self._convert_revision_contents(rev, inv, present_parents)
1676
self._store_new_weave(rev, inv, present_parents)
1677
self.converted_revs.add(rev_id)
1679
def _store_new_weave(self, rev, inv, present_parents):
1680
# the XML is now updated with text versions
1682
entries = inv.iter_entries()
1684
for path, ie in entries:
1685
assert getattr(ie, 'revision', None) is not None, \
1686
'no revision on {%s} in {%s}' % \
1687
(file_id, rev.revision_id)
1688
new_inv_xml = xml5.serializer_v5.write_inventory_to_string(inv)
1689
new_inv_sha1 = sha_string(new_inv_xml)
1690
self.inv_weave.add_lines(rev.revision_id,
1692
new_inv_xml.splitlines(True))
1693
rev.inventory_sha1 = new_inv_sha1
1695
def _convert_revision_contents(self, rev, inv, present_parents):
1696
"""Convert all the files within a revision.
1698
Also upgrade the inventory to refer to the text revision ids."""
1699
rev_id = rev.revision_id
1700
mutter('converting texts of revision {%s}',
1702
parent_invs = map(self._load_updated_inventory, present_parents)
1703
entries = inv.iter_entries()
1705
for path, ie in entries:
1706
self._convert_file_version(rev, ie, parent_invs)
1708
def _convert_file_version(self, rev, ie, parent_invs):
1709
"""Convert one version of one file.
1711
The file needs to be added into the weave if it is a merge
1712
of >=2 parents or if it's changed from its parent.
1714
file_id = ie.file_id
1715
rev_id = rev.revision_id
1716
w = self.text_weaves.get(file_id)
1719
self.text_weaves[file_id] = w
1720
text_changed = False
1721
previous_entries = ie.find_previous_heads(parent_invs,
1725
for old_revision in previous_entries:
1726
# if this fails, its a ghost ?
1727
assert old_revision in self.converted_revs, \
1728
"Revision {%s} not in converted_revs" % old_revision
1729
self.snapshot_ie(previous_entries, ie, w, rev_id)
1731
assert getattr(ie, 'revision', None) is not None
1733
def snapshot_ie(self, previous_revisions, ie, w, rev_id):
1734
# TODO: convert this logic, which is ~= snapshot to
1735
# a call to:. This needs the path figured out. rather than a work_tree
1736
# a v4 revision_tree can be given, or something that looks enough like
1737
# one to give the file content to the entry if it needs it.
1738
# and we need something that looks like a weave store for snapshot to
1740
#ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
1741
if len(previous_revisions) == 1:
1742
previous_ie = previous_revisions.values()[0]
1743
if ie._unchanged(previous_ie):
1744
ie.revision = previous_ie.revision
1747
text = self.branch.repository.text_store.get(ie.text_id)
1748
file_lines = text.readlines()
1749
assert sha_strings(file_lines) == ie.text_sha1
1750
assert sum(map(len, file_lines)) == ie.text_size
1751
w.add_lines(rev_id, previous_revisions, file_lines)
1752
self.text_count += 1
1754
w.add_lines(rev_id, previous_revisions, [])
1755
ie.revision = rev_id
1757
def _make_order(self):
1758
"""Return a suitable order for importing revisions.
1760
The order must be such that an revision is imported after all
1761
its (present) parents.
1763
todo = set(self.revisions.keys())
1764
done = self.absent_revisions.copy()
1767
# scan through looking for a revision whose parents
1769
for rev_id in sorted(list(todo)):
1770
rev = self.revisions[rev_id]
1771
parent_ids = set(rev.parent_ids)
1772
if parent_ids.issubset(done):
1773
# can take this one now
1774
order.append(rev_id)
1780
class ConvertBzrDir5To6(Converter):
1781
"""Converts format 5 bzr dirs to format 6."""
1783
def convert(self, to_convert, pb):
1784
"""See Converter.convert()."""
1785
self.bzrdir = to_convert
1787
self.pb.note('starting upgrade from format 5 to 6')
1788
self._convert_to_prefixed()
1789
return BzrDir.open(self.bzrdir.root_transport.base)
1791
def _convert_to_prefixed(self):
1792
from bzrlib.store import TransportStore
1793
self.bzrdir.transport.delete('branch-format')
1794
for store_name in ["weaves", "revision-store"]:
1795
self.pb.note("adding prefixes to %s" % store_name)
1796
store_transport = self.bzrdir.transport.clone(store_name)
1797
store = TransportStore(store_transport, prefixed=True)
1798
for urlfilename in store_transport.list_dir('.'):
1799
filename = urlutils.unescape(urlfilename)
1800
if (filename.endswith(".weave") or
1801
filename.endswith(".gz") or
1802
filename.endswith(".sig")):
1803
file_id = os.path.splitext(filename)[0]
1806
prefix_dir = store.hash_prefix(file_id)
1807
# FIXME keep track of the dirs made RBC 20060121
1809
store_transport.move(filename, prefix_dir + '/' + filename)
1810
except errors.NoSuchFile: # catches missing dirs strangely enough
1811
store_transport.mkdir(prefix_dir)
1812
store_transport.move(filename, prefix_dir + '/' + filename)
1813
self.bzrdir._control_files.put_utf8('branch-format', BzrDirFormat6().get_format_string())
1816
class ConvertBzrDir6ToMeta(Converter):
1817
"""Converts format 6 bzr dirs to metadirs."""
1819
def convert(self, to_convert, pb):
1820
"""See Converter.convert()."""
1821
self.bzrdir = to_convert
1824
self.total = 20 # the steps we know about
1825
self.garbage_inventories = []
1827
self.pb.note('starting upgrade from format 6 to metadir')
1828
self.bzrdir._control_files.put_utf8('branch-format', "Converting to format 6")
1829
# its faster to move specific files around than to open and use the apis...
1830
# first off, nuke ancestry.weave, it was never used.
1832
self.step('Removing ancestry.weave')
1833
self.bzrdir.transport.delete('ancestry.weave')
1834
except errors.NoSuchFile:
1836
# find out whats there
1837
self.step('Finding branch files')
1838
last_revision = self.bzrdir.open_branch().last_revision()
1839
bzrcontents = self.bzrdir.transport.list_dir('.')
1840
for name in bzrcontents:
1841
if name.startswith('basis-inventory.'):
1842
self.garbage_inventories.append(name)
1843
# create new directories for repository, working tree and branch
1844
self.dir_mode = self.bzrdir._control_files._dir_mode
1845
self.file_mode = self.bzrdir._control_files._file_mode
1846
repository_names = [('inventory.weave', True),
1847
('revision-store', True),
1849
self.step('Upgrading repository ')
1850
self.bzrdir.transport.mkdir('repository', mode=self.dir_mode)
1851
self.make_lock('repository')
1852
# we hard code the formats here because we are converting into
1853
# the meta format. The meta format upgrader can take this to a
1854
# future format within each component.
1855
self.put_format('repository', bzrlib.repository.RepositoryFormat7())
1856
for entry in repository_names:
1857
self.move_entry('repository', entry)
1859
self.step('Upgrading branch ')
1860
self.bzrdir.transport.mkdir('branch', mode=self.dir_mode)
1861
self.make_lock('branch')
1862
self.put_format('branch', bzrlib.branch.BzrBranchFormat5())
1863
branch_files = [('revision-history', True),
1864
('branch-name', True),
1866
for entry in branch_files:
1867
self.move_entry('branch', entry)
1869
checkout_files = [('pending-merges', True),
1870
('inventory', True),
1871
('stat-cache', False)]
1872
# If a mandatory checkout file is not present, the branch does not have
1873
# a functional checkout. Do not create a checkout in the converted
1875
for name, mandatory in checkout_files:
1876
if mandatory and name not in bzrcontents:
1877
has_checkout = False
1881
if not has_checkout:
1882
self.pb.note('No working tree.')
1883
# If some checkout files are there, we may as well get rid of them.
1884
for name, mandatory in checkout_files:
1885
if name in bzrcontents:
1886
self.bzrdir.transport.delete(name)
1888
from bzrlib.workingtree import WorkingTreeFormat3
1889
self.step('Upgrading working tree')
1890
self.bzrdir.transport.mkdir('checkout', mode=self.dir_mode)
1891
self.make_lock('checkout')
1893
'checkout', WorkingTreeFormat3())
1894
self.bzrdir.transport.delete_multi(
1895
self.garbage_inventories, self.pb)
1896
for entry in checkout_files:
1897
self.move_entry('checkout', entry)
1898
if last_revision is not None:
1899
self.bzrdir._control_files.put_utf8(
1900
'checkout/last-revision', last_revision)
1901
self.bzrdir._control_files.put_utf8(
1902
'branch-format', BzrDirMetaFormat1().get_format_string())
1903
return BzrDir.open(self.bzrdir.root_transport.base)
1905
def make_lock(self, name):
1906
"""Make a lock for the new control dir name."""
1907
self.step('Make %s lock' % name)
1908
ld = lockdir.LockDir(self.bzrdir.transport,
1910
file_modebits=self.file_mode,
1911
dir_modebits=self.dir_mode)
1914
def move_entry(self, new_dir, entry):
1915
"""Move then entry name into new_dir."""
1917
mandatory = entry[1]
1918
self.step('Moving %s' % name)
1920
self.bzrdir.transport.move(name, '%s/%s' % (new_dir, name))
1921
except errors.NoSuchFile:
1925
def put_format(self, dirname, format):
1926
self.bzrdir._control_files.put_utf8('%s/format' % dirname, format.get_format_string())
1929
class ConvertMetaToMeta(Converter):
1930
"""Converts the components of metadirs."""
1932
def __init__(self, target_format):
1933
"""Create a metadir to metadir converter.
1935
:param target_format: The final metadir format that is desired.
1937
self.target_format = target_format
1939
def convert(self, to_convert, pb):
1940
"""See Converter.convert()."""
1941
self.bzrdir = to_convert
1945
self.step('checking repository format')
1947
repo = self.bzrdir.open_repository()
1948
except errors.NoRepositoryPresent:
1951
if not isinstance(repo._format, self.target_format.repository_format.__class__):
1952
from bzrlib.repository import CopyConverter
1953
self.pb.note('starting repository conversion')
1954
converter = CopyConverter(self.target_format.repository_format)
1955
converter.convert(repo, pb)
1959
# This is not in remote.py because it's small, and needs to be registered.
1960
# Putting it in remote.py creates a circular import problem.
1961
# we can make it a lazy object if the control formats is turned into something
1963
class RemoteBzrDirFormat(BzrDirMetaFormat1):
1964
"""Format representing bzrdirs accessed via a smart server"""
1966
def get_format_description(self):
1967
return 'bzr remote bzrdir'
1970
def probe_transport(klass, transport):
1971
"""Return a RemoteBzrDirFormat object if it looks possible."""
1973
transport.get_smart_client()
1974
except (NotImplementedError, AttributeError,
1975
errors.TransportNotPossible):
1976
# no smart server, so not a branch for this format type.
1977
raise errors.NotBranchError(path=transport.base)
1981
def _open(self, transport):
1982
return remote.RemoteBzrDir(transport)
1984
def __eq__(self, other):
1985
if not isinstance(other, RemoteBzrDirFormat):
1987
return self.get_format_description() == other.get_format_description()
1990
# We can't use register_control_format because it adds it at a lower priority
1991
# than the existing branches, whereas this should take priority.
1992
BzrDirFormat._control_formats.insert(0, RemoteBzrDirFormat)