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 copy import deepcopy
32
from cStringIO import StringIO
34
from stat import S_ISDIR
35
from unittest import TestSuite
38
import bzrlib.errors as errors
39
from bzrlib.lockable_files import LockableFiles, TransportLock
40
from bzrlib.lockdir import LockDir
41
from bzrlib.osutils import (
48
import bzrlib.revision
49
from bzrlib.store.revision.text import TextRevisionStore
50
from bzrlib.store.text import TextStore
51
from bzrlib.store.versioned import WeaveStore
52
from bzrlib.trace import mutter
53
from bzrlib.transactions import WriteTransaction
54
from bzrlib.transport import get_transport
55
from bzrlib.transport.local import LocalTransport
56
import bzrlib.urlutils as urlutils
57
from bzrlib.weave import Weave
58
from bzrlib.xml4 import serializer_v4
63
"""A .bzr control diretory.
65
BzrDir instances let you create or open any of the things that can be
66
found within .bzr - checkouts, branches and repositories.
69
the transport which this bzr dir is rooted at (i.e. file:///.../.bzr/)
71
a transport connected to the directory this bzr was opened from.
75
"""Invoke break_lock on the first object in the bzrdir.
77
If there is a tree, the tree is opened and break_lock() called.
78
Otherwise, branch is tried, and finally repository.
81
thing_to_unlock = self.open_workingtree()
82
except (errors.NotLocalUrl, errors.NoWorkingTree):
84
thing_to_unlock = self.open_branch()
85
except errors.NotBranchError:
87
thing_to_unlock = self.open_repository()
88
except errors.NoRepositoryPresent:
90
thing_to_unlock.break_lock()
92
def can_convert_format(self):
93
"""Return true if this bzrdir is one whose format we can convert from."""
96
def check_conversion_target(self, target_format):
97
target_repo_format = target_format.repository_format
98
source_repo_format = self._format.repository_format
99
source_repo_format.check_conversion_target(target_repo_format)
102
def _check_supported(format, allow_unsupported):
103
"""Check whether format is a supported format.
105
If allow_unsupported is True, this is a no-op.
107
if not allow_unsupported and not format.is_supported():
108
# see open_downlevel to open legacy branches.
109
raise errors.UnsupportedFormatError(format=format)
111
def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
112
"""Clone this bzrdir and its contents to url verbatim.
114
If urls last component does not exist, it will be created.
116
if revision_id is not None, then the clone operation may tune
117
itself to download less data.
118
:param force_new_repo: Do not use a shared repository for the target
119
even if one is available.
122
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
123
result = self._format.initialize(url)
125
local_repo = self.find_repository()
126
except errors.NoRepositoryPresent:
129
# may need to copy content in
131
result_repo = local_repo.clone(
133
revision_id=revision_id,
135
result_repo.set_make_working_trees(local_repo.make_working_trees())
138
result_repo = result.find_repository()
139
# fetch content this dir needs.
141
# XXX FIXME RBC 20060214 need tests for this when the basis
143
result_repo.fetch(basis_repo, revision_id=revision_id)
144
result_repo.fetch(local_repo, revision_id=revision_id)
145
except errors.NoRepositoryPresent:
146
# needed to make one anyway.
147
result_repo = local_repo.clone(
149
revision_id=revision_id,
151
result_repo.set_make_working_trees(local_repo.make_working_trees())
152
# 1 if there is a branch present
153
# make sure its content is available in the target repository
156
self.open_branch().clone(result, revision_id=revision_id)
157
except errors.NotBranchError:
160
self.open_workingtree().clone(result, basis=basis_tree)
161
except (errors.NoWorkingTree, errors.NotLocalUrl):
165
def _get_basis_components(self, basis):
166
"""Retrieve the basis components that are available at basis."""
168
return None, None, None
170
basis_tree = basis.open_workingtree()
171
basis_branch = basis_tree.branch
172
basis_repo = basis_branch.repository
173
except (errors.NoWorkingTree, errors.NotLocalUrl):
176
basis_branch = basis.open_branch()
177
basis_repo = basis_branch.repository
178
except errors.NotBranchError:
181
basis_repo = basis.open_repository()
182
except errors.NoRepositoryPresent:
184
return basis_repo, basis_branch, basis_tree
186
# TODO: This should be given a Transport, and should chdir up; otherwise
187
# this will open a new connection.
188
def _make_tail(self, url):
189
head, tail = urlutils.split(url)
190
if tail and tail != '.':
191
t = bzrlib.transport.get_transport(head)
194
except errors.FileExists:
197
# TODO: Should take a Transport
199
def create(cls, base):
200
"""Create a new BzrDir at the url 'base'.
202
This will call the current default formats initialize with base
203
as the only parameter.
205
If you need a specific format, consider creating an instance
206
of that and calling initialize().
208
if cls is not BzrDir:
209
raise AssertionError("BzrDir.create always creates the default format, "
210
"not one of %r" % cls)
211
head, tail = urlutils.split(base)
212
if tail and tail != '.':
213
t = bzrlib.transport.get_transport(head)
216
except errors.FileExists:
218
return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
220
def create_branch(self):
221
"""Create a branch in this BzrDir.
223
The bzrdirs format will control what branch format is created.
224
For more control see BranchFormatXX.create(a_bzrdir).
226
raise NotImplementedError(self.create_branch)
229
def create_branch_and_repo(base, force_new_repo=False):
230
"""Create a new BzrDir, Branch and Repository at the url 'base'.
232
This will use the current default BzrDirFormat, and use whatever
233
repository format that that uses via bzrdir.create_branch and
234
create_repository. If a shared repository is available that is used
237
The created Branch object is returned.
239
:param base: The URL to create the branch at.
240
:param force_new_repo: If True a new repository is always created.
242
bzrdir = BzrDir.create(base)
243
bzrdir._find_or_create_repository(force_new_repo)
244
return bzrdir.create_branch()
246
def _find_or_create_repository(self, force_new_repo):
247
"""Create a new repository if needed, returning the repository."""
249
return self.create_repository()
251
return self.find_repository()
252
except errors.NoRepositoryPresent:
253
return self.create_repository()
256
def create_branch_convenience(base, force_new_repo=False,
257
force_new_tree=None, format=None):
258
"""Create a new BzrDir, Branch and Repository at the url 'base'.
260
This is a convenience function - it will use an existing repository
261
if possible, can be told explicitly whether to create a working tree or
264
This will use the current default BzrDirFormat, and use whatever
265
repository format that that uses via bzrdir.create_branch and
266
create_repository. If a shared repository is available that is used
267
preferentially. Whatever repository is used, its tree creation policy
270
The created Branch object is returned.
271
If a working tree cannot be made due to base not being a file:// url,
272
no error is raised unless force_new_tree is True, in which case no
273
data is created on disk and NotLocalUrl is raised.
275
:param base: The URL to create the branch at.
276
:param force_new_repo: If True a new repository is always created.
277
:param force_new_tree: If True or False force creation of a tree or
278
prevent such creation respectively.
279
:param format: Override for the for the bzrdir format to create
282
# check for non local urls
283
t = get_transport(safe_unicode(base))
284
if not isinstance(t, LocalTransport):
285
raise errors.NotLocalUrl(base)
287
bzrdir = BzrDir.create(base)
289
bzrdir = format.initialize(base)
290
repo = bzrdir._find_or_create_repository(force_new_repo)
291
result = bzrdir.create_branch()
292
if force_new_tree or (repo.make_working_trees() and
293
force_new_tree is None):
295
bzrdir.create_workingtree()
296
except errors.NotLocalUrl:
301
def create_repository(base, shared=False):
302
"""Create a new BzrDir and Repository at the url 'base'.
304
This will use the current default BzrDirFormat, and use whatever
305
repository format that that uses for bzrdirformat.create_repository.
307
:param shared: Create a shared repository rather than a standalone
309
The Repository object is returned.
311
This must be overridden as an instance method in child classes, where
312
it should take no parameters and construct whatever repository format
313
that child class desires.
315
bzrdir = BzrDir.create(base)
316
return bzrdir.create_repository(shared)
319
def create_standalone_workingtree(base):
320
"""Create a new BzrDir, WorkingTree, Branch and Repository at 'base'.
322
'base' must be a local path or a file:// url.
324
This will use the current default BzrDirFormat, and use whatever
325
repository format that that uses for bzrdirformat.create_workingtree,
326
create_branch and create_repository.
328
:return: The WorkingTree object.
330
t = get_transport(safe_unicode(base))
331
if not isinstance(t, LocalTransport):
332
raise errors.NotLocalUrl(base)
333
bzrdir = BzrDir.create_branch_and_repo(safe_unicode(base),
334
force_new_repo=True).bzrdir
335
return bzrdir.create_workingtree()
337
def create_workingtree(self, revision_id=None):
338
"""Create a working tree at this BzrDir.
340
revision_id: create it as of this revision id.
342
raise NotImplementedError(self.create_workingtree)
344
def find_repository(self):
345
"""Find the repository that should be used for a_bzrdir.
347
This does not require a branch as we use it to find the repo for
348
new branches as well as to hook existing branches up to their
352
return self.open_repository()
353
except errors.NoRepositoryPresent:
355
next_transport = self.root_transport.clone('..')
357
# find the next containing bzrdir
359
found_bzrdir = BzrDir.open_containing_from_transport(
361
except errors.NotBranchError:
363
raise errors.NoRepositoryPresent(self)
364
# does it have a repository ?
366
repository = found_bzrdir.open_repository()
367
except errors.NoRepositoryPresent:
368
next_transport = found_bzrdir.root_transport.clone('..')
369
if (found_bzrdir.root_transport.base == next_transport.base):
370
# top of the file system
374
if ((found_bzrdir.root_transport.base ==
375
self.root_transport.base) or repository.is_shared()):
378
raise errors.NoRepositoryPresent(self)
379
raise errors.NoRepositoryPresent(self)
381
def get_branch_transport(self, branch_format):
382
"""Get the transport for use by branch format in this BzrDir.
384
Note that bzr dirs that do not support format strings will raise
385
IncompatibleFormat if the branch format they are given has
386
a format string, and vice versa.
388
If branch_format is None, the transport is returned with no
389
checking. if it is not None, then the returned transport is
390
guaranteed to point to an existing directory ready for use.
392
raise NotImplementedError(self.get_branch_transport)
394
def get_repository_transport(self, repository_format):
395
"""Get the transport for use by repository format in this BzrDir.
397
Note that bzr dirs that do not support format strings will raise
398
IncompatibleFormat if the repository format they are given has
399
a format string, and vice versa.
401
If repository_format is None, the transport is returned with no
402
checking. if it is not None, then the returned transport is
403
guaranteed to point to an existing directory ready for use.
405
raise NotImplementedError(self.get_repository_transport)
407
def get_workingtree_transport(self, tree_format):
408
"""Get the transport for use by workingtree format in this BzrDir.
410
Note that bzr dirs that do not support format strings will raise
411
IncompatibleFormat if the workingtree format they are given has
412
a format string, and vice versa.
414
If workingtree_format is None, the transport is returned with no
415
checking. if it is not None, then the returned transport is
416
guaranteed to point to an existing directory ready for use.
418
raise NotImplementedError(self.get_workingtree_transport)
420
def __init__(self, _transport, _format):
421
"""Initialize a Bzr control dir object.
423
Only really common logic should reside here, concrete classes should be
424
made with varying behaviours.
426
:param _format: the format that is creating this BzrDir instance.
427
:param _transport: the transport this dir is based at.
429
self._format = _format
430
self.transport = _transport.clone('.bzr')
431
self.root_transport = _transport
433
def is_control_filename(self, filename):
434
"""True if filename is the name of a path which is reserved for bzrdir's.
436
:param filename: A filename within the root transport of this bzrdir.
438
This is true IF and ONLY IF the filename is part of the namespace reserved
439
for bzr control dirs. Currently this is the '.bzr' directory in the root
440
of the root_transport. it is expected that plugins will need to extend
441
this in the future - for instance to make bzr talk with svn working
444
# this might be better on the BzrDirFormat class because it refers to
445
# all the possible bzrdir disk formats.
446
# This method is tested via the workingtree is_control_filename tests-
447
# it was extracted from WorkingTree.is_control_filename. If the methods
448
# contract is extended beyond the current trivial implementation please
449
# add new tests for it to the appropriate place.
450
return filename == '.bzr' or filename.startswith('.bzr/')
452
def needs_format_conversion(self, format=None):
453
"""Return true if this bzrdir needs convert_format run on it.
455
For instance, if the repository format is out of date but the
456
branch and working tree are not, this should return True.
458
:param format: Optional parameter indicating a specific desired
459
format we plan to arrive at.
461
raise NotImplementedError(self.needs_format_conversion)
464
def open_unsupported(base):
465
"""Open a branch which is not supported."""
466
return BzrDir.open(base, _unsupported=True)
469
def open(base, _unsupported=False):
470
"""Open an existing bzrdir, rooted at 'base' (url)
472
_unsupported is a private parameter to the BzrDir class.
474
t = get_transport(base)
475
return BzrDir.open_from_transport(t, _unsupported=_unsupported)
478
def open_from_transport(transport, _unsupported=False):
479
"""Open a bzrdir within a particular directory.
481
:param transport: Transport containing the bzrdir.
482
:param _unsupported: private.
484
format = BzrDirFormat.find_format(transport)
485
BzrDir._check_supported(format, _unsupported)
486
return format.open(transport, _found=True)
488
def open_branch(self, unsupported=False):
489
"""Open the branch object at this BzrDir if one is present.
491
If unsupported is True, then no longer supported branch formats can
494
TODO: static convenience version of this?
496
raise NotImplementedError(self.open_branch)
499
def open_containing(url):
500
"""Open an existing branch which contains url.
502
:param url: url to search from.
503
See open_containing_from_transport for more detail.
505
return BzrDir.open_containing_from_transport(get_transport(url))
508
def open_containing_from_transport(a_transport):
509
"""Open an existing branch which contains a_transport.base
511
This probes for a branch at a_transport, and searches upwards from there.
513
Basically we keep looking up until we find the control directory or
514
run into the root. If there isn't one, raises NotBranchError.
515
If there is one and it is either an unrecognised format or an unsupported
516
format, UnknownFormatError or UnsupportedFormatError are raised.
517
If there is one, it is returned, along with the unused portion of url.
519
:return: The BzrDir that contains the path, and a Unicode path
520
for the rest of the URL.
522
# this gets the normalised url back. I.e. '.' -> the full path.
523
url = a_transport.base
526
result = BzrDir.open_from_transport(a_transport)
527
return result, urlutils.unescape(a_transport.relpath(url))
528
except errors.NotBranchError, e:
530
new_t = a_transport.clone('..')
531
if new_t.base == a_transport.base:
532
# reached the root, whatever that may be
533
raise errors.NotBranchError(path=url)
536
def open_repository(self, _unsupported=False):
537
"""Open the repository object at this BzrDir if one is present.
539
This will not follow the Branch object pointer - its strictly a direct
540
open facility. Most client code should use open_branch().repository to
543
_unsupported is a private parameter, not part of the api.
544
TODO: static convenience version of this?
546
raise NotImplementedError(self.open_repository)
548
def open_workingtree(self, _unsupported=False):
549
"""Open the workingtree object at this BzrDir if one is present.
551
TODO: static convenience version of this?
553
raise NotImplementedError(self.open_workingtree)
555
def has_branch(self):
556
"""Tell if this bzrdir contains a branch.
558
Note: if you're going to open the branch, you should just go ahead
559
and try, and not ask permission first. (This method just opens the
560
branch and discards it, and that's somewhat expensive.)
565
except errors.NotBranchError:
568
def has_workingtree(self):
569
"""Tell if this bzrdir contains a working tree.
571
This will still raise an exception if the bzrdir has a workingtree that
572
is remote & inaccessible.
574
Note: if you're going to open the working tree, you should just go ahead
575
and try, and not ask permission first. (This method just opens the
576
workingtree and discards it, and that's somewhat expensive.)
579
self.open_workingtree()
581
except errors.NoWorkingTree:
584
def cloning_metadir(self, basis=None):
585
"""Produce a metadir suitable for cloning with"""
586
def related_repository(bzrdir):
588
branch = bzrdir.open_branch()
589
return branch.repository
590
except errors.NotBranchError:
592
return bzrdir.open_repository()
593
result_format = self._format.__class__()
596
source_repository = related_repository(self)
597
except errors.NoRepositoryPresent:
600
source_repository = related_repository(self)
601
result_format.repository_format = source_repository._format
602
except errors.NoRepositoryPresent:
606
def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
607
"""Create a copy of this bzrdir prepared for use as a new line of
610
If urls last component does not exist, it will be created.
612
Attributes related to the identity of the source branch like
613
branch nickname will be cleaned, a working tree is created
614
whether one existed before or not; and a local branch is always
617
if revision_id is not None, then the clone operation may tune
618
itself to download less data.
621
cloning_format = self.cloning_metadir(basis)
622
result = cloning_format.initialize(url)
623
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
625
source_branch = self.open_branch()
626
source_repository = source_branch.repository
627
except errors.NotBranchError:
630
source_repository = self.open_repository()
631
except errors.NoRepositoryPresent:
632
# copy the entire basis one if there is one
633
# but there is no repository.
634
source_repository = basis_repo
639
result_repo = result.find_repository()
640
except errors.NoRepositoryPresent:
642
if source_repository is None and result_repo is not None:
644
elif source_repository is None and result_repo is None:
645
# no repo available, make a new one
646
result.create_repository()
647
elif source_repository is not None and result_repo is None:
648
# have source, and want to make a new target repo
649
# we don't clone the repo because that preserves attributes
650
# like is_shared(), and we have not yet implemented a
651
# repository sprout().
652
result_repo = result.create_repository()
653
if result_repo is not None:
654
# fetch needed content into target.
656
# XXX FIXME RBC 20060214 need tests for this when the basis
658
result_repo.fetch(basis_repo, revision_id=revision_id)
659
if source_repository is not None:
660
result_repo.fetch(source_repository, revision_id=revision_id)
661
if source_branch is not None:
662
source_branch.sprout(result, revision_id=revision_id)
664
result.create_branch()
665
# TODO: jam 20060426 we probably need a test in here in the
666
# case that the newly sprouted branch is a remote one
667
if result_repo is None or result_repo.make_working_trees():
668
result.create_workingtree()
672
class BzrDirPreSplitOut(BzrDir):
673
"""A common class for the all-in-one formats."""
675
def __init__(self, _transport, _format):
676
"""See BzrDir.__init__."""
677
super(BzrDirPreSplitOut, self).__init__(_transport, _format)
678
assert self._format._lock_class == TransportLock
679
assert self._format._lock_file_name == 'branch-lock'
680
self._control_files = LockableFiles(self.get_branch_transport(None),
681
self._format._lock_file_name,
682
self._format._lock_class)
684
def break_lock(self):
685
"""Pre-splitout bzrdirs do not suffer from stale locks."""
686
raise NotImplementedError(self.break_lock)
688
def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
689
"""See BzrDir.clone()."""
690
from bzrlib.workingtree import WorkingTreeFormat2
692
result = self._format._initialize_for_clone(url)
693
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
694
self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
695
from_branch = self.open_branch()
696
from_branch.clone(result, revision_id=revision_id)
698
self.open_workingtree().clone(result, basis=basis_tree)
699
except errors.NotLocalUrl:
700
# make a new one, this format always has to have one.
702
WorkingTreeFormat2().initialize(result)
703
except errors.NotLocalUrl:
704
# but we cannot do it for remote trees.
705
to_branch = result.open_branch()
706
WorkingTreeFormat2().stub_initialize_remote(to_branch.control_files)
709
def create_branch(self):
710
"""See BzrDir.create_branch."""
711
return self.open_branch()
713
def create_repository(self, shared=False):
714
"""See BzrDir.create_repository."""
716
raise errors.IncompatibleFormat('shared repository', self._format)
717
return self.open_repository()
719
def create_workingtree(self, revision_id=None):
720
"""See BzrDir.create_workingtree."""
721
# this looks buggy but is not -really-
722
# clone and sprout will have set the revision_id
723
# and that will have set it for us, its only
724
# specific uses of create_workingtree in isolation
725
# that can do wonky stuff here, and that only
726
# happens for creating checkouts, which cannot be
727
# done on this format anyway. So - acceptable wart.
728
result = self.open_workingtree()
729
if revision_id is not None:
730
if revision_id == bzrlib.revision.NULL_REVISION:
731
result.set_parent_ids([])
733
result.set_parent_ids([revision_id])
736
def get_branch_transport(self, branch_format):
737
"""See BzrDir.get_branch_transport()."""
738
if branch_format is None:
739
return self.transport
741
branch_format.get_format_string()
742
except NotImplementedError:
743
return self.transport
744
raise errors.IncompatibleFormat(branch_format, self._format)
746
def get_repository_transport(self, repository_format):
747
"""See BzrDir.get_repository_transport()."""
748
if repository_format is None:
749
return self.transport
751
repository_format.get_format_string()
752
except NotImplementedError:
753
return self.transport
754
raise errors.IncompatibleFormat(repository_format, self._format)
756
def get_workingtree_transport(self, workingtree_format):
757
"""See BzrDir.get_workingtree_transport()."""
758
if workingtree_format is None:
759
return self.transport
761
workingtree_format.get_format_string()
762
except NotImplementedError:
763
return self.transport
764
raise errors.IncompatibleFormat(workingtree_format, self._format)
766
def needs_format_conversion(self, format=None):
767
"""See BzrDir.needs_format_conversion()."""
768
# if the format is not the same as the system default,
769
# an upgrade is needed.
771
format = BzrDirFormat.get_default_format()
772
return not isinstance(self._format, format.__class__)
774
def open_branch(self, unsupported=False):
775
"""See BzrDir.open_branch."""
776
from bzrlib.branch import BzrBranchFormat4
777
format = BzrBranchFormat4()
778
self._check_supported(format, unsupported)
779
return format.open(self, _found=True)
781
def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
782
"""See BzrDir.sprout()."""
783
from bzrlib.workingtree import WorkingTreeFormat2
785
result = self._format._initialize_for_clone(url)
786
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
788
self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
789
except errors.NoRepositoryPresent:
792
self.open_branch().sprout(result, revision_id=revision_id)
793
except errors.NotBranchError:
795
# we always want a working tree
796
WorkingTreeFormat2().initialize(result)
800
class BzrDir4(BzrDirPreSplitOut):
801
"""A .bzr version 4 control object.
803
This is a deprecated format and may be removed after sept 2006.
806
def create_repository(self, shared=False):
807
"""See BzrDir.create_repository."""
808
return self._format.repository_format.initialize(self, shared)
810
def needs_format_conversion(self, format=None):
811
"""Format 4 dirs are always in need of conversion."""
814
def open_repository(self):
815
"""See BzrDir.open_repository."""
816
from bzrlib.repository import RepositoryFormat4
817
return RepositoryFormat4().open(self, _found=True)
820
class BzrDir5(BzrDirPreSplitOut):
821
"""A .bzr version 5 control object.
823
This is a deprecated format and may be removed after sept 2006.
826
def open_repository(self):
827
"""See BzrDir.open_repository."""
828
from bzrlib.repository import RepositoryFormat5
829
return RepositoryFormat5().open(self, _found=True)
831
def open_workingtree(self, _unsupported=False):
832
"""See BzrDir.create_workingtree."""
833
from bzrlib.workingtree import WorkingTreeFormat2
834
return WorkingTreeFormat2().open(self, _found=True)
837
class BzrDir6(BzrDirPreSplitOut):
838
"""A .bzr version 6 control object.
840
This is a deprecated format and may be removed after sept 2006.
843
def open_repository(self):
844
"""See BzrDir.open_repository."""
845
from bzrlib.repository import RepositoryFormat6
846
return RepositoryFormat6().open(self, _found=True)
848
def open_workingtree(self, _unsupported=False):
849
"""See BzrDir.create_workingtree."""
850
from bzrlib.workingtree import WorkingTreeFormat2
851
return WorkingTreeFormat2().open(self, _found=True)
854
class BzrDirMeta1(BzrDir):
855
"""A .bzr meta version 1 control object.
857
This is the first control object where the
858
individual aspects are really split out: there are separate repository,
859
workingtree and branch subdirectories and any subset of the three can be
860
present within a BzrDir.
863
def can_convert_format(self):
864
"""See BzrDir.can_convert_format()."""
867
def create_branch(self):
868
"""See BzrDir.create_branch."""
869
from bzrlib.branch import BranchFormat
870
return BranchFormat.get_default_format().initialize(self)
872
def create_repository(self, shared=False):
873
"""See BzrDir.create_repository."""
874
return self._format.repository_format.initialize(self, shared)
876
def create_workingtree(self, revision_id=None):
877
"""See BzrDir.create_workingtree."""
878
from bzrlib.workingtree import WorkingTreeFormat
879
return WorkingTreeFormat.get_default_format().initialize(self, revision_id)
881
def _get_mkdir_mode(self):
882
"""Figure out the mode to use when creating a bzrdir subdir."""
883
temp_control = LockableFiles(self.transport, '', TransportLock)
884
return temp_control._dir_mode
886
def get_branch_transport(self, branch_format):
887
"""See BzrDir.get_branch_transport()."""
888
if branch_format is None:
889
return self.transport.clone('branch')
891
branch_format.get_format_string()
892
except NotImplementedError:
893
raise errors.IncompatibleFormat(branch_format, self._format)
895
self.transport.mkdir('branch', mode=self._get_mkdir_mode())
896
except errors.FileExists:
898
return self.transport.clone('branch')
900
def get_repository_transport(self, repository_format):
901
"""See BzrDir.get_repository_transport()."""
902
if repository_format is None:
903
return self.transport.clone('repository')
905
repository_format.get_format_string()
906
except NotImplementedError:
907
raise errors.IncompatibleFormat(repository_format, self._format)
909
self.transport.mkdir('repository', mode=self._get_mkdir_mode())
910
except errors.FileExists:
912
return self.transport.clone('repository')
914
def get_workingtree_transport(self, workingtree_format):
915
"""See BzrDir.get_workingtree_transport()."""
916
if workingtree_format is None:
917
return self.transport.clone('checkout')
919
workingtree_format.get_format_string()
920
except NotImplementedError:
921
raise errors.IncompatibleFormat(workingtree_format, self._format)
923
self.transport.mkdir('checkout', mode=self._get_mkdir_mode())
924
except errors.FileExists:
926
return self.transport.clone('checkout')
928
def needs_format_conversion(self, format=None):
929
"""See BzrDir.needs_format_conversion()."""
931
format = BzrDirFormat.get_default_format()
932
if not isinstance(self._format, format.__class__):
933
# it is not a meta dir format, conversion is needed.
935
# we might want to push this down to the repository?
937
if not isinstance(self.open_repository()._format,
938
format.repository_format.__class__):
939
# the repository needs an upgrade.
941
except errors.NoRepositoryPresent:
943
# currently there are no other possible conversions for meta1 formats.
946
def open_branch(self, unsupported=False):
947
"""See BzrDir.open_branch."""
948
from bzrlib.branch import BranchFormat
949
format = BranchFormat.find_format(self)
950
self._check_supported(format, unsupported)
951
return format.open(self, _found=True)
953
def open_repository(self, unsupported=False):
954
"""See BzrDir.open_repository."""
955
from bzrlib.repository import RepositoryFormat
956
format = RepositoryFormat.find_format(self)
957
self._check_supported(format, unsupported)
958
return format.open(self, _found=True)
960
def open_workingtree(self, unsupported=False):
961
"""See BzrDir.open_workingtree."""
962
from bzrlib.workingtree import WorkingTreeFormat
963
format = WorkingTreeFormat.find_format(self)
964
self._check_supported(format, unsupported)
965
return format.open(self, _found=True)
968
class BzrDirFormat(object):
969
"""An encapsulation of the initialization and open routines for a format.
971
Formats provide three things:
972
* An initialization routine,
976
Formats are placed in an dict by their format string for reference
977
during bzrdir opening. These should be subclasses of BzrDirFormat
980
Once a format is deprecated, just deprecate the initialize and open
981
methods on the format class. Do not deprecate the object, as the
982
object will be created every system load.
985
_default_format = None
986
"""The default format used for new .bzr dirs."""
989
"""The known formats."""
991
_control_formats = []
992
"""The registered control formats - .bzr, ....
994
This is a list of BzrDirFormat objects.
997
_lock_file_name = 'branch-lock'
999
# _lock_class must be set in subclasses to the lock type, typ.
1000
# TransportLock or LockDir
1003
def find_format(klass, transport):
1004
"""Return the format present at transport."""
1005
for format in klass._control_formats:
1007
return format.probe_transport(transport)
1008
except errors.NotBranchError:
1009
# this format does not find a control dir here.
1011
raise errors.NotBranchError(path=transport.base)
1014
def probe_transport(klass, transport):
1015
"""Return the .bzrdir style transport present at URL."""
1017
format_string = transport.get(".bzr/branch-format").read()
1018
except errors.NoSuchFile:
1019
raise errors.NotBranchError(path=transport.base)
1022
return klass._formats[format_string]
1024
raise errors.UnknownFormatError(format=format_string)
1027
def get_default_format(klass):
1028
"""Return the current default format."""
1029
return klass._default_format
1031
def get_format_string(self):
1032
"""Return the ASCII format string that identifies this format."""
1033
raise NotImplementedError(self.get_format_string)
1035
def get_format_description(self):
1036
"""Return the short description for this format."""
1037
raise NotImplementedError(self.get_format_description)
1039
def get_converter(self, format=None):
1040
"""Return the converter to use to convert bzrdirs needing converts.
1042
This returns a bzrlib.bzrdir.Converter object.
1044
This should return the best upgrader to step this format towards the
1045
current default format. In the case of plugins we can/should provide
1046
some means for them to extend the range of returnable converters.
1048
:param format: Optional format to override the default format of the
1051
raise NotImplementedError(self.get_converter)
1053
def initialize(self, url):
1054
"""Create a bzr control dir at this url and return an opened copy.
1056
Subclasses should typically override initialize_on_transport
1057
instead of this method.
1059
return self.initialize_on_transport(get_transport(url))
1061
def initialize_on_transport(self, transport):
1062
"""Initialize a new bzrdir in the base directory of a Transport."""
1063
# Since we don't have a .bzr directory, inherit the
1064
# mode from the root directory
1065
temp_control = LockableFiles(transport, '', TransportLock)
1066
temp_control._transport.mkdir('.bzr',
1067
# FIXME: RBC 20060121 don't peek under
1069
mode=temp_control._dir_mode)
1070
file_mode = temp_control._file_mode
1072
mutter('created control directory in ' + transport.base)
1073
control = transport.clone('.bzr')
1074
utf8_files = [('README',
1075
"This is a Bazaar-NG control directory.\n"
1076
"Do not change any files in this directory.\n"),
1077
('branch-format', self.get_format_string()),
1079
# NB: no need to escape relative paths that are url safe.
1080
control_files = LockableFiles(control, self._lock_file_name,
1082
control_files.create_lock()
1083
control_files.lock_write()
1085
for file, content in utf8_files:
1086
control_files.put_utf8(file, content)
1088
control_files.unlock()
1089
return self.open(transport, _found=True)
1091
def is_supported(self):
1092
"""Is this format supported?
1094
Supported formats must be initializable and openable.
1095
Unsupported formats may not support initialization or committing or
1096
some other features depending on the reason for not being supported.
1100
def same_model(self, target_format):
1101
return (self.repository_format.rich_root_data ==
1102
target_format.rich_root_data)
1105
def known_formats(klass):
1106
"""Return all the known formats.
1108
Concrete formats should override _known_formats.
1110
# There is double indirection here to make sure that control
1111
# formats used by more than one dir format will only be probed
1112
# once. This can otherwise be quite expensive for remote connections.
1114
for format in klass._control_formats:
1115
result.update(format._known_formats())
1119
def _known_formats(klass):
1120
"""Return the known format instances for this control format."""
1121
return set(klass._formats.values())
1123
def open(self, transport, _found=False):
1124
"""Return an instance of this format for the dir transport points at.
1126
_found is a private parameter, do not use it.
1129
assert isinstance(BzrDirFormat.find_format(transport),
1131
return self._open(transport)
1133
def _open(self, transport):
1134
"""Template method helper for opening BzrDirectories.
1136
This performs the actual open and any additional logic or parameter
1139
raise NotImplementedError(self._open)
1142
def register_format(klass, format):
1143
klass._formats[format.get_format_string()] = format
1146
def register_control_format(klass, format):
1147
"""Register a format that does not use '.bzrdir' for its control dir.
1149
TODO: This should be pulled up into a 'ControlDirFormat' base class
1150
which BzrDirFormat can inherit from, and renamed to register_format
1151
there. It has been done without that for now for simplicity of
1154
klass._control_formats.append(format)
1157
def set_default_format(klass, format):
1158
klass._default_format = format
1161
return self.get_format_string()[:-1]
1164
def unregister_format(klass, format):
1165
assert klass._formats[format.get_format_string()] is format
1166
del klass._formats[format.get_format_string()]
1169
def unregister_control_format(klass, format):
1170
klass._control_formats.remove(format)
1173
# register BzrDirFormat as a control format
1174
BzrDirFormat.register_control_format(BzrDirFormat)
1177
class BzrDirFormat4(BzrDirFormat):
1178
"""Bzr dir format 4.
1180
This format is a combined format for working tree, branch and repository.
1182
- Format 1 working trees [always]
1183
- Format 4 branches [always]
1184
- Format 4 repositories [always]
1186
This format is deprecated: it indexes texts using a text it which is
1187
removed in format 5; write support for this format has been removed.
1190
_lock_class = TransportLock
1192
def get_format_string(self):
1193
"""See BzrDirFormat.get_format_string()."""
1194
return "Bazaar-NG branch, format 0.0.4\n"
1196
def get_format_description(self):
1197
"""See BzrDirFormat.get_format_description()."""
1198
return "All-in-one format 4"
1200
def get_converter(self, format=None):
1201
"""See BzrDirFormat.get_converter()."""
1202
# there is one and only one upgrade path here.
1203
return ConvertBzrDir4To5()
1205
def initialize_on_transport(self, transport):
1206
"""Format 4 branches cannot be created."""
1207
raise errors.UninitializableFormat(self)
1209
def is_supported(self):
1210
"""Format 4 is not supported.
1212
It is not supported because the model changed from 4 to 5 and the
1213
conversion logic is expensive - so doing it on the fly was not
1218
def _open(self, transport):
1219
"""See BzrDirFormat._open."""
1220
return BzrDir4(transport, self)
1222
def __return_repository_format(self):
1223
"""Circular import protection."""
1224
from bzrlib.repository import RepositoryFormat4
1225
return RepositoryFormat4()
1226
repository_format = property(__return_repository_format)
1229
class BzrDirFormat5(BzrDirFormat):
1230
"""Bzr control format 5.
1232
This format is a combined format for working tree, branch and repository.
1234
- Format 2 working trees [always]
1235
- Format 4 branches [always]
1236
- Format 5 repositories [always]
1237
Unhashed stores in the repository.
1240
_lock_class = TransportLock
1242
def get_format_string(self):
1243
"""See BzrDirFormat.get_format_string()."""
1244
return "Bazaar-NG branch, format 5\n"
1246
def get_format_description(self):
1247
"""See BzrDirFormat.get_format_description()."""
1248
return "All-in-one format 5"
1250
def get_converter(self, format=None):
1251
"""See BzrDirFormat.get_converter()."""
1252
# there is one and only one upgrade path here.
1253
return ConvertBzrDir5To6()
1255
def _initialize_for_clone(self, url):
1256
return self.initialize_on_transport(get_transport(url), _cloning=True)
1258
def initialize_on_transport(self, transport, _cloning=False):
1259
"""Format 5 dirs always have working tree, branch and repository.
1261
Except when they are being cloned.
1263
from bzrlib.branch import BzrBranchFormat4
1264
from bzrlib.repository import RepositoryFormat5
1265
from bzrlib.workingtree import WorkingTreeFormat2
1266
result = (super(BzrDirFormat5, self).initialize_on_transport(transport))
1267
RepositoryFormat5().initialize(result, _internal=True)
1269
branch = BzrBranchFormat4().initialize(result)
1271
WorkingTreeFormat2().initialize(result)
1272
except errors.NotLocalUrl:
1273
# Even though we can't access the working tree, we need to
1274
# create its control files.
1275
WorkingTreeFormat2().stub_initialize_remote(branch.control_files)
1278
def _open(self, transport):
1279
"""See BzrDirFormat._open."""
1280
return BzrDir5(transport, self)
1282
def __return_repository_format(self):
1283
"""Circular import protection."""
1284
from bzrlib.repository import RepositoryFormat5
1285
return RepositoryFormat5()
1286
repository_format = property(__return_repository_format)
1289
class BzrDirFormat6(BzrDirFormat):
1290
"""Bzr control format 6.
1292
This format is a combined format for working tree, branch and repository.
1294
- Format 2 working trees [always]
1295
- Format 4 branches [always]
1296
- Format 6 repositories [always]
1299
_lock_class = TransportLock
1301
def get_format_string(self):
1302
"""See BzrDirFormat.get_format_string()."""
1303
return "Bazaar-NG branch, format 6\n"
1305
def get_format_description(self):
1306
"""See BzrDirFormat.get_format_description()."""
1307
return "All-in-one format 6"
1309
def get_converter(self, format=None):
1310
"""See BzrDirFormat.get_converter()."""
1311
# there is one and only one upgrade path here.
1312
return ConvertBzrDir6ToMeta()
1314
def _initialize_for_clone(self, url):
1315
return self.initialize_on_transport(get_transport(url), _cloning=True)
1317
def initialize_on_transport(self, transport, _cloning=False):
1318
"""Format 6 dirs always have working tree, branch and repository.
1320
Except when they are being cloned.
1322
from bzrlib.branch import BzrBranchFormat4
1323
from bzrlib.repository import RepositoryFormat6
1324
from bzrlib.workingtree import WorkingTreeFormat2
1325
result = super(BzrDirFormat6, self).initialize_on_transport(transport)
1326
RepositoryFormat6().initialize(result, _internal=True)
1328
branch = BzrBranchFormat4().initialize(result)
1330
WorkingTreeFormat2().initialize(result)
1331
except errors.NotLocalUrl:
1332
# Even though we can't access the working tree, we need to
1333
# create its control files.
1334
WorkingTreeFormat2().stub_initialize_remote(branch.control_files)
1337
def _open(self, transport):
1338
"""See BzrDirFormat._open."""
1339
return BzrDir6(transport, self)
1341
def __return_repository_format(self):
1342
"""Circular import protection."""
1343
from bzrlib.repository import RepositoryFormat6
1344
return RepositoryFormat6()
1345
repository_format = property(__return_repository_format)
1348
class BzrDirMetaFormat1(BzrDirFormat):
1349
"""Bzr meta control format 1
1351
This is the first format with split out working tree, branch and repository
1354
- Format 3 working trees [optional]
1355
- Format 5 branches [optional]
1356
- Format 7 repositories [optional]
1359
_lock_class = LockDir
1361
def get_converter(self, format=None):
1362
"""See BzrDirFormat.get_converter()."""
1364
format = BzrDirFormat.get_default_format()
1365
if not isinstance(self, format.__class__):
1366
# converting away from metadir is not implemented
1367
raise NotImplementedError(self.get_converter)
1368
return ConvertMetaToMeta(format)
1370
def get_format_string(self):
1371
"""See BzrDirFormat.get_format_string()."""
1372
return "Bazaar-NG meta directory, format 1\n"
1374
def get_format_description(self):
1375
"""See BzrDirFormat.get_format_description()."""
1376
return "Meta directory format 1"
1378
def _open(self, transport):
1379
"""See BzrDirFormat._open."""
1380
return BzrDirMeta1(transport, self)
1382
def __return_repository_format(self):
1383
"""Circular import protection."""
1384
if getattr(self, '_repository_format', None):
1385
return self._repository_format
1386
from bzrlib.repository import RepositoryFormat
1387
return RepositoryFormat.get_default_format()
1389
def __set_repository_format(self, value):
1390
"""Allow changint the repository format for metadir formats."""
1391
self._repository_format = value
1393
repository_format = property(__return_repository_format, __set_repository_format)
1396
BzrDirFormat.register_format(BzrDirFormat4())
1397
BzrDirFormat.register_format(BzrDirFormat5())
1398
BzrDirFormat.register_format(BzrDirFormat6())
1399
__default_format = BzrDirMetaFormat1()
1400
BzrDirFormat.register_format(__default_format)
1401
BzrDirFormat.set_default_format(__default_format)
1404
class BzrDirTestProviderAdapter(object):
1405
"""A tool to generate a suite testing multiple bzrdir formats at once.
1407
This is done by copying the test once for each transport and injecting
1408
the transport_server, transport_readonly_server, and bzrdir_format
1409
classes into each copy. Each copy is also given a new id() to make it
1413
def __init__(self, transport_server, transport_readonly_server, formats):
1414
self._transport_server = transport_server
1415
self._transport_readonly_server = transport_readonly_server
1416
self._formats = formats
1418
def adapt(self, test):
1419
result = TestSuite()
1420
for format in self._formats:
1421
new_test = deepcopy(test)
1422
new_test.transport_server = self._transport_server
1423
new_test.transport_readonly_server = self._transport_readonly_server
1424
new_test.bzrdir_format = format
1425
def make_new_test_id():
1426
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1427
return lambda: new_id
1428
new_test.id = make_new_test_id()
1429
result.addTest(new_test)
1433
class Converter(object):
1434
"""Converts a disk format object from one format to another."""
1436
def convert(self, to_convert, pb):
1437
"""Perform the conversion of to_convert, giving feedback via pb.
1439
:param to_convert: The disk object to convert.
1440
:param pb: a progress bar to use for progress information.
1443
def step(self, message):
1444
"""Update the pb by a step."""
1446
self.pb.update(message, self.count, self.total)
1449
class ConvertBzrDir4To5(Converter):
1450
"""Converts format 4 bzr dirs to format 5."""
1453
super(ConvertBzrDir4To5, self).__init__()
1454
self.converted_revs = set()
1455
self.absent_revisions = set()
1459
def convert(self, to_convert, pb):
1460
"""See Converter.convert()."""
1461
self.bzrdir = to_convert
1463
self.pb.note('starting upgrade from format 4 to 5')
1464
if isinstance(self.bzrdir.transport, LocalTransport):
1465
self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
1466
self._convert_to_weaves()
1467
return BzrDir.open(self.bzrdir.root_transport.base)
1469
def _convert_to_weaves(self):
1470
self.pb.note('note: upgrade may be faster if all store files are ungzipped first')
1473
stat = self.bzrdir.transport.stat('weaves')
1474
if not S_ISDIR(stat.st_mode):
1475
self.bzrdir.transport.delete('weaves')
1476
self.bzrdir.transport.mkdir('weaves')
1477
except errors.NoSuchFile:
1478
self.bzrdir.transport.mkdir('weaves')
1479
# deliberately not a WeaveFile as we want to build it up slowly.
1480
self.inv_weave = Weave('inventory')
1481
# holds in-memory weaves for all files
1482
self.text_weaves = {}
1483
self.bzrdir.transport.delete('branch-format')
1484
self.branch = self.bzrdir.open_branch()
1485
self._convert_working_inv()
1486
rev_history = self.branch.revision_history()
1487
# to_read is a stack holding the revisions we still need to process;
1488
# appending to it adds new highest-priority revisions
1489
self.known_revisions = set(rev_history)
1490
self.to_read = rev_history[-1:]
1492
rev_id = self.to_read.pop()
1493
if (rev_id not in self.revisions
1494
and rev_id not in self.absent_revisions):
1495
self._load_one_rev(rev_id)
1497
to_import = self._make_order()
1498
for i, rev_id in enumerate(to_import):
1499
self.pb.update('converting revision', i, len(to_import))
1500
self._convert_one_rev(rev_id)
1502
self._write_all_weaves()
1503
self._write_all_revs()
1504
self.pb.note('upgraded to weaves:')
1505
self.pb.note(' %6d revisions and inventories', len(self.revisions))
1506
self.pb.note(' %6d revisions not present', len(self.absent_revisions))
1507
self.pb.note(' %6d texts', self.text_count)
1508
self._cleanup_spare_files_after_format4()
1509
self.branch.control_files.put_utf8('branch-format', BzrDirFormat5().get_format_string())
1511
def _cleanup_spare_files_after_format4(self):
1512
# FIXME working tree upgrade foo.
1513
for n in 'merged-patches', 'pending-merged-patches':
1515
## assert os.path.getsize(p) == 0
1516
self.bzrdir.transport.delete(n)
1517
except errors.NoSuchFile:
1519
self.bzrdir.transport.delete_tree('inventory-store')
1520
self.bzrdir.transport.delete_tree('text-store')
1522
def _convert_working_inv(self):
1523
inv = serializer_v4.read_inventory(self.branch.control_files.get('inventory'))
1524
new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1525
# FIXME inventory is a working tree change.
1526
self.branch.control_files.put('inventory', StringIO(new_inv_xml))
1528
def _write_all_weaves(self):
1529
controlweaves = WeaveStore(self.bzrdir.transport, prefixed=False)
1530
weave_transport = self.bzrdir.transport.clone('weaves')
1531
weaves = WeaveStore(weave_transport, prefixed=False)
1532
transaction = WriteTransaction()
1536
for file_id, file_weave in self.text_weaves.items():
1537
self.pb.update('writing weave', i, len(self.text_weaves))
1538
weaves._put_weave(file_id, file_weave, transaction)
1540
self.pb.update('inventory', 0, 1)
1541
controlweaves._put_weave('inventory', self.inv_weave, transaction)
1542
self.pb.update('inventory', 1, 1)
1546
def _write_all_revs(self):
1547
"""Write all revisions out in new form."""
1548
self.bzrdir.transport.delete_tree('revision-store')
1549
self.bzrdir.transport.mkdir('revision-store')
1550
revision_transport = self.bzrdir.transport.clone('revision-store')
1552
_revision_store = TextRevisionStore(TextStore(revision_transport,
1556
transaction = bzrlib.transactions.WriteTransaction()
1557
for i, rev_id in enumerate(self.converted_revs):
1558
self.pb.update('write revision', i, len(self.converted_revs))
1559
_revision_store.add_revision(self.revisions[rev_id], transaction)
1563
def _load_one_rev(self, rev_id):
1564
"""Load a revision object into memory.
1566
Any parents not either loaded or abandoned get queued to be
1568
self.pb.update('loading revision',
1569
len(self.revisions),
1570
len(self.known_revisions))
1571
if not self.branch.repository.has_revision(rev_id):
1573
self.pb.note('revision {%s} not present in branch; '
1574
'will be converted as a ghost',
1576
self.absent_revisions.add(rev_id)
1578
rev = self.branch.repository._revision_store.get_revision(rev_id,
1579
self.branch.repository.get_transaction())
1580
for parent_id in rev.parent_ids:
1581
self.known_revisions.add(parent_id)
1582
self.to_read.append(parent_id)
1583
self.revisions[rev_id] = rev
1585
def _load_old_inventory(self, rev_id):
1586
assert rev_id not in self.converted_revs
1587
old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
1588
inv = serializer_v4.read_inventory_from_string(old_inv_xml)
1589
inv.revision_id = rev_id
1590
rev = self.revisions[rev_id]
1591
if rev.inventory_sha1:
1592
assert rev.inventory_sha1 == sha_string(old_inv_xml), \
1593
'inventory sha mismatch for {%s}' % rev_id
1596
def _load_updated_inventory(self, rev_id):
1597
assert rev_id in self.converted_revs
1598
inv_xml = self.inv_weave.get_text(rev_id)
1599
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(inv_xml)
1602
def _convert_one_rev(self, rev_id):
1603
"""Convert revision and all referenced objects to new format."""
1604
rev = self.revisions[rev_id]
1605
inv = self._load_old_inventory(rev_id)
1606
present_parents = [p for p in rev.parent_ids
1607
if p not in self.absent_revisions]
1608
self._convert_revision_contents(rev, inv, present_parents)
1609
self._store_new_weave(rev, inv, present_parents)
1610
self.converted_revs.add(rev_id)
1612
def _store_new_weave(self, rev, inv, present_parents):
1613
# the XML is now updated with text versions
1615
entries = inv.iter_entries()
1617
for path, ie in entries:
1618
assert getattr(ie, 'revision', None) is not None, \
1619
'no revision on {%s} in {%s}' % \
1620
(file_id, rev.revision_id)
1621
new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1622
new_inv_sha1 = sha_string(new_inv_xml)
1623
self.inv_weave.add_lines(rev.revision_id,
1625
new_inv_xml.splitlines(True))
1626
rev.inventory_sha1 = new_inv_sha1
1628
def _convert_revision_contents(self, rev, inv, present_parents):
1629
"""Convert all the files within a revision.
1631
Also upgrade the inventory to refer to the text revision ids."""
1632
rev_id = rev.revision_id
1633
mutter('converting texts of revision {%s}',
1635
parent_invs = map(self._load_updated_inventory, present_parents)
1636
entries = inv.iter_entries()
1638
for path, ie in entries:
1639
self._convert_file_version(rev, ie, parent_invs)
1641
def _convert_file_version(self, rev, ie, parent_invs):
1642
"""Convert one version of one file.
1644
The file needs to be added into the weave if it is a merge
1645
of >=2 parents or if it's changed from its parent.
1647
file_id = ie.file_id
1648
rev_id = rev.revision_id
1649
w = self.text_weaves.get(file_id)
1652
self.text_weaves[file_id] = w
1653
text_changed = False
1654
previous_entries = ie.find_previous_heads(parent_invs,
1658
for old_revision in previous_entries:
1659
# if this fails, its a ghost ?
1660
assert old_revision in self.converted_revs
1661
self.snapshot_ie(previous_entries, ie, w, rev_id)
1663
assert getattr(ie, 'revision', None) is not None
1665
def snapshot_ie(self, previous_revisions, ie, w, rev_id):
1666
# TODO: convert this logic, which is ~= snapshot to
1667
# a call to:. This needs the path figured out. rather than a work_tree
1668
# a v4 revision_tree can be given, or something that looks enough like
1669
# one to give the file content to the entry if it needs it.
1670
# and we need something that looks like a weave store for snapshot to
1672
#ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
1673
if len(previous_revisions) == 1:
1674
previous_ie = previous_revisions.values()[0]
1675
if ie._unchanged(previous_ie):
1676
ie.revision = previous_ie.revision
1679
text = self.branch.repository.text_store.get(ie.text_id)
1680
file_lines = text.readlines()
1681
assert sha_strings(file_lines) == ie.text_sha1
1682
assert sum(map(len, file_lines)) == ie.text_size
1683
w.add_lines(rev_id, previous_revisions, file_lines)
1684
self.text_count += 1
1686
w.add_lines(rev_id, previous_revisions, [])
1687
ie.revision = rev_id
1689
def _make_order(self):
1690
"""Return a suitable order for importing revisions.
1692
The order must be such that an revision is imported after all
1693
its (present) parents.
1695
todo = set(self.revisions.keys())
1696
done = self.absent_revisions.copy()
1699
# scan through looking for a revision whose parents
1701
for rev_id in sorted(list(todo)):
1702
rev = self.revisions[rev_id]
1703
parent_ids = set(rev.parent_ids)
1704
if parent_ids.issubset(done):
1705
# can take this one now
1706
order.append(rev_id)
1712
class ConvertBzrDir5To6(Converter):
1713
"""Converts format 5 bzr dirs to format 6."""
1715
def convert(self, to_convert, pb):
1716
"""See Converter.convert()."""
1717
self.bzrdir = to_convert
1719
self.pb.note('starting upgrade from format 5 to 6')
1720
self._convert_to_prefixed()
1721
return BzrDir.open(self.bzrdir.root_transport.base)
1723
def _convert_to_prefixed(self):
1724
from bzrlib.store import TransportStore
1725
self.bzrdir.transport.delete('branch-format')
1726
for store_name in ["weaves", "revision-store"]:
1727
self.pb.note("adding prefixes to %s" % store_name)
1728
store_transport = self.bzrdir.transport.clone(store_name)
1729
store = TransportStore(store_transport, prefixed=True)
1730
for urlfilename in store_transport.list_dir('.'):
1731
filename = urlutils.unescape(urlfilename)
1732
if (filename.endswith(".weave") or
1733
filename.endswith(".gz") or
1734
filename.endswith(".sig")):
1735
file_id = os.path.splitext(filename)[0]
1738
prefix_dir = store.hash_prefix(file_id)
1739
# FIXME keep track of the dirs made RBC 20060121
1741
store_transport.move(filename, prefix_dir + '/' + filename)
1742
except errors.NoSuchFile: # catches missing dirs strangely enough
1743
store_transport.mkdir(prefix_dir)
1744
store_transport.move(filename, prefix_dir + '/' + filename)
1745
self.bzrdir._control_files.put_utf8('branch-format', BzrDirFormat6().get_format_string())
1748
class ConvertBzrDir6ToMeta(Converter):
1749
"""Converts format 6 bzr dirs to metadirs."""
1751
def convert(self, to_convert, pb):
1752
"""See Converter.convert()."""
1753
self.bzrdir = to_convert
1756
self.total = 20 # the steps we know about
1757
self.garbage_inventories = []
1759
self.pb.note('starting upgrade from format 6 to metadir')
1760
self.bzrdir._control_files.put_utf8('branch-format', "Converting to format 6")
1761
# its faster to move specific files around than to open and use the apis...
1762
# first off, nuke ancestry.weave, it was never used.
1764
self.step('Removing ancestry.weave')
1765
self.bzrdir.transport.delete('ancestry.weave')
1766
except errors.NoSuchFile:
1768
# find out whats there
1769
self.step('Finding branch files')
1770
last_revision = self.bzrdir.open_branch().last_revision()
1771
bzrcontents = self.bzrdir.transport.list_dir('.')
1772
for name in bzrcontents:
1773
if name.startswith('basis-inventory.'):
1774
self.garbage_inventories.append(name)
1775
# create new directories for repository, working tree and branch
1776
self.dir_mode = self.bzrdir._control_files._dir_mode
1777
self.file_mode = self.bzrdir._control_files._file_mode
1778
repository_names = [('inventory.weave', True),
1779
('revision-store', True),
1781
self.step('Upgrading repository ')
1782
self.bzrdir.transport.mkdir('repository', mode=self.dir_mode)
1783
self.make_lock('repository')
1784
# we hard code the formats here because we are converting into
1785
# the meta format. The meta format upgrader can take this to a
1786
# future format within each component.
1787
self.put_format('repository', bzrlib.repository.RepositoryFormat7())
1788
for entry in repository_names:
1789
self.move_entry('repository', entry)
1791
self.step('Upgrading branch ')
1792
self.bzrdir.transport.mkdir('branch', mode=self.dir_mode)
1793
self.make_lock('branch')
1794
self.put_format('branch', bzrlib.branch.BzrBranchFormat5())
1795
branch_files = [('revision-history', True),
1796
('branch-name', True),
1798
for entry in branch_files:
1799
self.move_entry('branch', entry)
1801
checkout_files = [('pending-merges', True),
1802
('inventory', True),
1803
('stat-cache', False)]
1804
# If a mandatory checkout file is not present, the branch does not have
1805
# a functional checkout. Do not create a checkout in the converted
1807
for name, mandatory in checkout_files:
1808
if mandatory and name not in bzrcontents:
1809
has_checkout = False
1813
if not has_checkout:
1814
self.pb.note('No working tree.')
1815
# If some checkout files are there, we may as well get rid of them.
1816
for name, mandatory in checkout_files:
1817
if name in bzrcontents:
1818
self.bzrdir.transport.delete(name)
1820
self.step('Upgrading working tree')
1821
self.bzrdir.transport.mkdir('checkout', mode=self.dir_mode)
1822
self.make_lock('checkout')
1824
'checkout', bzrlib.workingtree.WorkingTreeFormat3())
1825
self.bzrdir.transport.delete_multi(
1826
self.garbage_inventories, self.pb)
1827
for entry in checkout_files:
1828
self.move_entry('checkout', entry)
1829
if last_revision is not None:
1830
self.bzrdir._control_files.put_utf8(
1831
'checkout/last-revision', last_revision)
1832
self.bzrdir._control_files.put_utf8(
1833
'branch-format', BzrDirMetaFormat1().get_format_string())
1834
return BzrDir.open(self.bzrdir.root_transport.base)
1836
def make_lock(self, name):
1837
"""Make a lock for the new control dir name."""
1838
self.step('Make %s lock' % name)
1839
ld = LockDir(self.bzrdir.transport,
1841
file_modebits=self.file_mode,
1842
dir_modebits=self.dir_mode)
1845
def move_entry(self, new_dir, entry):
1846
"""Move then entry name into new_dir."""
1848
mandatory = entry[1]
1849
self.step('Moving %s' % name)
1851
self.bzrdir.transport.move(name, '%s/%s' % (new_dir, name))
1852
except errors.NoSuchFile:
1856
def put_format(self, dirname, format):
1857
self.bzrdir._control_files.put_utf8('%s/format' % dirname, format.get_format_string())
1860
class ConvertMetaToMeta(Converter):
1861
"""Converts the components of metadirs."""
1863
def __init__(self, target_format):
1864
"""Create a metadir to metadir converter.
1866
:param target_format: The final metadir format that is desired.
1868
self.target_format = target_format
1870
def convert(self, to_convert, pb):
1871
"""See Converter.convert()."""
1872
self.bzrdir = to_convert
1876
self.step('checking repository format')
1878
repo = self.bzrdir.open_repository()
1879
except errors.NoRepositoryPresent:
1882
if not isinstance(repo._format, self.target_format.repository_format.__class__):
1883
from bzrlib.repository import CopyConverter
1884
self.pb.note('starting repository conversion')
1885
converter = CopyConverter(self.target_format.repository_format)
1886
converter.convert(repo, pb)