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
wt = result.create_workingtree()
669
if wt.inventory.root is None:
671
wt.set_root_id(self.open_workingtree.get_root_id())
672
except errors.NoWorkingTree:
677
class BzrDirPreSplitOut(BzrDir):
678
"""A common class for the all-in-one formats."""
680
def __init__(self, _transport, _format):
681
"""See BzrDir.__init__."""
682
super(BzrDirPreSplitOut, self).__init__(_transport, _format)
683
assert self._format._lock_class == TransportLock
684
assert self._format._lock_file_name == 'branch-lock'
685
self._control_files = LockableFiles(self.get_branch_transport(None),
686
self._format._lock_file_name,
687
self._format._lock_class)
689
def break_lock(self):
690
"""Pre-splitout bzrdirs do not suffer from stale locks."""
691
raise NotImplementedError(self.break_lock)
693
def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
694
"""See BzrDir.clone()."""
695
from bzrlib.workingtree import WorkingTreeFormat2
697
result = self._format._initialize_for_clone(url)
698
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
699
self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
700
from_branch = self.open_branch()
701
from_branch.clone(result, revision_id=revision_id)
703
self.open_workingtree().clone(result, basis=basis_tree)
704
except errors.NotLocalUrl:
705
# make a new one, this format always has to have one.
707
WorkingTreeFormat2().initialize(result)
708
except errors.NotLocalUrl:
709
# but we cannot do it for remote trees.
710
to_branch = result.open_branch()
711
WorkingTreeFormat2().stub_initialize_remote(to_branch.control_files)
714
def create_branch(self):
715
"""See BzrDir.create_branch."""
716
return self.open_branch()
718
def create_repository(self, shared=False):
719
"""See BzrDir.create_repository."""
721
raise errors.IncompatibleFormat('shared repository', self._format)
722
return self.open_repository()
724
def create_workingtree(self, revision_id=None):
725
"""See BzrDir.create_workingtree."""
726
# this looks buggy but is not -really-
727
# clone and sprout will have set the revision_id
728
# and that will have set it for us, its only
729
# specific uses of create_workingtree in isolation
730
# that can do wonky stuff here, and that only
731
# happens for creating checkouts, which cannot be
732
# done on this format anyway. So - acceptable wart.
733
result = self.open_workingtree()
734
if revision_id is not None:
735
if revision_id == bzrlib.revision.NULL_REVISION:
736
result.set_parent_ids([])
738
result.set_parent_ids([revision_id])
741
def get_branch_transport(self, branch_format):
742
"""See BzrDir.get_branch_transport()."""
743
if branch_format is None:
744
return self.transport
746
branch_format.get_format_string()
747
except NotImplementedError:
748
return self.transport
749
raise errors.IncompatibleFormat(branch_format, self._format)
751
def get_repository_transport(self, repository_format):
752
"""See BzrDir.get_repository_transport()."""
753
if repository_format is None:
754
return self.transport
756
repository_format.get_format_string()
757
except NotImplementedError:
758
return self.transport
759
raise errors.IncompatibleFormat(repository_format, self._format)
761
def get_workingtree_transport(self, workingtree_format):
762
"""See BzrDir.get_workingtree_transport()."""
763
if workingtree_format is None:
764
return self.transport
766
workingtree_format.get_format_string()
767
except NotImplementedError:
768
return self.transport
769
raise errors.IncompatibleFormat(workingtree_format, self._format)
771
def needs_format_conversion(self, format=None):
772
"""See BzrDir.needs_format_conversion()."""
773
# if the format is not the same as the system default,
774
# an upgrade is needed.
776
format = BzrDirFormat.get_default_format()
777
return not isinstance(self._format, format.__class__)
779
def open_branch(self, unsupported=False):
780
"""See BzrDir.open_branch."""
781
from bzrlib.branch import BzrBranchFormat4
782
format = BzrBranchFormat4()
783
self._check_supported(format, unsupported)
784
return format.open(self, _found=True)
786
def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
787
"""See BzrDir.sprout()."""
788
from bzrlib.workingtree import WorkingTreeFormat2
790
result = self._format._initialize_for_clone(url)
791
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
793
self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
794
except errors.NoRepositoryPresent:
797
self.open_branch().sprout(result, revision_id=revision_id)
798
except errors.NotBranchError:
800
# we always want a working tree
801
WorkingTreeFormat2().initialize(result)
805
class BzrDir4(BzrDirPreSplitOut):
806
"""A .bzr version 4 control object.
808
This is a deprecated format and may be removed after sept 2006.
811
def create_repository(self, shared=False):
812
"""See BzrDir.create_repository."""
813
return self._format.repository_format.initialize(self, shared)
815
def needs_format_conversion(self, format=None):
816
"""Format 4 dirs are always in need of conversion."""
819
def open_repository(self):
820
"""See BzrDir.open_repository."""
821
from bzrlib.repository import RepositoryFormat4
822
return RepositoryFormat4().open(self, _found=True)
825
class BzrDir5(BzrDirPreSplitOut):
826
"""A .bzr version 5 control object.
828
This is a deprecated format and may be removed after sept 2006.
831
def open_repository(self):
832
"""See BzrDir.open_repository."""
833
from bzrlib.repository import RepositoryFormat5
834
return RepositoryFormat5().open(self, _found=True)
836
def open_workingtree(self, _unsupported=False):
837
"""See BzrDir.create_workingtree."""
838
from bzrlib.workingtree import WorkingTreeFormat2
839
return WorkingTreeFormat2().open(self, _found=True)
842
class BzrDir6(BzrDirPreSplitOut):
843
"""A .bzr version 6 control object.
845
This is a deprecated format and may be removed after sept 2006.
848
def open_repository(self):
849
"""See BzrDir.open_repository."""
850
from bzrlib.repository import RepositoryFormat6
851
return RepositoryFormat6().open(self, _found=True)
853
def open_workingtree(self, _unsupported=False):
854
"""See BzrDir.create_workingtree."""
855
from bzrlib.workingtree import WorkingTreeFormat2
856
return WorkingTreeFormat2().open(self, _found=True)
859
class BzrDirMeta1(BzrDir):
860
"""A .bzr meta version 1 control object.
862
This is the first control object where the
863
individual aspects are really split out: there are separate repository,
864
workingtree and branch subdirectories and any subset of the three can be
865
present within a BzrDir.
868
def can_convert_format(self):
869
"""See BzrDir.can_convert_format()."""
872
def create_branch(self):
873
"""See BzrDir.create_branch."""
874
from bzrlib.branch import BranchFormat
875
return BranchFormat.get_default_format().initialize(self)
877
def create_repository(self, shared=False):
878
"""See BzrDir.create_repository."""
879
return self._format.repository_format.initialize(self, shared)
881
def create_workingtree(self, revision_id=None):
882
"""See BzrDir.create_workingtree."""
883
from bzrlib.workingtree import WorkingTreeFormat
884
return WorkingTreeFormat.get_default_format().initialize(self, revision_id)
886
def _get_mkdir_mode(self):
887
"""Figure out the mode to use when creating a bzrdir subdir."""
888
temp_control = LockableFiles(self.transport, '', TransportLock)
889
return temp_control._dir_mode
891
def get_branch_transport(self, branch_format):
892
"""See BzrDir.get_branch_transport()."""
893
if branch_format is None:
894
return self.transport.clone('branch')
896
branch_format.get_format_string()
897
except NotImplementedError:
898
raise errors.IncompatibleFormat(branch_format, self._format)
900
self.transport.mkdir('branch', mode=self._get_mkdir_mode())
901
except errors.FileExists:
903
return self.transport.clone('branch')
905
def get_repository_transport(self, repository_format):
906
"""See BzrDir.get_repository_transport()."""
907
if repository_format is None:
908
return self.transport.clone('repository')
910
repository_format.get_format_string()
911
except NotImplementedError:
912
raise errors.IncompatibleFormat(repository_format, self._format)
914
self.transport.mkdir('repository', mode=self._get_mkdir_mode())
915
except errors.FileExists:
917
return self.transport.clone('repository')
919
def get_workingtree_transport(self, workingtree_format):
920
"""See BzrDir.get_workingtree_transport()."""
921
if workingtree_format is None:
922
return self.transport.clone('checkout')
924
workingtree_format.get_format_string()
925
except NotImplementedError:
926
raise errors.IncompatibleFormat(workingtree_format, self._format)
928
self.transport.mkdir('checkout', mode=self._get_mkdir_mode())
929
except errors.FileExists:
931
return self.transport.clone('checkout')
933
def needs_format_conversion(self, format=None):
934
"""See BzrDir.needs_format_conversion()."""
936
format = BzrDirFormat.get_default_format()
937
if not isinstance(self._format, format.__class__):
938
# it is not a meta dir format, conversion is needed.
940
# we might want to push this down to the repository?
942
if not isinstance(self.open_repository()._format,
943
format.repository_format.__class__):
944
# the repository needs an upgrade.
946
except errors.NoRepositoryPresent:
948
# currently there are no other possible conversions for meta1 formats.
951
def open_branch(self, unsupported=False):
952
"""See BzrDir.open_branch."""
953
from bzrlib.branch import BranchFormat
954
format = BranchFormat.find_format(self)
955
self._check_supported(format, unsupported)
956
return format.open(self, _found=True)
958
def open_repository(self, unsupported=False):
959
"""See BzrDir.open_repository."""
960
from bzrlib.repository import RepositoryFormat
961
format = RepositoryFormat.find_format(self)
962
self._check_supported(format, unsupported)
963
return format.open(self, _found=True)
965
def open_workingtree(self, unsupported=False):
966
"""See BzrDir.open_workingtree."""
967
from bzrlib.workingtree import WorkingTreeFormat
968
format = WorkingTreeFormat.find_format(self)
969
self._check_supported(format, unsupported)
970
return format.open(self, _found=True)
973
class BzrDirFormat(object):
974
"""An encapsulation of the initialization and open routines for a format.
976
Formats provide three things:
977
* An initialization routine,
981
Formats are placed in an dict by their format string for reference
982
during bzrdir opening. These should be subclasses of BzrDirFormat
985
Once a format is deprecated, just deprecate the initialize and open
986
methods on the format class. Do not deprecate the object, as the
987
object will be created every system load.
990
_default_format = None
991
"""The default format used for new .bzr dirs."""
994
"""The known formats."""
996
_control_formats = []
997
"""The registered control formats - .bzr, ....
999
This is a list of BzrDirFormat objects.
1002
_lock_file_name = 'branch-lock'
1004
# _lock_class must be set in subclasses to the lock type, typ.
1005
# TransportLock or LockDir
1008
def find_format(klass, transport):
1009
"""Return the format present at transport."""
1010
for format in klass._control_formats:
1012
return format.probe_transport(transport)
1013
except errors.NotBranchError:
1014
# this format does not find a control dir here.
1016
raise errors.NotBranchError(path=transport.base)
1019
def probe_transport(klass, transport):
1020
"""Return the .bzrdir style transport present at URL."""
1022
format_string = transport.get(".bzr/branch-format").read()
1023
except errors.NoSuchFile:
1024
raise errors.NotBranchError(path=transport.base)
1027
return klass._formats[format_string]
1029
raise errors.UnknownFormatError(format=format_string)
1032
def get_default_format(klass):
1033
"""Return the current default format."""
1034
return klass._default_format
1036
def get_format_string(self):
1037
"""Return the ASCII format string that identifies this format."""
1038
raise NotImplementedError(self.get_format_string)
1040
def get_format_description(self):
1041
"""Return the short description for this format."""
1042
raise NotImplementedError(self.get_format_description)
1044
def get_converter(self, format=None):
1045
"""Return the converter to use to convert bzrdirs needing converts.
1047
This returns a bzrlib.bzrdir.Converter object.
1049
This should return the best upgrader to step this format towards the
1050
current default format. In the case of plugins we can/should provide
1051
some means for them to extend the range of returnable converters.
1053
:param format: Optional format to override the default format of the
1056
raise NotImplementedError(self.get_converter)
1058
def initialize(self, url):
1059
"""Create a bzr control dir at this url and return an opened copy.
1061
Subclasses should typically override initialize_on_transport
1062
instead of this method.
1064
return self.initialize_on_transport(get_transport(url))
1066
def initialize_on_transport(self, transport):
1067
"""Initialize a new bzrdir in the base directory of a Transport."""
1068
# Since we don't have a .bzr directory, inherit the
1069
# mode from the root directory
1070
temp_control = LockableFiles(transport, '', TransportLock)
1071
temp_control._transport.mkdir('.bzr',
1072
# FIXME: RBC 20060121 don't peek under
1074
mode=temp_control._dir_mode)
1075
file_mode = temp_control._file_mode
1077
mutter('created control directory in ' + transport.base)
1078
control = transport.clone('.bzr')
1079
utf8_files = [('README',
1080
"This is a Bazaar-NG control directory.\n"
1081
"Do not change any files in this directory.\n"),
1082
('branch-format', self.get_format_string()),
1084
# NB: no need to escape relative paths that are url safe.
1085
control_files = LockableFiles(control, self._lock_file_name,
1087
control_files.create_lock()
1088
control_files.lock_write()
1090
for file, content in utf8_files:
1091
control_files.put_utf8(file, content)
1093
control_files.unlock()
1094
return self.open(transport, _found=True)
1096
def is_supported(self):
1097
"""Is this format supported?
1099
Supported formats must be initializable and openable.
1100
Unsupported formats may not support initialization or committing or
1101
some other features depending on the reason for not being supported.
1105
def same_model(self, target_format):
1106
return (self.repository_format.rich_root_data ==
1107
target_format.rich_root_data)
1110
def known_formats(klass):
1111
"""Return all the known formats.
1113
Concrete formats should override _known_formats.
1115
# There is double indirection here to make sure that control
1116
# formats used by more than one dir format will only be probed
1117
# once. This can otherwise be quite expensive for remote connections.
1119
for format in klass._control_formats:
1120
result.update(format._known_formats())
1124
def _known_formats(klass):
1125
"""Return the known format instances for this control format."""
1126
return set(klass._formats.values())
1128
def open(self, transport, _found=False):
1129
"""Return an instance of this format for the dir transport points at.
1131
_found is a private parameter, do not use it.
1134
assert isinstance(BzrDirFormat.find_format(transport),
1136
return self._open(transport)
1138
def _open(self, transport):
1139
"""Template method helper for opening BzrDirectories.
1141
This performs the actual open and any additional logic or parameter
1144
raise NotImplementedError(self._open)
1147
def register_format(klass, format):
1148
klass._formats[format.get_format_string()] = format
1151
def register_control_format(klass, format):
1152
"""Register a format that does not use '.bzrdir' for its control dir.
1154
TODO: This should be pulled up into a 'ControlDirFormat' base class
1155
which BzrDirFormat can inherit from, and renamed to register_format
1156
there. It has been done without that for now for simplicity of
1159
klass._control_formats.append(format)
1162
def set_default_format(klass, format):
1163
klass._default_format = format
1166
return self.get_format_string()[:-1]
1169
def unregister_format(klass, format):
1170
assert klass._formats[format.get_format_string()] is format
1171
del klass._formats[format.get_format_string()]
1174
def unregister_control_format(klass, format):
1175
klass._control_formats.remove(format)
1178
# register BzrDirFormat as a control format
1179
BzrDirFormat.register_control_format(BzrDirFormat)
1182
class BzrDirFormat4(BzrDirFormat):
1183
"""Bzr dir format 4.
1185
This format is a combined format for working tree, branch and repository.
1187
- Format 1 working trees [always]
1188
- Format 4 branches [always]
1189
- Format 4 repositories [always]
1191
This format is deprecated: it indexes texts using a text it which is
1192
removed in format 5; write support for this format has been removed.
1195
_lock_class = TransportLock
1197
def get_format_string(self):
1198
"""See BzrDirFormat.get_format_string()."""
1199
return "Bazaar-NG branch, format 0.0.4\n"
1201
def get_format_description(self):
1202
"""See BzrDirFormat.get_format_description()."""
1203
return "All-in-one format 4"
1205
def get_converter(self, format=None):
1206
"""See BzrDirFormat.get_converter()."""
1207
# there is one and only one upgrade path here.
1208
return ConvertBzrDir4To5()
1210
def initialize_on_transport(self, transport):
1211
"""Format 4 branches cannot be created."""
1212
raise errors.UninitializableFormat(self)
1214
def is_supported(self):
1215
"""Format 4 is not supported.
1217
It is not supported because the model changed from 4 to 5 and the
1218
conversion logic is expensive - so doing it on the fly was not
1223
def _open(self, transport):
1224
"""See BzrDirFormat._open."""
1225
return BzrDir4(transport, self)
1227
def __return_repository_format(self):
1228
"""Circular import protection."""
1229
from bzrlib.repository import RepositoryFormat4
1230
return RepositoryFormat4()
1231
repository_format = property(__return_repository_format)
1234
class BzrDirFormat5(BzrDirFormat):
1235
"""Bzr control format 5.
1237
This format is a combined format for working tree, branch and repository.
1239
- Format 2 working trees [always]
1240
- Format 4 branches [always]
1241
- Format 5 repositories [always]
1242
Unhashed stores in the repository.
1245
_lock_class = TransportLock
1247
def get_format_string(self):
1248
"""See BzrDirFormat.get_format_string()."""
1249
return "Bazaar-NG branch, format 5\n"
1251
def get_format_description(self):
1252
"""See BzrDirFormat.get_format_description()."""
1253
return "All-in-one format 5"
1255
def get_converter(self, format=None):
1256
"""See BzrDirFormat.get_converter()."""
1257
# there is one and only one upgrade path here.
1258
return ConvertBzrDir5To6()
1260
def _initialize_for_clone(self, url):
1261
return self.initialize_on_transport(get_transport(url), _cloning=True)
1263
def initialize_on_transport(self, transport, _cloning=False):
1264
"""Format 5 dirs always have working tree, branch and repository.
1266
Except when they are being cloned.
1268
from bzrlib.branch import BzrBranchFormat4
1269
from bzrlib.repository import RepositoryFormat5
1270
from bzrlib.workingtree import WorkingTreeFormat2
1271
result = (super(BzrDirFormat5, self).initialize_on_transport(transport))
1272
RepositoryFormat5().initialize(result, _internal=True)
1274
branch = BzrBranchFormat4().initialize(result)
1276
WorkingTreeFormat2().initialize(result)
1277
except errors.NotLocalUrl:
1278
# Even though we can't access the working tree, we need to
1279
# create its control files.
1280
WorkingTreeFormat2().stub_initialize_remote(branch.control_files)
1283
def _open(self, transport):
1284
"""See BzrDirFormat._open."""
1285
return BzrDir5(transport, self)
1287
def __return_repository_format(self):
1288
"""Circular import protection."""
1289
from bzrlib.repository import RepositoryFormat5
1290
return RepositoryFormat5()
1291
repository_format = property(__return_repository_format)
1294
class BzrDirFormat6(BzrDirFormat):
1295
"""Bzr control format 6.
1297
This format is a combined format for working tree, branch and repository.
1299
- Format 2 working trees [always]
1300
- Format 4 branches [always]
1301
- Format 6 repositories [always]
1304
_lock_class = TransportLock
1306
def get_format_string(self):
1307
"""See BzrDirFormat.get_format_string()."""
1308
return "Bazaar-NG branch, format 6\n"
1310
def get_format_description(self):
1311
"""See BzrDirFormat.get_format_description()."""
1312
return "All-in-one format 6"
1314
def get_converter(self, format=None):
1315
"""See BzrDirFormat.get_converter()."""
1316
# there is one and only one upgrade path here.
1317
return ConvertBzrDir6ToMeta()
1319
def _initialize_for_clone(self, url):
1320
return self.initialize_on_transport(get_transport(url), _cloning=True)
1322
def initialize_on_transport(self, transport, _cloning=False):
1323
"""Format 6 dirs always have working tree, branch and repository.
1325
Except when they are being cloned.
1327
from bzrlib.branch import BzrBranchFormat4
1328
from bzrlib.repository import RepositoryFormat6
1329
from bzrlib.workingtree import WorkingTreeFormat2
1330
result = super(BzrDirFormat6, self).initialize_on_transport(transport)
1331
RepositoryFormat6().initialize(result, _internal=True)
1333
branch = BzrBranchFormat4().initialize(result)
1335
WorkingTreeFormat2().initialize(result)
1336
except errors.NotLocalUrl:
1337
# Even though we can't access the working tree, we need to
1338
# create its control files.
1339
WorkingTreeFormat2().stub_initialize_remote(branch.control_files)
1342
def _open(self, transport):
1343
"""See BzrDirFormat._open."""
1344
return BzrDir6(transport, self)
1346
def __return_repository_format(self):
1347
"""Circular import protection."""
1348
from bzrlib.repository import RepositoryFormat6
1349
return RepositoryFormat6()
1350
repository_format = property(__return_repository_format)
1353
class BzrDirMetaFormat1(BzrDirFormat):
1354
"""Bzr meta control format 1
1356
This is the first format with split out working tree, branch and repository
1359
- Format 3 working trees [optional]
1360
- Format 5 branches [optional]
1361
- Format 7 repositories [optional]
1364
_lock_class = LockDir
1366
def get_converter(self, format=None):
1367
"""See BzrDirFormat.get_converter()."""
1369
format = BzrDirFormat.get_default_format()
1370
if not isinstance(self, format.__class__):
1371
# converting away from metadir is not implemented
1372
raise NotImplementedError(self.get_converter)
1373
return ConvertMetaToMeta(format)
1375
def get_format_string(self):
1376
"""See BzrDirFormat.get_format_string()."""
1377
return "Bazaar-NG meta directory, format 1\n"
1379
def get_format_description(self):
1380
"""See BzrDirFormat.get_format_description()."""
1381
return "Meta directory format 1"
1383
def _open(self, transport):
1384
"""See BzrDirFormat._open."""
1385
return BzrDirMeta1(transport, self)
1387
def __return_repository_format(self):
1388
"""Circular import protection."""
1389
if getattr(self, '_repository_format', None):
1390
return self._repository_format
1391
from bzrlib.repository import RepositoryFormat
1392
return RepositoryFormat.get_default_format()
1394
def __set_repository_format(self, value):
1395
"""Allow changint the repository format for metadir formats."""
1396
self._repository_format = value
1398
repository_format = property(__return_repository_format, __set_repository_format)
1401
BzrDirFormat.register_format(BzrDirFormat4())
1402
BzrDirFormat.register_format(BzrDirFormat5())
1403
BzrDirFormat.register_format(BzrDirFormat6())
1404
__default_format = BzrDirMetaFormat1()
1405
BzrDirFormat.register_format(__default_format)
1406
BzrDirFormat.set_default_format(__default_format)
1409
class BzrDirTestProviderAdapter(object):
1410
"""A tool to generate a suite testing multiple bzrdir formats at once.
1412
This is done by copying the test once for each transport and injecting
1413
the transport_server, transport_readonly_server, and bzrdir_format
1414
classes into each copy. Each copy is also given a new id() to make it
1418
def __init__(self, transport_server, transport_readonly_server, formats):
1419
self._transport_server = transport_server
1420
self._transport_readonly_server = transport_readonly_server
1421
self._formats = formats
1423
def adapt(self, test):
1424
result = TestSuite()
1425
for format in self._formats:
1426
new_test = deepcopy(test)
1427
new_test.transport_server = self._transport_server
1428
new_test.transport_readonly_server = self._transport_readonly_server
1429
new_test.bzrdir_format = format
1430
def make_new_test_id():
1431
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1432
return lambda: new_id
1433
new_test.id = make_new_test_id()
1434
result.addTest(new_test)
1438
class Converter(object):
1439
"""Converts a disk format object from one format to another."""
1441
def convert(self, to_convert, pb):
1442
"""Perform the conversion of to_convert, giving feedback via pb.
1444
:param to_convert: The disk object to convert.
1445
:param pb: a progress bar to use for progress information.
1448
def step(self, message):
1449
"""Update the pb by a step."""
1451
self.pb.update(message, self.count, self.total)
1454
class ConvertBzrDir4To5(Converter):
1455
"""Converts format 4 bzr dirs to format 5."""
1458
super(ConvertBzrDir4To5, self).__init__()
1459
self.converted_revs = set()
1460
self.absent_revisions = set()
1464
def convert(self, to_convert, pb):
1465
"""See Converter.convert()."""
1466
self.bzrdir = to_convert
1468
self.pb.note('starting upgrade from format 4 to 5')
1469
if isinstance(self.bzrdir.transport, LocalTransport):
1470
self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
1471
self._convert_to_weaves()
1472
return BzrDir.open(self.bzrdir.root_transport.base)
1474
def _convert_to_weaves(self):
1475
self.pb.note('note: upgrade may be faster if all store files are ungzipped first')
1478
stat = self.bzrdir.transport.stat('weaves')
1479
if not S_ISDIR(stat.st_mode):
1480
self.bzrdir.transport.delete('weaves')
1481
self.bzrdir.transport.mkdir('weaves')
1482
except errors.NoSuchFile:
1483
self.bzrdir.transport.mkdir('weaves')
1484
# deliberately not a WeaveFile as we want to build it up slowly.
1485
self.inv_weave = Weave('inventory')
1486
# holds in-memory weaves for all files
1487
self.text_weaves = {}
1488
self.bzrdir.transport.delete('branch-format')
1489
self.branch = self.bzrdir.open_branch()
1490
self._convert_working_inv()
1491
rev_history = self.branch.revision_history()
1492
# to_read is a stack holding the revisions we still need to process;
1493
# appending to it adds new highest-priority revisions
1494
self.known_revisions = set(rev_history)
1495
self.to_read = rev_history[-1:]
1497
rev_id = self.to_read.pop()
1498
if (rev_id not in self.revisions
1499
and rev_id not in self.absent_revisions):
1500
self._load_one_rev(rev_id)
1502
to_import = self._make_order()
1503
for i, rev_id in enumerate(to_import):
1504
self.pb.update('converting revision', i, len(to_import))
1505
self._convert_one_rev(rev_id)
1507
self._write_all_weaves()
1508
self._write_all_revs()
1509
self.pb.note('upgraded to weaves:')
1510
self.pb.note(' %6d revisions and inventories', len(self.revisions))
1511
self.pb.note(' %6d revisions not present', len(self.absent_revisions))
1512
self.pb.note(' %6d texts', self.text_count)
1513
self._cleanup_spare_files_after_format4()
1514
self.branch.control_files.put_utf8('branch-format', BzrDirFormat5().get_format_string())
1516
def _cleanup_spare_files_after_format4(self):
1517
# FIXME working tree upgrade foo.
1518
for n in 'merged-patches', 'pending-merged-patches':
1520
## assert os.path.getsize(p) == 0
1521
self.bzrdir.transport.delete(n)
1522
except errors.NoSuchFile:
1524
self.bzrdir.transport.delete_tree('inventory-store')
1525
self.bzrdir.transport.delete_tree('text-store')
1527
def _convert_working_inv(self):
1528
inv = serializer_v4.read_inventory(self.branch.control_files.get('inventory'))
1529
new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1530
# FIXME inventory is a working tree change.
1531
self.branch.control_files.put('inventory', StringIO(new_inv_xml))
1533
def _write_all_weaves(self):
1534
controlweaves = WeaveStore(self.bzrdir.transport, prefixed=False)
1535
weave_transport = self.bzrdir.transport.clone('weaves')
1536
weaves = WeaveStore(weave_transport, prefixed=False)
1537
transaction = WriteTransaction()
1541
for file_id, file_weave in self.text_weaves.items():
1542
self.pb.update('writing weave', i, len(self.text_weaves))
1543
weaves._put_weave(file_id, file_weave, transaction)
1545
self.pb.update('inventory', 0, 1)
1546
controlweaves._put_weave('inventory', self.inv_weave, transaction)
1547
self.pb.update('inventory', 1, 1)
1551
def _write_all_revs(self):
1552
"""Write all revisions out in new form."""
1553
self.bzrdir.transport.delete_tree('revision-store')
1554
self.bzrdir.transport.mkdir('revision-store')
1555
revision_transport = self.bzrdir.transport.clone('revision-store')
1557
_revision_store = TextRevisionStore(TextStore(revision_transport,
1561
transaction = bzrlib.transactions.WriteTransaction()
1562
for i, rev_id in enumerate(self.converted_revs):
1563
self.pb.update('write revision', i, len(self.converted_revs))
1564
_revision_store.add_revision(self.revisions[rev_id], transaction)
1568
def _load_one_rev(self, rev_id):
1569
"""Load a revision object into memory.
1571
Any parents not either loaded or abandoned get queued to be
1573
self.pb.update('loading revision',
1574
len(self.revisions),
1575
len(self.known_revisions))
1576
if not self.branch.repository.has_revision(rev_id):
1578
self.pb.note('revision {%s} not present in branch; '
1579
'will be converted as a ghost',
1581
self.absent_revisions.add(rev_id)
1583
rev = self.branch.repository._revision_store.get_revision(rev_id,
1584
self.branch.repository.get_transaction())
1585
for parent_id in rev.parent_ids:
1586
self.known_revisions.add(parent_id)
1587
self.to_read.append(parent_id)
1588
self.revisions[rev_id] = rev
1590
def _load_old_inventory(self, rev_id):
1591
assert rev_id not in self.converted_revs
1592
old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
1593
inv = serializer_v4.read_inventory_from_string(old_inv_xml)
1594
inv.revision_id = rev_id
1595
rev = self.revisions[rev_id]
1596
if rev.inventory_sha1:
1597
assert rev.inventory_sha1 == sha_string(old_inv_xml), \
1598
'inventory sha mismatch for {%s}' % rev_id
1601
def _load_updated_inventory(self, rev_id):
1602
assert rev_id in self.converted_revs
1603
inv_xml = self.inv_weave.get_text(rev_id)
1604
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(inv_xml)
1607
def _convert_one_rev(self, rev_id):
1608
"""Convert revision and all referenced objects to new format."""
1609
rev = self.revisions[rev_id]
1610
inv = self._load_old_inventory(rev_id)
1611
present_parents = [p for p in rev.parent_ids
1612
if p not in self.absent_revisions]
1613
self._convert_revision_contents(rev, inv, present_parents)
1614
self._store_new_weave(rev, inv, present_parents)
1615
self.converted_revs.add(rev_id)
1617
def _store_new_weave(self, rev, inv, present_parents):
1618
# the XML is now updated with text versions
1620
entries = inv.iter_entries()
1622
for path, ie in entries:
1623
if inv.is_root(ie.file_id):
1625
assert getattr(ie, 'revision', None) is not None, \
1626
'no revision on {%s} in {%s}' % \
1627
(file_id, rev.revision_id)
1628
new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1629
new_inv_sha1 = sha_string(new_inv_xml)
1630
self.inv_weave.add_lines(rev.revision_id,
1632
new_inv_xml.splitlines(True))
1633
rev.inventory_sha1 = new_inv_sha1
1635
def _convert_revision_contents(self, rev, inv, present_parents):
1636
"""Convert all the files within a revision.
1638
Also upgrade the inventory to refer to the text revision ids."""
1639
rev_id = rev.revision_id
1640
mutter('converting texts of revision {%s}',
1642
parent_invs = map(self._load_updated_inventory, present_parents)
1644
if inv.is_root(file_id):
1647
self._convert_file_version(rev, ie, parent_invs)
1649
def _convert_file_version(self, rev, ie, parent_invs):
1650
"""Convert one version of one file.
1652
The file needs to be added into the weave if it is a merge
1653
of >=2 parents or if it's changed from its parent.
1655
file_id = ie.file_id
1656
rev_id = rev.revision_id
1657
w = self.text_weaves.get(file_id)
1660
self.text_weaves[file_id] = w
1661
text_changed = False
1662
previous_entries = ie.find_previous_heads(parent_invs,
1666
for old_revision in previous_entries:
1667
# if this fails, its a ghost ?
1668
assert old_revision in self.converted_revs, \
1669
"Revision {%s} not in converted_revs" % old_revision
1670
self.snapshot_ie(previous_entries, ie, w, rev_id)
1672
assert getattr(ie, 'revision', None) is not None
1674
def snapshot_ie(self, previous_revisions, ie, w, rev_id):
1675
# TODO: convert this logic, which is ~= snapshot to
1676
# a call to:. This needs the path figured out. rather than a work_tree
1677
# a v4 revision_tree can be given, or something that looks enough like
1678
# one to give the file content to the entry if it needs it.
1679
# and we need something that looks like a weave store for snapshot to
1681
#ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
1682
if len(previous_revisions) == 1:
1683
previous_ie = previous_revisions.values()[0]
1684
if ie._unchanged(previous_ie):
1685
ie.revision = previous_ie.revision
1688
text = self.branch.repository.text_store.get(ie.text_id)
1689
file_lines = text.readlines()
1690
assert sha_strings(file_lines) == ie.text_sha1
1691
assert sum(map(len, file_lines)) == ie.text_size
1692
w.add_lines(rev_id, previous_revisions, file_lines)
1693
self.text_count += 1
1695
w.add_lines(rev_id, previous_revisions, [])
1696
ie.revision = rev_id
1698
def _make_order(self):
1699
"""Return a suitable order for importing revisions.
1701
The order must be such that an revision is imported after all
1702
its (present) parents.
1704
todo = set(self.revisions.keys())
1705
done = self.absent_revisions.copy()
1708
# scan through looking for a revision whose parents
1710
for rev_id in sorted(list(todo)):
1711
rev = self.revisions[rev_id]
1712
parent_ids = set(rev.parent_ids)
1713
if parent_ids.issubset(done):
1714
# can take this one now
1715
order.append(rev_id)
1721
class ConvertBzrDir5To6(Converter):
1722
"""Converts format 5 bzr dirs to format 6."""
1724
def convert(self, to_convert, pb):
1725
"""See Converter.convert()."""
1726
self.bzrdir = to_convert
1728
self.pb.note('starting upgrade from format 5 to 6')
1729
self._convert_to_prefixed()
1730
return BzrDir.open(self.bzrdir.root_transport.base)
1732
def _convert_to_prefixed(self):
1733
from bzrlib.store import TransportStore
1734
self.bzrdir.transport.delete('branch-format')
1735
for store_name in ["weaves", "revision-store"]:
1736
self.pb.note("adding prefixes to %s" % store_name)
1737
store_transport = self.bzrdir.transport.clone(store_name)
1738
store = TransportStore(store_transport, prefixed=True)
1739
for urlfilename in store_transport.list_dir('.'):
1740
filename = urlutils.unescape(urlfilename)
1741
if (filename.endswith(".weave") or
1742
filename.endswith(".gz") or
1743
filename.endswith(".sig")):
1744
file_id = os.path.splitext(filename)[0]
1747
prefix_dir = store.hash_prefix(file_id)
1748
# FIXME keep track of the dirs made RBC 20060121
1750
store_transport.move(filename, prefix_dir + '/' + filename)
1751
except errors.NoSuchFile: # catches missing dirs strangely enough
1752
store_transport.mkdir(prefix_dir)
1753
store_transport.move(filename, prefix_dir + '/' + filename)
1754
self.bzrdir._control_files.put_utf8('branch-format', BzrDirFormat6().get_format_string())
1757
class ConvertBzrDir6ToMeta(Converter):
1758
"""Converts format 6 bzr dirs to metadirs."""
1760
def convert(self, to_convert, pb):
1761
"""See Converter.convert()."""
1762
self.bzrdir = to_convert
1765
self.total = 20 # the steps we know about
1766
self.garbage_inventories = []
1768
self.pb.note('starting upgrade from format 6 to metadir')
1769
self.bzrdir._control_files.put_utf8('branch-format', "Converting to format 6")
1770
# its faster to move specific files around than to open and use the apis...
1771
# first off, nuke ancestry.weave, it was never used.
1773
self.step('Removing ancestry.weave')
1774
self.bzrdir.transport.delete('ancestry.weave')
1775
except errors.NoSuchFile:
1777
# find out whats there
1778
self.step('Finding branch files')
1779
last_revision = self.bzrdir.open_branch().last_revision()
1780
bzrcontents = self.bzrdir.transport.list_dir('.')
1781
for name in bzrcontents:
1782
if name.startswith('basis-inventory.'):
1783
self.garbage_inventories.append(name)
1784
# create new directories for repository, working tree and branch
1785
self.dir_mode = self.bzrdir._control_files._dir_mode
1786
self.file_mode = self.bzrdir._control_files._file_mode
1787
repository_names = [('inventory.weave', True),
1788
('revision-store', True),
1790
self.step('Upgrading repository ')
1791
self.bzrdir.transport.mkdir('repository', mode=self.dir_mode)
1792
self.make_lock('repository')
1793
# we hard code the formats here because we are converting into
1794
# the meta format. The meta format upgrader can take this to a
1795
# future format within each component.
1796
self.put_format('repository', bzrlib.repository.RepositoryFormat7())
1797
for entry in repository_names:
1798
self.move_entry('repository', entry)
1800
self.step('Upgrading branch ')
1801
self.bzrdir.transport.mkdir('branch', mode=self.dir_mode)
1802
self.make_lock('branch')
1803
self.put_format('branch', bzrlib.branch.BzrBranchFormat5())
1804
branch_files = [('revision-history', True),
1805
('branch-name', True),
1807
for entry in branch_files:
1808
self.move_entry('branch', entry)
1810
checkout_files = [('pending-merges', True),
1811
('inventory', True),
1812
('stat-cache', False)]
1813
# If a mandatory checkout file is not present, the branch does not have
1814
# a functional checkout. Do not create a checkout in the converted
1816
for name, mandatory in checkout_files:
1817
if mandatory and name not in bzrcontents:
1818
has_checkout = False
1822
if not has_checkout:
1823
self.pb.note('No working tree.')
1824
# If some checkout files are there, we may as well get rid of them.
1825
for name, mandatory in checkout_files:
1826
if name in bzrcontents:
1827
self.bzrdir.transport.delete(name)
1829
self.step('Upgrading working tree')
1830
self.bzrdir.transport.mkdir('checkout', mode=self.dir_mode)
1831
self.make_lock('checkout')
1833
'checkout', bzrlib.workingtree.WorkingTreeFormat3())
1834
self.bzrdir.transport.delete_multi(
1835
self.garbage_inventories, self.pb)
1836
for entry in checkout_files:
1837
self.move_entry('checkout', entry)
1838
if last_revision is not None:
1839
self.bzrdir._control_files.put_utf8(
1840
'checkout/last-revision', last_revision)
1841
self.bzrdir._control_files.put_utf8(
1842
'branch-format', BzrDirMetaFormat1().get_format_string())
1843
return BzrDir.open(self.bzrdir.root_transport.base)
1845
def make_lock(self, name):
1846
"""Make a lock for the new control dir name."""
1847
self.step('Make %s lock' % name)
1848
ld = LockDir(self.bzrdir.transport,
1850
file_modebits=self.file_mode,
1851
dir_modebits=self.dir_mode)
1854
def move_entry(self, new_dir, entry):
1855
"""Move then entry name into new_dir."""
1857
mandatory = entry[1]
1858
self.step('Moving %s' % name)
1860
self.bzrdir.transport.move(name, '%s/%s' % (new_dir, name))
1861
except errors.NoSuchFile:
1865
def put_format(self, dirname, format):
1866
self.bzrdir._control_files.put_utf8('%s/format' % dirname, format.get_format_string())
1869
class ConvertMetaToMeta(Converter):
1870
"""Converts the components of metadirs."""
1872
def __init__(self, target_format):
1873
"""Create a metadir to metadir converter.
1875
:param target_format: The final metadir format that is desired.
1877
self.target_format = target_format
1879
def convert(self, to_convert, pb):
1880
"""See Converter.convert()."""
1881
self.bzrdir = to_convert
1885
self.step('checking repository format')
1887
repo = self.bzrdir.open_repository()
1888
except errors.NoRepositoryPresent:
1891
if not isinstance(repo._format, self.target_format.repository_format.__class__):
1892
from bzrlib.repository import CopyConverter
1893
self.pb.note('starting repository conversion')
1894
converter = CopyConverter(self.target_format.repository_format)
1895
converter.convert(repo, pb)