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: The Format probe_transport seems a bit redundant with just trying to
26
# open the bzrdir. -- mbp
28
# TODO: Can we move specific formats into separate modules to make this file
31
from cStringIO import StringIO
34
from bzrlib.lazy_import import lazy_import
35
lazy_import(globals(), """
36
from copy import deepcopy
37
from stat import S_ISDIR
45
revision as _mod_revision,
50
from bzrlib.osutils import (
55
from bzrlib.store.revision.text import TextRevisionStore
56
from bzrlib.store.text import TextStore
57
from bzrlib.store.versioned import WeaveStore
58
from bzrlib.transactions import WriteTransaction
59
from bzrlib.transport import get_transport
60
from bzrlib.weave import Weave
63
from bzrlib.trace import mutter
64
from bzrlib.transport.local import LocalTransport
68
"""A .bzr control diretory.
70
BzrDir instances let you create or open any of the things that can be
71
found within .bzr - checkouts, branches and repositories.
74
the transport which this bzr dir is rooted at (i.e. file:///.../.bzr/)
76
a transport connected to the directory this bzr was opened from.
80
"""Invoke break_lock on the first object in the bzrdir.
82
If there is a tree, the tree is opened and break_lock() called.
83
Otherwise, branch is tried, and finally repository.
85
# XXX: This seems more like a UI function than something that really
86
# belongs in this class.
88
thing_to_unlock = self.open_workingtree()
89
except (errors.NotLocalUrl, errors.NoWorkingTree):
91
thing_to_unlock = self.open_branch()
92
except errors.NotBranchError:
94
thing_to_unlock = self.open_repository()
95
except errors.NoRepositoryPresent:
97
thing_to_unlock.break_lock()
99
def can_convert_format(self):
100
"""Return true if this bzrdir is one whose format we can convert from."""
103
def check_conversion_target(self, target_format):
104
target_repo_format = target_format.repository_format
105
source_repo_format = self._format.repository_format
106
source_repo_format.check_conversion_target(target_repo_format)
109
def _check_supported(format, allow_unsupported):
110
"""Check whether format is a supported format.
112
If allow_unsupported is True, this is a no-op.
114
if not allow_unsupported and not format.is_supported():
115
# see open_downlevel to open legacy branches.
116
raise errors.UnsupportedFormatError(format=format)
118
def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
119
"""Clone this bzrdir and its contents to url verbatim.
121
If urls last component does not exist, it will be created.
123
if revision_id is not None, then the clone operation may tune
124
itself to download less data.
125
:param force_new_repo: Do not use a shared repository for the target
126
even if one is available.
129
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
130
result = self._format.initialize(url)
132
local_repo = self.find_repository()
133
except errors.NoRepositoryPresent:
136
# may need to copy content in
138
result_repo = local_repo.clone(
140
revision_id=revision_id,
142
result_repo.set_make_working_trees(local_repo.make_working_trees())
145
result_repo = result.find_repository()
146
# fetch content this dir needs.
148
# XXX FIXME RBC 20060214 need tests for this when the basis
150
result_repo.fetch(basis_repo, revision_id=revision_id)
151
result_repo.fetch(local_repo, revision_id=revision_id)
152
except errors.NoRepositoryPresent:
153
# needed to make one anyway.
154
result_repo = local_repo.clone(
156
revision_id=revision_id,
158
result_repo.set_make_working_trees(local_repo.make_working_trees())
159
# 1 if there is a branch present
160
# make sure its content is available in the target repository
163
self.open_branch().clone(result, revision_id=revision_id)
164
except errors.NotBranchError:
167
self.open_workingtree().clone(result, basis=basis_tree)
168
except (errors.NoWorkingTree, errors.NotLocalUrl):
172
def _get_basis_components(self, basis):
173
"""Retrieve the basis components that are available at basis."""
175
return None, None, None
177
basis_tree = basis.open_workingtree()
178
basis_branch = basis_tree.branch
179
basis_repo = basis_branch.repository
180
except (errors.NoWorkingTree, errors.NotLocalUrl):
183
basis_branch = basis.open_branch()
184
basis_repo = basis_branch.repository
185
except errors.NotBranchError:
188
basis_repo = basis.open_repository()
189
except errors.NoRepositoryPresent:
191
return basis_repo, basis_branch, basis_tree
193
# TODO: This should be given a Transport, and should chdir up; otherwise
194
# this will open a new connection.
195
def _make_tail(self, url):
196
head, tail = urlutils.split(url)
197
if tail and tail != '.':
198
t = get_transport(head)
201
except errors.FileExists:
204
# TODO: Should take a Transport
206
def create(cls, base):
207
"""Create a new BzrDir at the url 'base'.
209
This will call the current default formats initialize with base
210
as the only parameter.
212
If you need a specific format, consider creating an instance
213
of that and calling initialize().
215
if cls is not BzrDir:
216
raise AssertionError("BzrDir.create always creates the default format, "
217
"not one of %r" % cls)
218
head, tail = urlutils.split(base)
219
if tail and tail != '.':
220
t = get_transport(head)
223
except errors.FileExists:
225
return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
227
def create_branch(self):
228
"""Create a branch in this BzrDir.
230
The bzrdirs format will control what branch format is created.
231
For more control see BranchFormatXX.create(a_bzrdir).
233
raise NotImplementedError(self.create_branch)
236
def create_branch_and_repo(base, force_new_repo=False):
237
"""Create a new BzrDir, Branch and Repository at the url 'base'.
239
This will use the current default BzrDirFormat, and use whatever
240
repository format that that uses via bzrdir.create_branch and
241
create_repository. If a shared repository is available that is used
244
The created Branch object is returned.
246
:param base: The URL to create the branch at.
247
:param force_new_repo: If True a new repository is always created.
249
bzrdir = BzrDir.create(base)
250
bzrdir._find_or_create_repository(force_new_repo)
251
return bzrdir.create_branch()
253
def _find_or_create_repository(self, force_new_repo):
254
"""Create a new repository if needed, returning the repository."""
256
return self.create_repository()
258
return self.find_repository()
259
except errors.NoRepositoryPresent:
260
return self.create_repository()
263
def create_branch_convenience(base, force_new_repo=False,
264
force_new_tree=None, format=None):
265
"""Create a new BzrDir, Branch and Repository at the url 'base'.
267
This is a convenience function - it will use an existing repository
268
if possible, can be told explicitly whether to create a working tree or
271
This will use the current default BzrDirFormat, and use whatever
272
repository format that that uses via bzrdir.create_branch and
273
create_repository. If a shared repository is available that is used
274
preferentially. Whatever repository is used, its tree creation policy
277
The created Branch object is returned.
278
If a working tree cannot be made due to base not being a file:// url,
279
no error is raised unless force_new_tree is True, in which case no
280
data is created on disk and NotLocalUrl is raised.
282
:param base: The URL to create the branch at.
283
:param force_new_repo: If True a new repository is always created.
284
:param force_new_tree: If True or False force creation of a tree or
285
prevent such creation respectively.
286
:param format: Override for the for the bzrdir format to create
289
# check for non local urls
290
t = get_transport(safe_unicode(base))
291
if not isinstance(t, LocalTransport):
292
raise errors.NotLocalUrl(base)
294
bzrdir = BzrDir.create(base)
296
bzrdir = format.initialize(base)
297
repo = bzrdir._find_or_create_repository(force_new_repo)
298
result = bzrdir.create_branch()
299
if force_new_tree or (repo.make_working_trees() and
300
force_new_tree is None):
302
bzrdir.create_workingtree()
303
except errors.NotLocalUrl:
308
def create_repository(base, shared=False):
309
"""Create a new BzrDir and Repository at the url 'base'.
311
This will use the current default BzrDirFormat, and use whatever
312
repository format that that uses for bzrdirformat.create_repository.
314
:param shared: Create a shared repository rather than a standalone
316
The Repository object is returned.
318
This must be overridden as an instance method in child classes, where
319
it should take no parameters and construct whatever repository format
320
that child class desires.
322
bzrdir = BzrDir.create(base)
323
return bzrdir.create_repository(shared)
326
def create_standalone_workingtree(base):
327
"""Create a new BzrDir, WorkingTree, Branch and Repository at 'base'.
329
'base' must be a local path or a file:// url.
331
This will use the current default BzrDirFormat, and use whatever
332
repository format that that uses for bzrdirformat.create_workingtree,
333
create_branch and create_repository.
335
:return: The WorkingTree object.
337
t = get_transport(safe_unicode(base))
338
if not isinstance(t, LocalTransport):
339
raise errors.NotLocalUrl(base)
340
bzrdir = BzrDir.create_branch_and_repo(safe_unicode(base),
341
force_new_repo=True).bzrdir
342
return bzrdir.create_workingtree()
344
def create_workingtree(self, revision_id=None):
345
"""Create a working tree at this BzrDir.
347
revision_id: create it as of this revision id.
349
raise NotImplementedError(self.create_workingtree)
351
def destroy_workingtree(self):
352
"""Destroy the working tree at this BzrDir.
354
Formats that do not support this may raise UnsupportedOperation.
356
raise NotImplementedError(self.destroy_workingtree)
358
def destroy_workingtree_metadata(self):
359
"""Destroy the control files for the working tree at this BzrDir.
361
The contents of working tree files are not affected.
362
Formats that do not support this may raise UnsupportedOperation.
364
raise NotImplementedError(self.destroy_workingtree_metadata)
366
def find_repository(self):
367
"""Find the repository that should be used for a_bzrdir.
369
This does not require a branch as we use it to find the repo for
370
new branches as well as to hook existing branches up to their
374
return self.open_repository()
375
except errors.NoRepositoryPresent:
377
next_transport = self.root_transport.clone('..')
379
# find the next containing bzrdir
381
found_bzrdir = BzrDir.open_containing_from_transport(
383
except errors.NotBranchError:
385
raise errors.NoRepositoryPresent(self)
386
# does it have a repository ?
388
repository = found_bzrdir.open_repository()
389
except errors.NoRepositoryPresent:
390
next_transport = found_bzrdir.root_transport.clone('..')
391
if (found_bzrdir.root_transport.base == next_transport.base):
392
# top of the file system
396
if ((found_bzrdir.root_transport.base ==
397
self.root_transport.base) or repository.is_shared()):
400
raise errors.NoRepositoryPresent(self)
401
raise errors.NoRepositoryPresent(self)
403
def get_branch_transport(self, branch_format):
404
"""Get the transport for use by branch format in this BzrDir.
406
Note that bzr dirs that do not support format strings will raise
407
IncompatibleFormat if the branch format they are given has
408
a format string, and vice versa.
410
If branch_format is None, the transport is returned with no
411
checking. if it is not None, then the returned transport is
412
guaranteed to point to an existing directory ready for use.
414
raise NotImplementedError(self.get_branch_transport)
416
def get_repository_transport(self, repository_format):
417
"""Get the transport for use by repository format in this BzrDir.
419
Note that bzr dirs that do not support format strings will raise
420
IncompatibleFormat if the repository format they are given has
421
a format string, and vice versa.
423
If repository_format is None, the transport is returned with no
424
checking. if it is not None, then the returned transport is
425
guaranteed to point to an existing directory ready for use.
427
raise NotImplementedError(self.get_repository_transport)
429
def get_workingtree_transport(self, tree_format):
430
"""Get the transport for use by workingtree format in this BzrDir.
432
Note that bzr dirs that do not support format strings will raise
433
IncompatibleFormat if the workingtree format they are given has
434
a format string, and vice versa.
436
If workingtree_format is None, the transport is returned with no
437
checking. if it is not None, then the returned transport is
438
guaranteed to point to an existing directory ready for use.
440
raise NotImplementedError(self.get_workingtree_transport)
442
def __init__(self, _transport, _format):
443
"""Initialize a Bzr control dir object.
445
Only really common logic should reside here, concrete classes should be
446
made with varying behaviours.
448
:param _format: the format that is creating this BzrDir instance.
449
:param _transport: the transport this dir is based at.
451
self._format = _format
452
self.transport = _transport.clone('.bzr')
453
self.root_transport = _transport
455
def is_control_filename(self, filename):
456
"""True if filename is the name of a path which is reserved for bzrdir's.
458
:param filename: A filename within the root transport of this bzrdir.
460
This is true IF and ONLY IF the filename is part of the namespace reserved
461
for bzr control dirs. Currently this is the '.bzr' directory in the root
462
of the root_transport. it is expected that plugins will need to extend
463
this in the future - for instance to make bzr talk with svn working
466
# this might be better on the BzrDirFormat class because it refers to
467
# all the possible bzrdir disk formats.
468
# This method is tested via the workingtree is_control_filename tests-
469
# it was extracted from WorkingTree.is_control_filename. If the methods
470
# contract is extended beyond the current trivial implementation please
471
# add new tests for it to the appropriate place.
472
return filename == '.bzr' or filename.startswith('.bzr/')
474
def needs_format_conversion(self, format=None):
475
"""Return true if this bzrdir needs convert_format run on it.
477
For instance, if the repository format is out of date but the
478
branch and working tree are not, this should return True.
480
:param format: Optional parameter indicating a specific desired
481
format we plan to arrive at.
483
raise NotImplementedError(self.needs_format_conversion)
486
def open_unsupported(base):
487
"""Open a branch which is not supported."""
488
return BzrDir.open(base, _unsupported=True)
491
def open(base, _unsupported=False):
492
"""Open an existing bzrdir, rooted at 'base' (url)
494
_unsupported is a private parameter to the BzrDir class.
496
t = get_transport(base)
497
return BzrDir.open_from_transport(t, _unsupported=_unsupported)
500
def open_from_transport(transport, _unsupported=False):
501
"""Open a bzrdir within a particular directory.
503
:param transport: Transport containing the bzrdir.
504
:param _unsupported: private.
506
format = BzrDirFormat.find_format(transport)
507
BzrDir._check_supported(format, _unsupported)
508
return format.open(transport, _found=True)
510
def open_branch(self, unsupported=False):
511
"""Open the branch object at this BzrDir if one is present.
513
If unsupported is True, then no longer supported branch formats can
516
TODO: static convenience version of this?
518
raise NotImplementedError(self.open_branch)
521
def open_containing(url):
522
"""Open an existing branch which contains url.
524
:param url: url to search from.
525
See open_containing_from_transport for more detail.
527
return BzrDir.open_containing_from_transport(get_transport(url))
530
def open_containing_from_transport(a_transport):
531
"""Open an existing branch which contains a_transport.base
533
This probes for a branch at a_transport, and searches upwards from there.
535
Basically we keep looking up until we find the control directory or
536
run into the root. If there isn't one, raises NotBranchError.
537
If there is one and it is either an unrecognised format or an unsupported
538
format, UnknownFormatError or UnsupportedFormatError are raised.
539
If there is one, it is returned, along with the unused portion of url.
541
:return: The BzrDir that contains the path, and a Unicode path
542
for the rest of the URL.
544
# this gets the normalised url back. I.e. '.' -> the full path.
545
url = a_transport.base
548
result = BzrDir.open_from_transport(a_transport)
549
return result, urlutils.unescape(a_transport.relpath(url))
550
except errors.NotBranchError, e:
552
new_t = a_transport.clone('..')
553
if new_t.base == a_transport.base:
554
# reached the root, whatever that may be
555
raise errors.NotBranchError(path=url)
558
def open_repository(self, _unsupported=False):
559
"""Open the repository object at this BzrDir if one is present.
561
This will not follow the Branch object pointer - its strictly a direct
562
open facility. Most client code should use open_branch().repository to
565
_unsupported is a private parameter, not part of the api.
566
TODO: static convenience version of this?
568
raise NotImplementedError(self.open_repository)
570
def open_workingtree(self, _unsupported=False):
571
"""Open the workingtree object at this BzrDir if one is present.
573
TODO: static convenience version of this?
575
raise NotImplementedError(self.open_workingtree)
577
def has_branch(self):
578
"""Tell if this bzrdir contains a branch.
580
Note: if you're going to open the branch, you should just go ahead
581
and try, and not ask permission first. (This method just opens the
582
branch and discards it, and that's somewhat expensive.)
587
except errors.NotBranchError:
590
def has_workingtree(self):
591
"""Tell if this bzrdir contains a working tree.
593
This will still raise an exception if the bzrdir has a workingtree that
594
is remote & inaccessible.
596
Note: if you're going to open the working tree, you should just go ahead
597
and try, and not ask permission first. (This method just opens the
598
workingtree and discards it, and that's somewhat expensive.)
601
self.open_workingtree()
603
except errors.NoWorkingTree:
606
def cloning_metadir(self, basis=None):
607
"""Produce a metadir suitable for cloning with"""
608
def related_repository(bzrdir):
610
branch = bzrdir.open_branch()
611
return branch.repository
612
except errors.NotBranchError:
614
return bzrdir.open_repository()
615
result_format = self._format.__class__()
618
source_repository = related_repository(self)
619
except errors.NoRepositoryPresent:
622
source_repository = related_repository(self)
623
result_format.repository_format = source_repository._format
624
except errors.NoRepositoryPresent:
628
def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
629
"""Create a copy of this bzrdir prepared for use as a new line of
632
If urls last component does not exist, it will be created.
634
Attributes related to the identity of the source branch like
635
branch nickname will be cleaned, a working tree is created
636
whether one existed before or not; and a local branch is always
639
if revision_id is not None, then the clone operation may tune
640
itself to download less data.
643
cloning_format = self.cloning_metadir(basis)
644
result = cloning_format.initialize(url)
645
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
647
source_branch = self.open_branch()
648
source_repository = source_branch.repository
649
except errors.NotBranchError:
652
source_repository = self.open_repository()
653
except errors.NoRepositoryPresent:
654
# copy the entire basis one if there is one
655
# but there is no repository.
656
source_repository = basis_repo
661
result_repo = result.find_repository()
662
except errors.NoRepositoryPresent:
664
if source_repository is None and result_repo is not None:
666
elif source_repository is None and result_repo is None:
667
# no repo available, make a new one
668
result.create_repository()
669
elif source_repository is not None and result_repo is None:
670
# have source, and want to make a new target repo
671
# we don't clone the repo because that preserves attributes
672
# like is_shared(), and we have not yet implemented a
673
# repository sprout().
674
result_repo = result.create_repository()
675
if result_repo is not None:
676
# fetch needed content into target.
678
# XXX FIXME RBC 20060214 need tests for this when the basis
680
result_repo.fetch(basis_repo, revision_id=revision_id)
681
if source_repository is not None:
682
result_repo.fetch(source_repository, revision_id=revision_id)
683
if source_branch is not None:
684
source_branch.sprout(result, revision_id=revision_id)
686
result.create_branch()
687
# TODO: jam 20060426 we probably need a test in here in the
688
# case that the newly sprouted branch is a remote one
689
if result_repo is None or result_repo.make_working_trees():
690
wt = result.create_workingtree()
691
if wt.inventory.root is None:
693
wt.set_root_id(self.open_workingtree.get_root_id())
694
except errors.NoWorkingTree:
699
class BzrDirPreSplitOut(BzrDir):
700
"""A common class for the all-in-one formats."""
702
def __init__(self, _transport, _format):
703
"""See BzrDir.__init__."""
704
super(BzrDirPreSplitOut, self).__init__(_transport, _format)
705
assert self._format._lock_class == lockable_files.TransportLock
706
assert self._format._lock_file_name == 'branch-lock'
707
self._control_files = lockable_files.LockableFiles(
708
self.get_branch_transport(None),
709
self._format._lock_file_name,
710
self._format._lock_class)
712
def break_lock(self):
713
"""Pre-splitout bzrdirs do not suffer from stale locks."""
714
raise NotImplementedError(self.break_lock)
716
def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
717
"""See BzrDir.clone()."""
718
from bzrlib.workingtree import WorkingTreeFormat2
720
result = self._format._initialize_for_clone(url)
721
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
722
self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
723
from_branch = self.open_branch()
724
from_branch.clone(result, revision_id=revision_id)
726
self.open_workingtree().clone(result, basis=basis_tree)
727
except errors.NotLocalUrl:
728
# make a new one, this format always has to have one.
730
WorkingTreeFormat2().initialize(result)
731
except errors.NotLocalUrl:
732
# but we cannot do it for remote trees.
733
to_branch = result.open_branch()
734
WorkingTreeFormat2().stub_initialize_remote(to_branch.control_files)
737
def create_branch(self):
738
"""See BzrDir.create_branch."""
739
return self.open_branch()
741
def create_repository(self, shared=False):
742
"""See BzrDir.create_repository."""
744
raise errors.IncompatibleFormat('shared repository', self._format)
745
return self.open_repository()
747
def create_workingtree(self, revision_id=None):
748
"""See BzrDir.create_workingtree."""
749
# this looks buggy but is not -really-
750
# clone and sprout will have set the revision_id
751
# and that will have set it for us, its only
752
# specific uses of create_workingtree in isolation
753
# that can do wonky stuff here, and that only
754
# happens for creating checkouts, which cannot be
755
# done on this format anyway. So - acceptable wart.
756
result = self.open_workingtree()
757
if revision_id is not None:
758
if revision_id == _mod_revision.NULL_REVISION:
759
result.set_parent_ids([])
761
result.set_parent_ids([revision_id])
764
def destroy_workingtree(self):
765
"""See BzrDir.destroy_workingtree."""
766
raise errors.UnsupportedOperation(self.destroy_workingtree, self)
768
def destroy_workingtree_metadata(self):
769
"""See BzrDir.destroy_workingtree_metadata."""
770
raise errors.UnsupportedOperation(self.destroy_workingtree_metadata,
773
def get_branch_transport(self, branch_format):
774
"""See BzrDir.get_branch_transport()."""
775
if branch_format is None:
776
return self.transport
778
branch_format.get_format_string()
779
except NotImplementedError:
780
return self.transport
781
raise errors.IncompatibleFormat(branch_format, self._format)
783
def get_repository_transport(self, repository_format):
784
"""See BzrDir.get_repository_transport()."""
785
if repository_format is None:
786
return self.transport
788
repository_format.get_format_string()
789
except NotImplementedError:
790
return self.transport
791
raise errors.IncompatibleFormat(repository_format, self._format)
793
def get_workingtree_transport(self, workingtree_format):
794
"""See BzrDir.get_workingtree_transport()."""
795
if workingtree_format is None:
796
return self.transport
798
workingtree_format.get_format_string()
799
except NotImplementedError:
800
return self.transport
801
raise errors.IncompatibleFormat(workingtree_format, self._format)
803
def needs_format_conversion(self, format=None):
804
"""See BzrDir.needs_format_conversion()."""
805
# if the format is not the same as the system default,
806
# an upgrade is needed.
808
format = BzrDirFormat.get_default_format()
809
return not isinstance(self._format, format.__class__)
811
def open_branch(self, unsupported=False):
812
"""See BzrDir.open_branch."""
813
from bzrlib.branch import BzrBranchFormat4
814
format = BzrBranchFormat4()
815
self._check_supported(format, unsupported)
816
return format.open(self, _found=True)
818
def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
819
"""See BzrDir.sprout()."""
820
from bzrlib.workingtree import WorkingTreeFormat2
822
result = self._format._initialize_for_clone(url)
823
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
825
self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
826
except errors.NoRepositoryPresent:
829
self.open_branch().sprout(result, revision_id=revision_id)
830
except errors.NotBranchError:
832
# we always want a working tree
833
WorkingTreeFormat2().initialize(result)
837
class BzrDir4(BzrDirPreSplitOut):
838
"""A .bzr version 4 control object.
840
This is a deprecated format and may be removed after sept 2006.
843
def create_repository(self, shared=False):
844
"""See BzrDir.create_repository."""
845
return self._format.repository_format.initialize(self, shared)
847
def needs_format_conversion(self, format=None):
848
"""Format 4 dirs are always in need of conversion."""
851
def open_repository(self):
852
"""See BzrDir.open_repository."""
853
from bzrlib.repository import RepositoryFormat4
854
return RepositoryFormat4().open(self, _found=True)
857
class BzrDir5(BzrDirPreSplitOut):
858
"""A .bzr version 5 control object.
860
This is a deprecated format and may be removed after sept 2006.
863
def open_repository(self):
864
"""See BzrDir.open_repository."""
865
from bzrlib.repository import RepositoryFormat5
866
return RepositoryFormat5().open(self, _found=True)
868
def open_workingtree(self, _unsupported=False):
869
"""See BzrDir.create_workingtree."""
870
from bzrlib.workingtree import WorkingTreeFormat2
871
return WorkingTreeFormat2().open(self, _found=True)
874
class BzrDir6(BzrDirPreSplitOut):
875
"""A .bzr version 6 control object.
877
This is a deprecated format and may be removed after sept 2006.
880
def open_repository(self):
881
"""See BzrDir.open_repository."""
882
from bzrlib.repository import RepositoryFormat6
883
return RepositoryFormat6().open(self, _found=True)
885
def open_workingtree(self, _unsupported=False):
886
"""See BzrDir.create_workingtree."""
887
from bzrlib.workingtree import WorkingTreeFormat2
888
return WorkingTreeFormat2().open(self, _found=True)
891
class BzrDirMeta1(BzrDir):
892
"""A .bzr meta version 1 control object.
894
This is the first control object where the
895
individual aspects are really split out: there are separate repository,
896
workingtree and branch subdirectories and any subset of the three can be
897
present within a BzrDir.
900
def can_convert_format(self):
901
"""See BzrDir.can_convert_format()."""
904
def create_branch(self):
905
"""See BzrDir.create_branch."""
906
from bzrlib.branch import BranchFormat
907
return BranchFormat.get_default_format().initialize(self)
909
def create_repository(self, shared=False):
910
"""See BzrDir.create_repository."""
911
return self._format.repository_format.initialize(self, shared)
913
def create_workingtree(self, revision_id=None):
914
"""See BzrDir.create_workingtree."""
915
from bzrlib.workingtree import WorkingTreeFormat
916
return WorkingTreeFormat.get_default_format().initialize(self, revision_id)
918
def destroy_workingtree(self):
919
"""See BzrDir.destroy_workingtree."""
920
wt = self.open_workingtree()
921
repository = wt.branch.repository
922
empty = repository.revision_tree(bzrlib.revision.NULL_REVISION)
923
wt.revert([], old_tree=empty)
924
self.destroy_workingtree_metadata()
926
def destroy_workingtree_metadata(self):
927
self.transport.delete_tree('checkout')
929
def _get_mkdir_mode(self):
930
"""Figure out the mode to use when creating a bzrdir subdir."""
931
temp_control = lockable_files.LockableFiles(self.transport, '',
932
lockable_files.TransportLock)
933
return temp_control._dir_mode
935
def get_branch_transport(self, branch_format):
936
"""See BzrDir.get_branch_transport()."""
937
if branch_format is None:
938
return self.transport.clone('branch')
940
branch_format.get_format_string()
941
except NotImplementedError:
942
raise errors.IncompatibleFormat(branch_format, self._format)
944
self.transport.mkdir('branch', mode=self._get_mkdir_mode())
945
except errors.FileExists:
947
return self.transport.clone('branch')
949
def get_repository_transport(self, repository_format):
950
"""See BzrDir.get_repository_transport()."""
951
if repository_format is None:
952
return self.transport.clone('repository')
954
repository_format.get_format_string()
955
except NotImplementedError:
956
raise errors.IncompatibleFormat(repository_format, self._format)
958
self.transport.mkdir('repository', mode=self._get_mkdir_mode())
959
except errors.FileExists:
961
return self.transport.clone('repository')
963
def get_workingtree_transport(self, workingtree_format):
964
"""See BzrDir.get_workingtree_transport()."""
965
if workingtree_format is None:
966
return self.transport.clone('checkout')
968
workingtree_format.get_format_string()
969
except NotImplementedError:
970
raise errors.IncompatibleFormat(workingtree_format, self._format)
972
self.transport.mkdir('checkout', mode=self._get_mkdir_mode())
973
except errors.FileExists:
975
return self.transport.clone('checkout')
977
def needs_format_conversion(self, format=None):
978
"""See BzrDir.needs_format_conversion()."""
980
format = BzrDirFormat.get_default_format()
981
if not isinstance(self._format, format.__class__):
982
# it is not a meta dir format, conversion is needed.
984
# we might want to push this down to the repository?
986
if not isinstance(self.open_repository()._format,
987
format.repository_format.__class__):
988
# the repository needs an upgrade.
990
except errors.NoRepositoryPresent:
992
# currently there are no other possible conversions for meta1 formats.
995
def open_branch(self, unsupported=False):
996
"""See BzrDir.open_branch."""
997
from bzrlib.branch import BranchFormat
998
format = BranchFormat.find_format(self)
999
self._check_supported(format, unsupported)
1000
return format.open(self, _found=True)
1002
def open_repository(self, unsupported=False):
1003
"""See BzrDir.open_repository."""
1004
from bzrlib.repository import RepositoryFormat
1005
format = RepositoryFormat.find_format(self)
1006
self._check_supported(format, unsupported)
1007
return format.open(self, _found=True)
1009
def open_workingtree(self, unsupported=False):
1010
"""See BzrDir.open_workingtree."""
1011
from bzrlib.workingtree import WorkingTreeFormat
1012
format = WorkingTreeFormat.find_format(self)
1013
self._check_supported(format, unsupported)
1014
return format.open(self, _found=True)
1017
class BzrDirFormat(object):
1018
"""An encapsulation of the initialization and open routines for a format.
1020
Formats provide three things:
1021
* An initialization routine,
1025
Formats are placed in an dict by their format string for reference
1026
during bzrdir opening. These should be subclasses of BzrDirFormat
1029
Once a format is deprecated, just deprecate the initialize and open
1030
methods on the format class. Do not deprecate the object, as the
1031
object will be created every system load.
1034
_default_format = None
1035
"""The default format used for new .bzr dirs."""
1038
"""The known formats."""
1040
_control_formats = []
1041
"""The registered control formats - .bzr, ....
1043
This is a list of BzrDirFormat objects.
1046
_lock_file_name = 'branch-lock'
1048
# _lock_class must be set in subclasses to the lock type, typ.
1049
# TransportLock or LockDir
1052
def find_format(klass, transport):
1053
"""Return the format present at transport."""
1054
for format in klass._control_formats:
1056
return format.probe_transport(transport)
1057
except errors.NotBranchError:
1058
# this format does not find a control dir here.
1060
raise errors.NotBranchError(path=transport.base)
1063
def probe_transport(klass, transport):
1064
"""Return the .bzrdir style format present in a directory."""
1066
format_string = transport.get(".bzr/branch-format").read()
1067
except errors.NoSuchFile:
1068
raise errors.NotBranchError(path=transport.base)
1071
return klass._formats[format_string]
1073
raise errors.UnknownFormatError(format=format_string)
1076
def get_default_format(klass):
1077
"""Return the current default format."""
1078
return klass._default_format
1080
def get_format_string(self):
1081
"""Return the ASCII format string that identifies this format."""
1082
raise NotImplementedError(self.get_format_string)
1084
def get_format_description(self):
1085
"""Return the short description for this format."""
1086
raise NotImplementedError(self.get_format_description)
1088
def get_converter(self, format=None):
1089
"""Return the converter to use to convert bzrdirs needing converts.
1091
This returns a bzrlib.bzrdir.Converter object.
1093
This should return the best upgrader to step this format towards the
1094
current default format. In the case of plugins we can/should provide
1095
some means for them to extend the range of returnable converters.
1097
:param format: Optional format to override the default format of the
1100
raise NotImplementedError(self.get_converter)
1102
def initialize(self, url):
1103
"""Create a bzr control dir at this url and return an opened copy.
1105
Subclasses should typically override initialize_on_transport
1106
instead of this method.
1108
return self.initialize_on_transport(get_transport(url))
1110
def initialize_on_transport(self, transport):
1111
"""Initialize a new bzrdir in the base directory of a Transport."""
1112
# Since we don't have a .bzr directory, inherit the
1113
# mode from the root directory
1114
temp_control = lockable_files.LockableFiles(transport,
1115
'', lockable_files.TransportLock)
1116
temp_control._transport.mkdir('.bzr',
1117
# FIXME: RBC 20060121 don't peek under
1119
mode=temp_control._dir_mode)
1120
file_mode = temp_control._file_mode
1122
mutter('created control directory in ' + transport.base)
1123
control = transport.clone('.bzr')
1124
utf8_files = [('README',
1125
"This is a Bazaar-NG control directory.\n"
1126
"Do not change any files in this directory.\n"),
1127
('branch-format', self.get_format_string()),
1129
# NB: no need to escape relative paths that are url safe.
1130
control_files = lockable_files.LockableFiles(control,
1131
self._lock_file_name, self._lock_class)
1132
control_files.create_lock()
1133
control_files.lock_write()
1135
for file, content in utf8_files:
1136
control_files.put_utf8(file, content)
1138
control_files.unlock()
1139
return self.open(transport, _found=True)
1141
def is_supported(self):
1142
"""Is this format supported?
1144
Supported formats must be initializable and openable.
1145
Unsupported formats may not support initialization or committing or
1146
some other features depending on the reason for not being supported.
1150
def same_model(self, target_format):
1151
return (self.repository_format.rich_root_data ==
1152
target_format.rich_root_data)
1155
def known_formats(klass):
1156
"""Return all the known formats.
1158
Concrete formats should override _known_formats.
1160
# There is double indirection here to make sure that control
1161
# formats used by more than one dir format will only be probed
1162
# once. This can otherwise be quite expensive for remote connections.
1164
for format in klass._control_formats:
1165
result.update(format._known_formats())
1169
def _known_formats(klass):
1170
"""Return the known format instances for this control format."""
1171
return set(klass._formats.values())
1173
def open(self, transport, _found=False):
1174
"""Return an instance of this format for the dir transport points at.
1176
_found is a private parameter, do not use it.
1179
assert isinstance(BzrDirFormat.find_format(transport),
1181
return self._open(transport)
1183
def _open(self, transport):
1184
"""Template method helper for opening BzrDirectories.
1186
This performs the actual open and any additional logic or parameter
1189
raise NotImplementedError(self._open)
1192
def register_format(klass, format):
1193
klass._formats[format.get_format_string()] = format
1196
def register_control_format(klass, format):
1197
"""Register a format that does not use '.bzrdir' for its control dir.
1199
TODO: This should be pulled up into a 'ControlDirFormat' base class
1200
which BzrDirFormat can inherit from, and renamed to register_format
1201
there. It has been done without that for now for simplicity of
1204
klass._control_formats.append(format)
1207
def set_default_format(klass, format):
1208
klass._default_format = format
1211
return self.get_format_string()[:-1]
1214
def unregister_format(klass, format):
1215
assert klass._formats[format.get_format_string()] is format
1216
del klass._formats[format.get_format_string()]
1219
def unregister_control_format(klass, format):
1220
klass._control_formats.remove(format)
1223
# register BzrDirFormat as a control format
1224
BzrDirFormat.register_control_format(BzrDirFormat)
1227
class BzrDirFormat4(BzrDirFormat):
1228
"""Bzr dir format 4.
1230
This format is a combined format for working tree, branch and repository.
1232
- Format 1 working trees [always]
1233
- Format 4 branches [always]
1234
- Format 4 repositories [always]
1236
This format is deprecated: it indexes texts using a text it which is
1237
removed in format 5; write support for this format has been removed.
1240
_lock_class = lockable_files.TransportLock
1242
def get_format_string(self):
1243
"""See BzrDirFormat.get_format_string()."""
1244
return "Bazaar-NG branch, format 0.0.4\n"
1246
def get_format_description(self):
1247
"""See BzrDirFormat.get_format_description()."""
1248
return "All-in-one format 4"
1250
def get_converter(self, format=None):
1251
"""See BzrDirFormat.get_converter()."""
1252
# there is one and only one upgrade path here.
1253
return ConvertBzrDir4To5()
1255
def initialize_on_transport(self, transport):
1256
"""Format 4 branches cannot be created."""
1257
raise errors.UninitializableFormat(self)
1259
def is_supported(self):
1260
"""Format 4 is not supported.
1262
It is not supported because the model changed from 4 to 5 and the
1263
conversion logic is expensive - so doing it on the fly was not
1268
def _open(self, transport):
1269
"""See BzrDirFormat._open."""
1270
return BzrDir4(transport, self)
1272
def __return_repository_format(self):
1273
"""Circular import protection."""
1274
from bzrlib.repository import RepositoryFormat4
1275
return RepositoryFormat4()
1276
repository_format = property(__return_repository_format)
1279
class BzrDirFormat5(BzrDirFormat):
1280
"""Bzr control format 5.
1282
This format is a combined format for working tree, branch and repository.
1284
- Format 2 working trees [always]
1285
- Format 4 branches [always]
1286
- Format 5 repositories [always]
1287
Unhashed stores in the repository.
1290
_lock_class = lockable_files.TransportLock
1292
def get_format_string(self):
1293
"""See BzrDirFormat.get_format_string()."""
1294
return "Bazaar-NG branch, format 5\n"
1296
def get_format_description(self):
1297
"""See BzrDirFormat.get_format_description()."""
1298
return "All-in-one format 5"
1300
def get_converter(self, format=None):
1301
"""See BzrDirFormat.get_converter()."""
1302
# there is one and only one upgrade path here.
1303
return ConvertBzrDir5To6()
1305
def _initialize_for_clone(self, url):
1306
return self.initialize_on_transport(get_transport(url), _cloning=True)
1308
def initialize_on_transport(self, transport, _cloning=False):
1309
"""Format 5 dirs always have working tree, branch and repository.
1311
Except when they are being cloned.
1313
from bzrlib.branch import BzrBranchFormat4
1314
from bzrlib.repository import RepositoryFormat5
1315
from bzrlib.workingtree import WorkingTreeFormat2
1316
result = (super(BzrDirFormat5, self).initialize_on_transport(transport))
1317
RepositoryFormat5().initialize(result, _internal=True)
1319
branch = BzrBranchFormat4().initialize(result)
1321
WorkingTreeFormat2().initialize(result)
1322
except errors.NotLocalUrl:
1323
# Even though we can't access the working tree, we need to
1324
# create its control files.
1325
WorkingTreeFormat2().stub_initialize_remote(branch.control_files)
1328
def _open(self, transport):
1329
"""See BzrDirFormat._open."""
1330
return BzrDir5(transport, self)
1332
def __return_repository_format(self):
1333
"""Circular import protection."""
1334
from bzrlib.repository import RepositoryFormat5
1335
return RepositoryFormat5()
1336
repository_format = property(__return_repository_format)
1339
class BzrDirFormat6(BzrDirFormat):
1340
"""Bzr control format 6.
1342
This format is a combined format for working tree, branch and repository.
1344
- Format 2 working trees [always]
1345
- Format 4 branches [always]
1346
- Format 6 repositories [always]
1349
_lock_class = lockable_files.TransportLock
1351
def get_format_string(self):
1352
"""See BzrDirFormat.get_format_string()."""
1353
return "Bazaar-NG branch, format 6\n"
1355
def get_format_description(self):
1356
"""See BzrDirFormat.get_format_description()."""
1357
return "All-in-one format 6"
1359
def get_converter(self, format=None):
1360
"""See BzrDirFormat.get_converter()."""
1361
# there is one and only one upgrade path here.
1362
return ConvertBzrDir6ToMeta()
1364
def _initialize_for_clone(self, url):
1365
return self.initialize_on_transport(get_transport(url), _cloning=True)
1367
def initialize_on_transport(self, transport, _cloning=False):
1368
"""Format 6 dirs always have working tree, branch and repository.
1370
Except when they are being cloned.
1372
from bzrlib.branch import BzrBranchFormat4
1373
from bzrlib.repository import RepositoryFormat6
1374
from bzrlib.workingtree import WorkingTreeFormat2
1375
result = super(BzrDirFormat6, self).initialize_on_transport(transport)
1376
RepositoryFormat6().initialize(result, _internal=True)
1378
branch = BzrBranchFormat4().initialize(result)
1380
WorkingTreeFormat2().initialize(result)
1381
except errors.NotLocalUrl:
1382
# Even though we can't access the working tree, we need to
1383
# create its control files.
1384
WorkingTreeFormat2().stub_initialize_remote(branch.control_files)
1387
def _open(self, transport):
1388
"""See BzrDirFormat._open."""
1389
return BzrDir6(transport, self)
1391
def __return_repository_format(self):
1392
"""Circular import protection."""
1393
from bzrlib.repository import RepositoryFormat6
1394
return RepositoryFormat6()
1395
repository_format = property(__return_repository_format)
1398
class BzrDirMetaFormat1(BzrDirFormat):
1399
"""Bzr meta control format 1
1401
This is the first format with split out working tree, branch and repository
1404
- Format 3 working trees [optional]
1405
- Format 5 branches [optional]
1406
- Format 7 repositories [optional]
1409
_lock_class = lockdir.LockDir
1411
def get_converter(self, format=None):
1412
"""See BzrDirFormat.get_converter()."""
1414
format = BzrDirFormat.get_default_format()
1415
if not isinstance(self, format.__class__):
1416
# converting away from metadir is not implemented
1417
raise NotImplementedError(self.get_converter)
1418
return ConvertMetaToMeta(format)
1420
def get_format_string(self):
1421
"""See BzrDirFormat.get_format_string()."""
1422
return "Bazaar-NG meta directory, format 1\n"
1424
def get_format_description(self):
1425
"""See BzrDirFormat.get_format_description()."""
1426
return "Meta directory format 1"
1428
def _open(self, transport):
1429
"""See BzrDirFormat._open."""
1430
return BzrDirMeta1(transport, self)
1432
def __return_repository_format(self):
1433
"""Circular import protection."""
1434
if getattr(self, '_repository_format', None):
1435
return self._repository_format
1436
from bzrlib.repository import RepositoryFormat
1437
return RepositoryFormat.get_default_format()
1439
def __set_repository_format(self, value):
1440
"""Allow changint the repository format for metadir formats."""
1441
self._repository_format = value
1443
repository_format = property(__return_repository_format, __set_repository_format)
1446
BzrDirFormat.register_format(BzrDirFormat4())
1447
BzrDirFormat.register_format(BzrDirFormat5())
1448
BzrDirFormat.register_format(BzrDirFormat6())
1449
__default_format = BzrDirMetaFormat1()
1450
BzrDirFormat.register_format(__default_format)
1451
BzrDirFormat.set_default_format(__default_format)
1454
class BzrDirTestProviderAdapter(object):
1455
"""A tool to generate a suite testing multiple bzrdir formats at once.
1457
This is done by copying the test once for each transport and injecting
1458
the transport_server, transport_readonly_server, and bzrdir_format
1459
classes into each copy. Each copy is also given a new id() to make it
1463
def __init__(self, transport_server, transport_readonly_server, formats):
1464
self._transport_server = transport_server
1465
self._transport_readonly_server = transport_readonly_server
1466
self._formats = formats
1468
def adapt(self, test):
1469
result = unittest.TestSuite()
1470
for format in self._formats:
1471
new_test = deepcopy(test)
1472
new_test.transport_server = self._transport_server
1473
new_test.transport_readonly_server = self._transport_readonly_server
1474
new_test.bzrdir_format = format
1475
def make_new_test_id():
1476
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1477
return lambda: new_id
1478
new_test.id = make_new_test_id()
1479
result.addTest(new_test)
1483
class Converter(object):
1484
"""Converts a disk format object from one format to another."""
1486
def convert(self, to_convert, pb):
1487
"""Perform the conversion of to_convert, giving feedback via pb.
1489
:param to_convert: The disk object to convert.
1490
:param pb: a progress bar to use for progress information.
1493
def step(self, message):
1494
"""Update the pb by a step."""
1496
self.pb.update(message, self.count, self.total)
1499
class ConvertBzrDir4To5(Converter):
1500
"""Converts format 4 bzr dirs to format 5."""
1503
super(ConvertBzrDir4To5, self).__init__()
1504
self.converted_revs = set()
1505
self.absent_revisions = set()
1509
def convert(self, to_convert, pb):
1510
"""See Converter.convert()."""
1511
self.bzrdir = to_convert
1513
self.pb.note('starting upgrade from format 4 to 5')
1514
if isinstance(self.bzrdir.transport, LocalTransport):
1515
self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
1516
self._convert_to_weaves()
1517
return BzrDir.open(self.bzrdir.root_transport.base)
1519
def _convert_to_weaves(self):
1520
self.pb.note('note: upgrade may be faster if all store files are ungzipped first')
1523
stat = self.bzrdir.transport.stat('weaves')
1524
if not S_ISDIR(stat.st_mode):
1525
self.bzrdir.transport.delete('weaves')
1526
self.bzrdir.transport.mkdir('weaves')
1527
except errors.NoSuchFile:
1528
self.bzrdir.transport.mkdir('weaves')
1529
# deliberately not a WeaveFile as we want to build it up slowly.
1530
self.inv_weave = Weave('inventory')
1531
# holds in-memory weaves for all files
1532
self.text_weaves = {}
1533
self.bzrdir.transport.delete('branch-format')
1534
self.branch = self.bzrdir.open_branch()
1535
self._convert_working_inv()
1536
rev_history = self.branch.revision_history()
1537
# to_read is a stack holding the revisions we still need to process;
1538
# appending to it adds new highest-priority revisions
1539
self.known_revisions = set(rev_history)
1540
self.to_read = rev_history[-1:]
1542
rev_id = self.to_read.pop()
1543
if (rev_id not in self.revisions
1544
and rev_id not in self.absent_revisions):
1545
self._load_one_rev(rev_id)
1547
to_import = self._make_order()
1548
for i, rev_id in enumerate(to_import):
1549
self.pb.update('converting revision', i, len(to_import))
1550
self._convert_one_rev(rev_id)
1552
self._write_all_weaves()
1553
self._write_all_revs()
1554
self.pb.note('upgraded to weaves:')
1555
self.pb.note(' %6d revisions and inventories', len(self.revisions))
1556
self.pb.note(' %6d revisions not present', len(self.absent_revisions))
1557
self.pb.note(' %6d texts', self.text_count)
1558
self._cleanup_spare_files_after_format4()
1559
self.branch.control_files.put_utf8('branch-format', BzrDirFormat5().get_format_string())
1561
def _cleanup_spare_files_after_format4(self):
1562
# FIXME working tree upgrade foo.
1563
for n in 'merged-patches', 'pending-merged-patches':
1565
## assert os.path.getsize(p) == 0
1566
self.bzrdir.transport.delete(n)
1567
except errors.NoSuchFile:
1569
self.bzrdir.transport.delete_tree('inventory-store')
1570
self.bzrdir.transport.delete_tree('text-store')
1572
def _convert_working_inv(self):
1573
inv = xml4.serializer_v4.read_inventory(
1574
self.branch.control_files.get('inventory'))
1575
new_inv_xml = xml5.serializer_v5.write_inventory_to_string(inv)
1576
# FIXME inventory is a working tree change.
1577
self.branch.control_files.put('inventory', StringIO(new_inv_xml))
1579
def _write_all_weaves(self):
1580
controlweaves = WeaveStore(self.bzrdir.transport, prefixed=False)
1581
weave_transport = self.bzrdir.transport.clone('weaves')
1582
weaves = WeaveStore(weave_transport, prefixed=False)
1583
transaction = WriteTransaction()
1587
for file_id, file_weave in self.text_weaves.items():
1588
self.pb.update('writing weave', i, len(self.text_weaves))
1589
weaves._put_weave(file_id, file_weave, transaction)
1591
self.pb.update('inventory', 0, 1)
1592
controlweaves._put_weave('inventory', self.inv_weave, transaction)
1593
self.pb.update('inventory', 1, 1)
1597
def _write_all_revs(self):
1598
"""Write all revisions out in new form."""
1599
self.bzrdir.transport.delete_tree('revision-store')
1600
self.bzrdir.transport.mkdir('revision-store')
1601
revision_transport = self.bzrdir.transport.clone('revision-store')
1603
_revision_store = TextRevisionStore(TextStore(revision_transport,
1607
transaction = WriteTransaction()
1608
for i, rev_id in enumerate(self.converted_revs):
1609
self.pb.update('write revision', i, len(self.converted_revs))
1610
_revision_store.add_revision(self.revisions[rev_id], transaction)
1614
def _load_one_rev(self, rev_id):
1615
"""Load a revision object into memory.
1617
Any parents not either loaded or abandoned get queued to be
1619
self.pb.update('loading revision',
1620
len(self.revisions),
1621
len(self.known_revisions))
1622
if not self.branch.repository.has_revision(rev_id):
1624
self.pb.note('revision {%s} not present in branch; '
1625
'will be converted as a ghost',
1627
self.absent_revisions.add(rev_id)
1629
rev = self.branch.repository._revision_store.get_revision(rev_id,
1630
self.branch.repository.get_transaction())
1631
for parent_id in rev.parent_ids:
1632
self.known_revisions.add(parent_id)
1633
self.to_read.append(parent_id)
1634
self.revisions[rev_id] = rev
1636
def _load_old_inventory(self, rev_id):
1637
assert rev_id not in self.converted_revs
1638
old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
1639
inv = xml4.serializer_v4.read_inventory_from_string(old_inv_xml)
1640
inv.revision_id = rev_id
1641
rev = self.revisions[rev_id]
1642
if rev.inventory_sha1:
1643
assert rev.inventory_sha1 == sha_string(old_inv_xml), \
1644
'inventory sha mismatch for {%s}' % rev_id
1647
def _load_updated_inventory(self, rev_id):
1648
assert rev_id in self.converted_revs
1649
inv_xml = self.inv_weave.get_text(rev_id)
1650
inv = xml5.serializer_v5.read_inventory_from_string(inv_xml)
1653
def _convert_one_rev(self, rev_id):
1654
"""Convert revision and all referenced objects to new format."""
1655
rev = self.revisions[rev_id]
1656
inv = self._load_old_inventory(rev_id)
1657
present_parents = [p for p in rev.parent_ids
1658
if p not in self.absent_revisions]
1659
self._convert_revision_contents(rev, inv, present_parents)
1660
self._store_new_weave(rev, inv, present_parents)
1661
self.converted_revs.add(rev_id)
1663
def _store_new_weave(self, rev, inv, present_parents):
1664
# the XML is now updated with text versions
1666
entries = inv.iter_entries()
1668
for path, ie in entries:
1669
assert getattr(ie, 'revision', None) is not None, \
1670
'no revision on {%s} in {%s}' % \
1671
(file_id, rev.revision_id)
1672
new_inv_xml = xml5.serializer_v5.write_inventory_to_string(inv)
1673
new_inv_sha1 = sha_string(new_inv_xml)
1674
self.inv_weave.add_lines(rev.revision_id,
1676
new_inv_xml.splitlines(True))
1677
rev.inventory_sha1 = new_inv_sha1
1679
def _convert_revision_contents(self, rev, inv, present_parents):
1680
"""Convert all the files within a revision.
1682
Also upgrade the inventory to refer to the text revision ids."""
1683
rev_id = rev.revision_id
1684
mutter('converting texts of revision {%s}',
1686
parent_invs = map(self._load_updated_inventory, present_parents)
1687
entries = inv.iter_entries()
1689
for path, ie in entries:
1690
self._convert_file_version(rev, ie, parent_invs)
1692
def _convert_file_version(self, rev, ie, parent_invs):
1693
"""Convert one version of one file.
1695
The file needs to be added into the weave if it is a merge
1696
of >=2 parents or if it's changed from its parent.
1698
file_id = ie.file_id
1699
rev_id = rev.revision_id
1700
w = self.text_weaves.get(file_id)
1703
self.text_weaves[file_id] = w
1704
text_changed = False
1705
previous_entries = ie.find_previous_heads(parent_invs,
1709
for old_revision in previous_entries:
1710
# if this fails, its a ghost ?
1711
assert old_revision in self.converted_revs, \
1712
"Revision {%s} not in converted_revs" % old_revision
1713
self.snapshot_ie(previous_entries, ie, w, rev_id)
1715
assert getattr(ie, 'revision', None) is not None
1717
def snapshot_ie(self, previous_revisions, ie, w, rev_id):
1718
# TODO: convert this logic, which is ~= snapshot to
1719
# a call to:. This needs the path figured out. rather than a work_tree
1720
# a v4 revision_tree can be given, or something that looks enough like
1721
# one to give the file content to the entry if it needs it.
1722
# and we need something that looks like a weave store for snapshot to
1724
#ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
1725
if len(previous_revisions) == 1:
1726
previous_ie = previous_revisions.values()[0]
1727
if ie._unchanged(previous_ie):
1728
ie.revision = previous_ie.revision
1731
text = self.branch.repository.text_store.get(ie.text_id)
1732
file_lines = text.readlines()
1733
assert sha_strings(file_lines) == ie.text_sha1
1734
assert sum(map(len, file_lines)) == ie.text_size
1735
w.add_lines(rev_id, previous_revisions, file_lines)
1736
self.text_count += 1
1738
w.add_lines(rev_id, previous_revisions, [])
1739
ie.revision = rev_id
1741
def _make_order(self):
1742
"""Return a suitable order for importing revisions.
1744
The order must be such that an revision is imported after all
1745
its (present) parents.
1747
todo = set(self.revisions.keys())
1748
done = self.absent_revisions.copy()
1751
# scan through looking for a revision whose parents
1753
for rev_id in sorted(list(todo)):
1754
rev = self.revisions[rev_id]
1755
parent_ids = set(rev.parent_ids)
1756
if parent_ids.issubset(done):
1757
# can take this one now
1758
order.append(rev_id)
1764
class ConvertBzrDir5To6(Converter):
1765
"""Converts format 5 bzr dirs to format 6."""
1767
def convert(self, to_convert, pb):
1768
"""See Converter.convert()."""
1769
self.bzrdir = to_convert
1771
self.pb.note('starting upgrade from format 5 to 6')
1772
self._convert_to_prefixed()
1773
return BzrDir.open(self.bzrdir.root_transport.base)
1775
def _convert_to_prefixed(self):
1776
from bzrlib.store import TransportStore
1777
self.bzrdir.transport.delete('branch-format')
1778
for store_name in ["weaves", "revision-store"]:
1779
self.pb.note("adding prefixes to %s" % store_name)
1780
store_transport = self.bzrdir.transport.clone(store_name)
1781
store = TransportStore(store_transport, prefixed=True)
1782
for urlfilename in store_transport.list_dir('.'):
1783
filename = urlutils.unescape(urlfilename)
1784
if (filename.endswith(".weave") or
1785
filename.endswith(".gz") or
1786
filename.endswith(".sig")):
1787
file_id = os.path.splitext(filename)[0]
1790
prefix_dir = store.hash_prefix(file_id)
1791
# FIXME keep track of the dirs made RBC 20060121
1793
store_transport.move(filename, prefix_dir + '/' + filename)
1794
except errors.NoSuchFile: # catches missing dirs strangely enough
1795
store_transport.mkdir(prefix_dir)
1796
store_transport.move(filename, prefix_dir + '/' + filename)
1797
self.bzrdir._control_files.put_utf8('branch-format', BzrDirFormat6().get_format_string())
1800
class ConvertBzrDir6ToMeta(Converter):
1801
"""Converts format 6 bzr dirs to metadirs."""
1803
def convert(self, to_convert, pb):
1804
"""See Converter.convert()."""
1805
self.bzrdir = to_convert
1808
self.total = 20 # the steps we know about
1809
self.garbage_inventories = []
1811
self.pb.note('starting upgrade from format 6 to metadir')
1812
self.bzrdir._control_files.put_utf8('branch-format', "Converting to format 6")
1813
# its faster to move specific files around than to open and use the apis...
1814
# first off, nuke ancestry.weave, it was never used.
1816
self.step('Removing ancestry.weave')
1817
self.bzrdir.transport.delete('ancestry.weave')
1818
except errors.NoSuchFile:
1820
# find out whats there
1821
self.step('Finding branch files')
1822
last_revision = self.bzrdir.open_branch().last_revision()
1823
bzrcontents = self.bzrdir.transport.list_dir('.')
1824
for name in bzrcontents:
1825
if name.startswith('basis-inventory.'):
1826
self.garbage_inventories.append(name)
1827
# create new directories for repository, working tree and branch
1828
self.dir_mode = self.bzrdir._control_files._dir_mode
1829
self.file_mode = self.bzrdir._control_files._file_mode
1830
repository_names = [('inventory.weave', True),
1831
('revision-store', True),
1833
self.step('Upgrading repository ')
1834
self.bzrdir.transport.mkdir('repository', mode=self.dir_mode)
1835
self.make_lock('repository')
1836
# we hard code the formats here because we are converting into
1837
# the meta format. The meta format upgrader can take this to a
1838
# future format within each component.
1839
self.put_format('repository', bzrlib.repository.RepositoryFormat7())
1840
for entry in repository_names:
1841
self.move_entry('repository', entry)
1843
self.step('Upgrading branch ')
1844
self.bzrdir.transport.mkdir('branch', mode=self.dir_mode)
1845
self.make_lock('branch')
1846
self.put_format('branch', bzrlib.branch.BzrBranchFormat5())
1847
branch_files = [('revision-history', True),
1848
('branch-name', True),
1850
for entry in branch_files:
1851
self.move_entry('branch', entry)
1853
checkout_files = [('pending-merges', True),
1854
('inventory', True),
1855
('stat-cache', False)]
1856
# If a mandatory checkout file is not present, the branch does not have
1857
# a functional checkout. Do not create a checkout in the converted
1859
for name, mandatory in checkout_files:
1860
if mandatory and name not in bzrcontents:
1861
has_checkout = False
1865
if not has_checkout:
1866
self.pb.note('No working tree.')
1867
# If some checkout files are there, we may as well get rid of them.
1868
for name, mandatory in checkout_files:
1869
if name in bzrcontents:
1870
self.bzrdir.transport.delete(name)
1872
self.step('Upgrading working tree')
1873
self.bzrdir.transport.mkdir('checkout', mode=self.dir_mode)
1874
self.make_lock('checkout')
1876
'checkout', bzrlib.workingtree.WorkingTreeFormat3())
1877
self.bzrdir.transport.delete_multi(
1878
self.garbage_inventories, self.pb)
1879
for entry in checkout_files:
1880
self.move_entry('checkout', entry)
1881
if last_revision is not None:
1882
self.bzrdir._control_files.put_utf8(
1883
'checkout/last-revision', last_revision)
1884
self.bzrdir._control_files.put_utf8(
1885
'branch-format', BzrDirMetaFormat1().get_format_string())
1886
return BzrDir.open(self.bzrdir.root_transport.base)
1888
def make_lock(self, name):
1889
"""Make a lock for the new control dir name."""
1890
self.step('Make %s lock' % name)
1891
ld = lockdir.LockDir(self.bzrdir.transport,
1893
file_modebits=self.file_mode,
1894
dir_modebits=self.dir_mode)
1897
def move_entry(self, new_dir, entry):
1898
"""Move then entry name into new_dir."""
1900
mandatory = entry[1]
1901
self.step('Moving %s' % name)
1903
self.bzrdir.transport.move(name, '%s/%s' % (new_dir, name))
1904
except errors.NoSuchFile:
1908
def put_format(self, dirname, format):
1909
self.bzrdir._control_files.put_utf8('%s/format' % dirname, format.get_format_string())
1912
class ConvertMetaToMeta(Converter):
1913
"""Converts the components of metadirs."""
1915
def __init__(self, target_format):
1916
"""Create a metadir to metadir converter.
1918
:param target_format: The final metadir format that is desired.
1920
self.target_format = target_format
1922
def convert(self, to_convert, pb):
1923
"""See Converter.convert()."""
1924
self.bzrdir = to_convert
1928
self.step('checking repository format')
1930
repo = self.bzrdir.open_repository()
1931
except errors.NoRepositoryPresent:
1934
if not isinstance(repo._format, self.target_format.repository_format.__class__):
1935
from bzrlib.repository import CopyConverter
1936
self.pb.note('starting repository conversion')
1937
converter = CopyConverter(self.target_format.repository_format)
1938
converter.convert(repo, pb)