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
from copy import deepcopy
25
from cStringIO import StringIO
26
from unittest import TestSuite
29
import bzrlib.errors as errors
30
from bzrlib.lockable_files import LockableFiles, TransportLock
31
from bzrlib.lockdir import LockDir
32
from bzrlib.osutils import (
39
from bzrlib.store.revision.text import TextRevisionStore
40
from bzrlib.store.text import TextStore
41
from bzrlib.store.versioned import WeaveStore
42
from bzrlib.symbol_versioning import *
43
from bzrlib.trace import mutter
44
from bzrlib.transactions import WriteTransaction
45
from bzrlib.transport import get_transport
46
from bzrlib.transport.local import LocalTransport
47
import bzrlib.urlutils as urlutils
48
from bzrlib.weave import Weave
49
from bzrlib.xml4 import serializer_v4
54
"""A .bzr control diretory.
56
BzrDir instances let you create or open any of the things that can be
57
found within .bzr - checkouts, branches and repositories.
60
the transport which this bzr dir is rooted at (i.e. file:///.../.bzr/)
62
a transport connected to the directory this bzr was opened from.
66
"""Invoke break_lock on the first object in the bzrdir.
68
If there is a tree, the tree is opened and break_lock() called.
69
Otherwise, branch is tried, and finally repository.
72
thing_to_unlock = self.open_workingtree()
73
except (errors.NotLocalUrl, errors.NoWorkingTree):
75
thing_to_unlock = self.open_branch()
76
except errors.NotBranchError:
78
thing_to_unlock = self.open_repository()
79
except errors.NoRepositoryPresent:
81
thing_to_unlock.break_lock()
83
def can_convert_format(self):
84
"""Return true if this bzrdir is one whose format we can convert from."""
88
def _check_supported(format, allow_unsupported):
89
"""Check whether format is a supported format.
91
If allow_unsupported is True, this is a no-op.
93
if not allow_unsupported and not format.is_supported():
94
# see open_downlevel to open legacy branches.
95
raise errors.UnsupportedFormatError(format=format)
97
def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
98
"""Clone this bzrdir and its contents to url verbatim.
100
If urls last component does not exist, it will be created.
102
if revision_id is not None, then the clone operation may tune
103
itself to download less data.
104
:param force_new_repo: Do not use a shared repository for the target
105
even if one is available.
108
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
109
result = self._format.initialize(url)
111
local_repo = self.find_repository()
112
except errors.NoRepositoryPresent:
115
# may need to copy content in
117
result_repo = local_repo.clone(
119
revision_id=revision_id,
121
result_repo.set_make_working_trees(local_repo.make_working_trees())
124
result_repo = result.find_repository()
125
# fetch content this dir needs.
127
# XXX FIXME RBC 20060214 need tests for this when the basis
129
result_repo.fetch(basis_repo, revision_id=revision_id)
130
result_repo.fetch(local_repo, revision_id=revision_id)
131
except errors.NoRepositoryPresent:
132
# needed to make one anyway.
133
result_repo = local_repo.clone(
135
revision_id=revision_id,
137
result_repo.set_make_working_trees(local_repo.make_working_trees())
138
# 1 if there is a branch present
139
# make sure its content is available in the target repository
142
self.open_branch().clone(result, revision_id=revision_id)
143
except errors.NotBranchError:
146
self.open_workingtree().clone(result, basis=basis_tree)
147
except (errors.NoWorkingTree, errors.NotLocalUrl):
151
def _get_basis_components(self, basis):
152
"""Retrieve the basis components that are available at basis."""
154
return None, None, None
156
basis_tree = basis.open_workingtree()
157
basis_branch = basis_tree.branch
158
basis_repo = basis_branch.repository
159
except (errors.NoWorkingTree, errors.NotLocalUrl):
162
basis_branch = basis.open_branch()
163
basis_repo = basis_branch.repository
164
except errors.NotBranchError:
167
basis_repo = basis.open_repository()
168
except errors.NoRepositoryPresent:
170
return basis_repo, basis_branch, basis_tree
172
# TODO: This should be given a Transport, and should chdir up; otherwise
173
# this will open a new connection.
174
def _make_tail(self, url):
175
head, tail = urlutils.split(url)
176
if tail and tail != '.':
177
t = bzrlib.transport.get_transport(head)
180
except errors.FileExists:
183
# TODO: Should take a Transport
185
def create(cls, base):
186
"""Create a new BzrDir at the url 'base'.
188
This will call the current default formats initialize with base
189
as the only parameter.
191
If you need a specific format, consider creating an instance
192
of that and calling initialize().
194
if cls is not BzrDir:
195
raise AssertionError("BzrDir.create always creates the default format, "
196
"not one of %r" % cls)
197
head, tail = urlutils.split(base)
198
if tail and tail != '.':
199
t = bzrlib.transport.get_transport(head)
202
except errors.FileExists:
204
return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
206
def create_branch(self):
207
"""Create a branch in this BzrDir.
209
The bzrdirs format will control what branch format is created.
210
For more control see BranchFormatXX.create(a_bzrdir).
212
raise NotImplementedError(self.create_branch)
215
def create_branch_and_repo(base, force_new_repo=False):
216
"""Create a new BzrDir, Branch and Repository at the url 'base'.
218
This will use the current default BzrDirFormat, and use whatever
219
repository format that that uses via bzrdir.create_branch and
220
create_repository. If a shared repository is available that is used
223
The created Branch object is returned.
225
:param base: The URL to create the branch at.
226
:param force_new_repo: If True a new repository is always created.
228
bzrdir = BzrDir.create(base)
229
bzrdir._find_or_create_repository(force_new_repo)
230
return bzrdir.create_branch()
232
def _find_or_create_repository(self, force_new_repo):
233
"""Create a new repository if needed, returning the repository."""
235
return self.create_repository()
237
return self.find_repository()
238
except errors.NoRepositoryPresent:
239
return self.create_repository()
242
def create_branch_convenience(base, force_new_repo=False,
243
force_new_tree=None, format=None):
244
"""Create a new BzrDir, Branch and Repository at the url 'base'.
246
This is a convenience function - it will use an existing repository
247
if possible, can be told explicitly whether to create a working tree or
250
This will use the current default BzrDirFormat, and use whatever
251
repository format that that uses via bzrdir.create_branch and
252
create_repository. If a shared repository is available that is used
253
preferentially. Whatever repository is used, its tree creation policy
256
The created Branch object is returned.
257
If a working tree cannot be made due to base not being a file:// url,
258
no error is raised unless force_new_tree is True, in which case no
259
data is created on disk and NotLocalUrl is raised.
261
:param base: The URL to create the branch at.
262
:param force_new_repo: If True a new repository is always created.
263
:param force_new_tree: If True or False force creation of a tree or
264
prevent such creation respectively.
265
:param format: Override for the for the bzrdir format to create
268
# check for non local urls
269
t = get_transport(safe_unicode(base))
270
if not isinstance(t, LocalTransport):
271
raise errors.NotLocalUrl(base)
273
bzrdir = BzrDir.create(base)
275
bzrdir = format.initialize(base)
276
repo = bzrdir._find_or_create_repository(force_new_repo)
277
result = bzrdir.create_branch()
278
if force_new_tree or (repo.make_working_trees() and
279
force_new_tree is None):
281
bzrdir.create_workingtree()
282
except errors.NotLocalUrl:
287
def create_repository(base, shared=False):
288
"""Create a new BzrDir and Repository at the url 'base'.
290
This will use the current default BzrDirFormat, and use whatever
291
repository format that that uses for bzrdirformat.create_repository.
293
;param shared: Create a shared repository rather than a standalone
295
The Repository object is returned.
297
This must be overridden as an instance method in child classes, where
298
it should take no parameters and construct whatever repository format
299
that child class desires.
301
bzrdir = BzrDir.create(base)
302
return bzrdir.create_repository()
305
def create_standalone_workingtree(base):
306
"""Create a new BzrDir, WorkingTree, Branch and Repository at 'base'.
308
'base' must be a local path or a file:// url.
310
This will use the current default BzrDirFormat, and use whatever
311
repository format that that uses for bzrdirformat.create_workingtree,
312
create_branch and create_repository.
314
The WorkingTree object is returned.
316
t = get_transport(safe_unicode(base))
317
if not isinstance(t, LocalTransport):
318
raise errors.NotLocalUrl(base)
319
bzrdir = BzrDir.create_branch_and_repo(safe_unicode(base),
320
force_new_repo=True).bzrdir
321
return bzrdir.create_workingtree()
323
def create_workingtree(self, revision_id=None):
324
"""Create a working tree at this BzrDir.
326
revision_id: create it as of this revision id.
328
raise NotImplementedError(self.create_workingtree)
330
def find_repository(self):
331
"""Find the repository that should be used for a_bzrdir.
333
This does not require a branch as we use it to find the repo for
334
new branches as well as to hook existing branches up to their
338
return self.open_repository()
339
except errors.NoRepositoryPresent:
341
next_transport = self.root_transport.clone('..')
343
# find the next containing bzrdir
345
found_bzrdir = BzrDir.open_containing_from_transport(
347
except errors.NotBranchError:
349
raise errors.NoRepositoryPresent(self)
350
# does it have a repository ?
352
repository = found_bzrdir.open_repository()
353
except errors.NoRepositoryPresent:
354
next_transport = found_bzrdir.root_transport.clone('..')
355
if (found_bzrdir.root_transport.base == next_transport.base):
356
# top of the file system
360
if ((found_bzrdir.root_transport.base ==
361
self.root_transport.base) or repository.is_shared()):
364
raise errors.NoRepositoryPresent(self)
365
raise errors.NoRepositoryPresent(self)
367
def get_branch_transport(self, branch_format):
368
"""Get the transport for use by branch format in this BzrDir.
370
Note that bzr dirs that do not support format strings will raise
371
IncompatibleFormat if the branch format they are given has
372
a format string, and vice versa.
374
If branch_format is None, the transport is returned with no
375
checking. if it is not None, then the returned transport is
376
guaranteed to point to an existing directory ready for use.
378
raise NotImplementedError(self.get_branch_transport)
380
def get_repository_transport(self, repository_format):
381
"""Get the transport for use by repository format in this BzrDir.
383
Note that bzr dirs that do not support format strings will raise
384
IncompatibleFormat if the repository format they are given has
385
a format string, and vice versa.
387
If repository_format is None, the transport is returned with no
388
checking. if it is not None, then the returned transport is
389
guaranteed to point to an existing directory ready for use.
391
raise NotImplementedError(self.get_repository_transport)
393
def get_workingtree_transport(self, tree_format):
394
"""Get the transport for use by workingtree format in this BzrDir.
396
Note that bzr dirs that do not support format strings will raise
397
IncompatibleFormat if the workingtree format they are given has
398
a format string, and vice versa.
400
If workingtree_format is None, the transport is returned with no
401
checking. if it is not None, then the returned transport is
402
guaranteed to point to an existing directory ready for use.
404
raise NotImplementedError(self.get_workingtree_transport)
406
def __init__(self, _transport, _format):
407
"""Initialize a Bzr control dir object.
409
Only really common logic should reside here, concrete classes should be
410
made with varying behaviours.
412
:param _format: the format that is creating this BzrDir instance.
413
:param _transport: the transport this dir is based at.
415
self._format = _format
416
self.transport = _transport.clone('.bzr')
417
self.root_transport = _transport
419
def is_control_filename(self, filename):
420
"""True if filename is the name of a path which is reserved for bzrdir's.
422
:param filename: A filename within the root transport of this bzrdir.
424
This is true IF and ONLY IF the filename is part of the namespace reserved
425
for bzr control dirs. Currently this is the '.bzr' directory in the root
426
of the root_transport. it is expected that plugins will need to extend
427
this in the future - for instance to make bzr talk with svn working
430
# this might be better on the BzrDirFormat class because it refers to
431
# all the possible bzrdir disk formats.
432
# This method is tested via the workingtree is_control_filename tests-
433
# it was extracted from WorkingTree.is_control_filename. If the methods
434
# contract is extended beyond the current trivial implementation please
435
# add new tests for it to the appropriate place.
436
return filename == '.bzr' or filename.startswith('.bzr/')
438
def needs_format_conversion(self, format=None):
439
"""Return true if this bzrdir needs convert_format run on it.
441
For instance, if the repository format is out of date but the
442
branch and working tree are not, this should return True.
444
:param format: Optional parameter indicating a specific desired
445
format we plan to arrive at.
447
raise NotImplementedError(self.needs_format_conversion)
450
def open_unsupported(base):
451
"""Open a branch which is not supported."""
452
return BzrDir.open(base, _unsupported=True)
455
def open(base, _unsupported=False):
456
"""Open an existing bzrdir, rooted at 'base' (url)
458
_unsupported is a private parameter to the BzrDir class.
460
t = get_transport(base)
461
mutter("trying to open %r with transport %r", base, t)
462
format = BzrDirFormat.find_format(t)
463
BzrDir._check_supported(format, _unsupported)
464
return format.open(t, _found=True)
466
def open_branch(self, unsupported=False):
467
"""Open the branch object at this BzrDir if one is present.
469
If unsupported is True, then no longer supported branch formats can
472
TODO: static convenience version of this?
474
raise NotImplementedError(self.open_branch)
477
def open_containing(url):
478
"""Open an existing branch which contains url.
480
:param url: url to search from.
481
See open_containing_from_transport for more detail.
483
return BzrDir.open_containing_from_transport(get_transport(url))
486
def open_containing_from_transport(a_transport):
487
"""Open an existing branch which contains a_transport.base
489
This probes for a branch at a_transport, and searches upwards from there.
491
Basically we keep looking up until we find the control directory or
492
run into the root. If there isn't one, raises NotBranchError.
493
If there is one and it is either an unrecognised format or an unsupported
494
format, UnknownFormatError or UnsupportedFormatError are raised.
495
If there is one, it is returned, along with the unused portion of url.
497
:return: The BzrDir that contains the path, and a Unicode path
498
for the rest of the URL.
500
# this gets the normalised url back. I.e. '.' -> the full path.
501
url = a_transport.base
504
format = BzrDirFormat.find_format(a_transport)
505
BzrDir._check_supported(format, False)
506
return format.open(a_transport), urlutils.unescape(a_transport.relpath(url))
507
except errors.NotBranchError, e:
508
## mutter('not a branch in: %r %s', a_transport.base, e)
510
new_t = a_transport.clone('..')
511
if new_t.base == a_transport.base:
512
# reached the root, whatever that may be
513
raise errors.NotBranchError(path=url)
516
def open_repository(self, _unsupported=False):
517
"""Open the repository object at this BzrDir if one is present.
519
This will not follow the Branch object pointer - its strictly a direct
520
open facility. Most client code should use open_branch().repository to
523
_unsupported is a private parameter, not part of the api.
524
TODO: static convenience version of this?
526
raise NotImplementedError(self.open_repository)
528
def open_workingtree(self, _unsupported=False):
529
"""Open the workingtree object at this BzrDir if one is present.
531
TODO: static convenience version of this?
533
raise NotImplementedError(self.open_workingtree)
535
def has_branch(self):
536
"""Tell if this bzrdir contains a branch.
538
Note: if you're going to open the branch, you should just go ahead
539
and try, and not ask permission first. (This method just opens the
540
branch and discards it, and that's somewhat expensive.)
545
except errors.NotBranchError:
548
def has_workingtree(self):
549
"""Tell if this bzrdir contains a working tree.
551
This will still raise an exception if the bzrdir has a workingtree that
552
is remote & inaccessible.
554
Note: if you're going to open the working tree, you should just go ahead
555
and try, and not ask permission first. (This method just opens the
556
workingtree and discards it, and that's somewhat expensive.)
559
self.open_workingtree()
561
except errors.NoWorkingTree:
564
def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
565
"""Create a copy of this bzrdir prepared for use as a new line of
568
If urls last component does not exist, it will be created.
570
Attributes related to the identity of the source branch like
571
branch nickname will be cleaned, a working tree is created
572
whether one existed before or not; and a local branch is always
575
if revision_id is not None, then the clone operation may tune
576
itself to download less data.
579
result = self._format.initialize(url)
580
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
582
source_branch = self.open_branch()
583
source_repository = source_branch.repository
584
except errors.NotBranchError:
587
source_repository = self.open_repository()
588
except errors.NoRepositoryPresent:
589
# copy the entire basis one if there is one
590
# but there is no repository.
591
source_repository = basis_repo
596
result_repo = result.find_repository()
597
except errors.NoRepositoryPresent:
599
if source_repository is None and result_repo is not None:
601
elif source_repository is None and result_repo is None:
602
# no repo available, make a new one
603
result.create_repository()
604
elif source_repository is not None and result_repo is None:
605
# have source, and want to make a new target repo
606
# we don't clone the repo because that preserves attributes
607
# like is_shared(), and we have not yet implemented a
608
# repository sprout().
609
result_repo = result.create_repository()
610
if result_repo is not None:
611
# fetch needed content into target.
613
# XXX FIXME RBC 20060214 need tests for this when the basis
615
result_repo.fetch(basis_repo, revision_id=revision_id)
616
result_repo.fetch(source_repository, revision_id=revision_id)
617
if source_branch is not None:
618
source_branch.sprout(result, revision_id=revision_id)
620
result.create_branch()
621
# TODO: jam 20060426 we probably need a test in here in the
622
# case that the newly sprouted branch is a remote one
623
if result_repo is None or result_repo.make_working_trees():
624
result.create_workingtree()
628
class BzrDirPreSplitOut(BzrDir):
629
"""A common class for the all-in-one formats."""
631
def __init__(self, _transport, _format):
632
"""See BzrDir.__init__."""
633
super(BzrDirPreSplitOut, self).__init__(_transport, _format)
634
assert self._format._lock_class == TransportLock
635
assert self._format._lock_file_name == 'branch-lock'
636
self._control_files = LockableFiles(self.get_branch_transport(None),
637
self._format._lock_file_name,
638
self._format._lock_class)
640
def break_lock(self):
641
"""Pre-splitout bzrdirs do not suffer from stale locks."""
642
raise NotImplementedError(self.break_lock)
644
def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
645
"""See BzrDir.clone()."""
646
from bzrlib.workingtree import WorkingTreeFormat2
648
result = self._format._initialize_for_clone(url)
649
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
650
self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
651
from_branch = self.open_branch()
652
from_branch.clone(result, revision_id=revision_id)
654
self.open_workingtree().clone(result, basis=basis_tree)
655
except errors.NotLocalUrl:
656
# make a new one, this format always has to have one.
658
WorkingTreeFormat2().initialize(result)
659
except errors.NotLocalUrl:
660
# but we cannot do it for remote trees.
661
to_branch = result.open_branch()
662
WorkingTreeFormat2().stub_initialize_remote(to_branch.control_files)
665
def create_branch(self):
666
"""See BzrDir.create_branch."""
667
return self.open_branch()
669
def create_repository(self, shared=False):
670
"""See BzrDir.create_repository."""
672
raise errors.IncompatibleFormat('shared repository', self._format)
673
return self.open_repository()
675
def create_workingtree(self, revision_id=None):
676
"""See BzrDir.create_workingtree."""
677
# this looks buggy but is not -really-
678
# clone and sprout will have set the revision_id
679
# and that will have set it for us, its only
680
# specific uses of create_workingtree in isolation
681
# that can do wonky stuff here, and that only
682
# happens for creating checkouts, which cannot be
683
# done on this format anyway. So - acceptable wart.
684
result = self.open_workingtree()
685
if revision_id is not None:
686
result.set_last_revision(revision_id)
689
def get_branch_transport(self, branch_format):
690
"""See BzrDir.get_branch_transport()."""
691
if branch_format is None:
692
return self.transport
694
branch_format.get_format_string()
695
except NotImplementedError:
696
return self.transport
697
raise errors.IncompatibleFormat(branch_format, self._format)
699
def get_repository_transport(self, repository_format):
700
"""See BzrDir.get_repository_transport()."""
701
if repository_format is None:
702
return self.transport
704
repository_format.get_format_string()
705
except NotImplementedError:
706
return self.transport
707
raise errors.IncompatibleFormat(repository_format, self._format)
709
def get_workingtree_transport(self, workingtree_format):
710
"""See BzrDir.get_workingtree_transport()."""
711
if workingtree_format is None:
712
return self.transport
714
workingtree_format.get_format_string()
715
except NotImplementedError:
716
return self.transport
717
raise errors.IncompatibleFormat(workingtree_format, self._format)
719
def needs_format_conversion(self, format=None):
720
"""See BzrDir.needs_format_conversion()."""
721
# if the format is not the same as the system default,
722
# an upgrade is needed.
724
format = BzrDirFormat.get_default_format()
725
return not isinstance(self._format, format.__class__)
727
def open_branch(self, unsupported=False):
728
"""See BzrDir.open_branch."""
729
from bzrlib.branch import BzrBranchFormat4
730
format = BzrBranchFormat4()
731
self._check_supported(format, unsupported)
732
return format.open(self, _found=True)
734
def sprout(self, url, revision_id=None, basis=None):
735
"""See BzrDir.sprout()."""
736
from bzrlib.workingtree import WorkingTreeFormat2
738
result = self._format._initialize_for_clone(url)
739
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
741
self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
742
except errors.NoRepositoryPresent:
745
self.open_branch().sprout(result, revision_id=revision_id)
746
except errors.NotBranchError:
748
# we always want a working tree
749
WorkingTreeFormat2().initialize(result)
753
class BzrDir4(BzrDirPreSplitOut):
754
"""A .bzr version 4 control object.
756
This is a deprecated format and may be removed after sept 2006.
759
def create_repository(self, shared=False):
760
"""See BzrDir.create_repository."""
761
return self._format.repository_format.initialize(self, shared)
763
def needs_format_conversion(self, format=None):
764
"""Format 4 dirs are always in need of conversion."""
767
def open_repository(self):
768
"""See BzrDir.open_repository."""
769
from bzrlib.repository import RepositoryFormat4
770
return RepositoryFormat4().open(self, _found=True)
773
class BzrDir5(BzrDirPreSplitOut):
774
"""A .bzr version 5 control object.
776
This is a deprecated format and may be removed after sept 2006.
779
def open_repository(self):
780
"""See BzrDir.open_repository."""
781
from bzrlib.repository import RepositoryFormat5
782
return RepositoryFormat5().open(self, _found=True)
784
def open_workingtree(self, _unsupported=False):
785
"""See BzrDir.create_workingtree."""
786
from bzrlib.workingtree import WorkingTreeFormat2
787
return WorkingTreeFormat2().open(self, _found=True)
790
class BzrDir6(BzrDirPreSplitOut):
791
"""A .bzr version 6 control object.
793
This is a deprecated format and may be removed after sept 2006.
796
def open_repository(self):
797
"""See BzrDir.open_repository."""
798
from bzrlib.repository import RepositoryFormat6
799
return RepositoryFormat6().open(self, _found=True)
801
def open_workingtree(self, _unsupported=False):
802
"""See BzrDir.create_workingtree."""
803
from bzrlib.workingtree import WorkingTreeFormat2
804
return WorkingTreeFormat2().open(self, _found=True)
807
class BzrDirMeta1(BzrDir):
808
"""A .bzr meta version 1 control object.
810
This is the first control object where the
811
individual aspects are really split out: there are separate repository,
812
workingtree and branch subdirectories and any subset of the three can be
813
present within a BzrDir.
816
def can_convert_format(self):
817
"""See BzrDir.can_convert_format()."""
820
def create_branch(self):
821
"""See BzrDir.create_branch."""
822
from bzrlib.branch import BranchFormat
823
return BranchFormat.get_default_format().initialize(self)
825
def create_repository(self, shared=False):
826
"""See BzrDir.create_repository."""
827
return self._format.repository_format.initialize(self, shared)
829
def create_workingtree(self, revision_id=None):
830
"""See BzrDir.create_workingtree."""
831
from bzrlib.workingtree import WorkingTreeFormat
832
return WorkingTreeFormat.get_default_format().initialize(self, revision_id)
834
def _get_mkdir_mode(self):
835
"""Figure out the mode to use when creating a bzrdir subdir."""
836
temp_control = LockableFiles(self.transport, '', TransportLock)
837
return temp_control._dir_mode
839
def get_branch_transport(self, branch_format):
840
"""See BzrDir.get_branch_transport()."""
841
if branch_format is None:
842
return self.transport.clone('branch')
844
branch_format.get_format_string()
845
except NotImplementedError:
846
raise errors.IncompatibleFormat(branch_format, self._format)
848
self.transport.mkdir('branch', mode=self._get_mkdir_mode())
849
except errors.FileExists:
851
return self.transport.clone('branch')
853
def get_repository_transport(self, repository_format):
854
"""See BzrDir.get_repository_transport()."""
855
if repository_format is None:
856
return self.transport.clone('repository')
858
repository_format.get_format_string()
859
except NotImplementedError:
860
raise errors.IncompatibleFormat(repository_format, self._format)
862
self.transport.mkdir('repository', mode=self._get_mkdir_mode())
863
except errors.FileExists:
865
return self.transport.clone('repository')
867
def get_workingtree_transport(self, workingtree_format):
868
"""See BzrDir.get_workingtree_transport()."""
869
if workingtree_format is None:
870
return self.transport.clone('checkout')
872
workingtree_format.get_format_string()
873
except NotImplementedError:
874
raise errors.IncompatibleFormat(workingtree_format, self._format)
876
self.transport.mkdir('checkout', mode=self._get_mkdir_mode())
877
except errors.FileExists:
879
return self.transport.clone('checkout')
881
def needs_format_conversion(self, format=None):
882
"""See BzrDir.needs_format_conversion()."""
884
format = BzrDirFormat.get_default_format()
885
if not isinstance(self._format, format.__class__):
886
# it is not a meta dir format, conversion is needed.
888
# we might want to push this down to the repository?
890
if not isinstance(self.open_repository()._format,
891
format.repository_format.__class__):
892
# the repository needs an upgrade.
894
except errors.NoRepositoryPresent:
896
# currently there are no other possible conversions for meta1 formats.
899
def open_branch(self, unsupported=False):
900
"""See BzrDir.open_branch."""
901
from bzrlib.branch import BranchFormat
902
format = BranchFormat.find_format(self)
903
self._check_supported(format, unsupported)
904
return format.open(self, _found=True)
906
def open_repository(self, unsupported=False):
907
"""See BzrDir.open_repository."""
908
from bzrlib.repository import RepositoryFormat
909
format = RepositoryFormat.find_format(self)
910
self._check_supported(format, unsupported)
911
return format.open(self, _found=True)
913
def open_workingtree(self, unsupported=False):
914
"""See BzrDir.open_workingtree."""
915
from bzrlib.workingtree import WorkingTreeFormat
916
format = WorkingTreeFormat.find_format(self)
917
self._check_supported(format, unsupported)
918
return format.open(self, _found=True)
921
class BzrDirFormat(object):
922
"""An encapsulation of the initialization and open routines for a format.
924
Formats provide three things:
925
* An initialization routine,
929
Formats are placed in an dict by their format string for reference
930
during bzrdir opening. These should be subclasses of BzrDirFormat
933
Once a format is deprecated, just deprecate the initialize and open
934
methods on the format class. Do not deprecate the object, as the
935
object will be created every system load.
938
_default_format = None
939
"""The default format used for new .bzr dirs."""
942
"""The known formats."""
944
_control_formats = []
945
"""The registered control formats - .bzr, ....
947
This is a list of BzrDirFormat objects.
950
_lock_file_name = 'branch-lock'
952
# _lock_class must be set in subclasses to the lock type, typ.
953
# TransportLock or LockDir
956
def find_format(klass, transport):
957
"""Return the format present at transport."""
958
for format in klass._control_formats:
960
return format.probe_transport(transport)
961
except errors.NotBranchError:
962
# this format does not find a control dir here.
964
raise errors.NotBranchError(path=transport.base)
967
def probe_transport(klass, transport):
968
"""Return the .bzrdir style transport present at URL."""
970
format_string = transport.get(".bzr/branch-format").read()
971
except errors.NoSuchFile:
972
raise errors.NotBranchError(path=transport.base)
975
return klass._formats[format_string]
977
raise errors.UnknownFormatError(format=format_string)
980
def get_default_format(klass):
981
"""Return the current default format."""
982
return klass._default_format
984
def get_format_string(self):
985
"""Return the ASCII format string that identifies this format."""
986
raise NotImplementedError(self.get_format_string)
988
def get_format_description(self):
989
"""Return the short description for this format."""
990
raise NotImplementedError(self.get_format_description)
992
def get_converter(self, format=None):
993
"""Return the converter to use to convert bzrdirs needing converts.
995
This returns a bzrlib.bzrdir.Converter object.
997
This should return the best upgrader to step this format towards the
998
current default format. In the case of plugins we can/should provide
999
some means for them to extend the range of returnable converters.
1001
:param format: Optional format to override the default format of the
1004
raise NotImplementedError(self.get_converter)
1006
def initialize(self, url):
1007
"""Create a bzr control dir at this url and return an opened copy.
1009
Subclasses should typically override initialize_on_transport
1010
instead of this method.
1012
return self.initialize_on_transport(get_transport(url))
1014
def initialize_on_transport(self, transport):
1015
"""Initialize a new bzrdir in the base directory of a Transport."""
1016
# Since we don't have a .bzr directory, inherit the
1017
# mode from the root directory
1018
temp_control = LockableFiles(transport, '', TransportLock)
1019
temp_control._transport.mkdir('.bzr',
1020
# FIXME: RBC 20060121 don't peek under
1022
mode=temp_control._dir_mode)
1023
file_mode = temp_control._file_mode
1025
mutter('created control directory in ' + transport.base)
1026
control = transport.clone('.bzr')
1027
utf8_files = [('README',
1028
"This is a Bazaar-NG control directory.\n"
1029
"Do not change any files in this directory.\n"),
1030
('branch-format', self.get_format_string()),
1032
# NB: no need to escape relative paths that are url safe.
1033
control_files = LockableFiles(control, self._lock_file_name,
1035
control_files.create_lock()
1036
control_files.lock_write()
1038
for file, content in utf8_files:
1039
control_files.put_utf8(file, content)
1041
control_files.unlock()
1042
return self.open(transport, _found=True)
1044
def is_supported(self):
1045
"""Is this format supported?
1047
Supported formats must be initializable and openable.
1048
Unsupported formats may not support initialization or committing or
1049
some other features depending on the reason for not being supported.
1054
def known_formats(klass):
1055
"""Return all the known formats.
1057
Concrete formats should override _known_formats.
1059
# There is double indirection here to make sure that control
1060
# formats used by more than one dir format will only be probed
1061
# once. This can otherwise be quite expensive for remote connections.
1063
for format in klass._control_formats:
1064
result.update(format._known_formats())
1068
def _known_formats(klass):
1069
"""Return the known format instances for this control format."""
1070
return set(klass._formats.values())
1072
def open(self, transport, _found=False):
1073
"""Return an instance of this format for the dir transport points at.
1075
_found is a private parameter, do not use it.
1078
assert isinstance(BzrDirFormat.find_format(transport),
1080
return self._open(transport)
1082
def _open(self, transport):
1083
"""Template method helper for opening BzrDirectories.
1085
This performs the actual open and any additional logic or parameter
1088
raise NotImplementedError(self._open)
1091
def register_format(klass, format):
1092
klass._formats[format.get_format_string()] = format
1095
def register_control_format(klass, format):
1096
"""Register a format that does not use '.bzrdir' for its control dir.
1098
TODO: This should be pulled up into a 'ControlDirFormat' base class
1099
which BzrDirFormat can inherit from, and renamed to register_format
1100
there. It has been done without that for now for simplicity of
1103
klass._control_formats.append(format)
1106
def set_default_format(klass, format):
1107
klass._default_format = format
1110
return self.get_format_string()[:-1]
1113
def unregister_format(klass, format):
1114
assert klass._formats[format.get_format_string()] is format
1115
del klass._formats[format.get_format_string()]
1118
def unregister_control_format(klass, format):
1119
klass._control_formats.remove(format)
1122
# register BzrDirFormat as a control format
1123
BzrDirFormat.register_control_format(BzrDirFormat)
1126
class BzrDirFormat4(BzrDirFormat):
1127
"""Bzr dir format 4.
1129
This format is a combined format for working tree, branch and repository.
1131
- Format 1 working trees [always]
1132
- Format 4 branches [always]
1133
- Format 4 repositories [always]
1135
This format is deprecated: it indexes texts using a text it which is
1136
removed in format 5; write support for this format has been removed.
1139
_lock_class = TransportLock
1141
def get_format_string(self):
1142
"""See BzrDirFormat.get_format_string()."""
1143
return "Bazaar-NG branch, format 0.0.4\n"
1145
def get_format_description(self):
1146
"""See BzrDirFormat.get_format_description()."""
1147
return "All-in-one format 4"
1149
def get_converter(self, format=None):
1150
"""See BzrDirFormat.get_converter()."""
1151
# there is one and only one upgrade path here.
1152
return ConvertBzrDir4To5()
1154
def initialize_on_transport(self, transport):
1155
"""Format 4 branches cannot be created."""
1156
raise errors.UninitializableFormat(self)
1158
def is_supported(self):
1159
"""Format 4 is not supported.
1161
It is not supported because the model changed from 4 to 5 and the
1162
conversion logic is expensive - so doing it on the fly was not
1167
def _open(self, transport):
1168
"""See BzrDirFormat._open."""
1169
return BzrDir4(transport, self)
1171
def __return_repository_format(self):
1172
"""Circular import protection."""
1173
from bzrlib.repository import RepositoryFormat4
1174
return RepositoryFormat4(self)
1175
repository_format = property(__return_repository_format)
1178
class BzrDirFormat5(BzrDirFormat):
1179
"""Bzr control format 5.
1181
This format is a combined format for working tree, branch and repository.
1183
- Format 2 working trees [always]
1184
- Format 4 branches [always]
1185
- Format 5 repositories [always]
1186
Unhashed stores in the repository.
1189
_lock_class = TransportLock
1191
def get_format_string(self):
1192
"""See BzrDirFormat.get_format_string()."""
1193
return "Bazaar-NG branch, format 5\n"
1195
def get_format_description(self):
1196
"""See BzrDirFormat.get_format_description()."""
1197
return "All-in-one format 5"
1199
def get_converter(self, format=None):
1200
"""See BzrDirFormat.get_converter()."""
1201
# there is one and only one upgrade path here.
1202
return ConvertBzrDir5To6()
1204
def _initialize_for_clone(self, url):
1205
return self.initialize_on_transport(get_transport(url), _cloning=True)
1207
def initialize_on_transport(self, transport, _cloning=False):
1208
"""Format 5 dirs always have working tree, branch and repository.
1210
Except when they are being cloned.
1212
from bzrlib.branch import BzrBranchFormat4
1213
from bzrlib.repository import RepositoryFormat5
1214
from bzrlib.workingtree import WorkingTreeFormat2
1215
result = (super(BzrDirFormat5, self).initialize_on_transport(transport))
1216
RepositoryFormat5().initialize(result, _internal=True)
1218
BzrBranchFormat4().initialize(result)
1219
WorkingTreeFormat2().initialize(result)
1222
def _open(self, transport):
1223
"""See BzrDirFormat._open."""
1224
return BzrDir5(transport, self)
1226
def __return_repository_format(self):
1227
"""Circular import protection."""
1228
from bzrlib.repository import RepositoryFormat5
1229
return RepositoryFormat5(self)
1230
repository_format = property(__return_repository_format)
1233
class BzrDirFormat6(BzrDirFormat):
1234
"""Bzr control format 6.
1236
This format is a combined format for working tree, branch and repository.
1238
- Format 2 working trees [always]
1239
- Format 4 branches [always]
1240
- Format 6 repositories [always]
1243
_lock_class = TransportLock
1245
def get_format_string(self):
1246
"""See BzrDirFormat.get_format_string()."""
1247
return "Bazaar-NG branch, format 6\n"
1249
def get_format_description(self):
1250
"""See BzrDirFormat.get_format_description()."""
1251
return "All-in-one format 6"
1253
def get_converter(self, format=None):
1254
"""See BzrDirFormat.get_converter()."""
1255
# there is one and only one upgrade path here.
1256
return ConvertBzrDir6ToMeta()
1258
def _initialize_for_clone(self, url):
1259
return self.initialize_on_transport(get_transport(url), _cloning=True)
1261
def initialize_on_transport(self, transport, _cloning=False):
1262
"""Format 6 dirs always have working tree, branch and repository.
1264
Except when they are being cloned.
1266
from bzrlib.branch import BzrBranchFormat4
1267
from bzrlib.repository import RepositoryFormat6
1268
from bzrlib.workingtree import WorkingTreeFormat2
1269
result = super(BzrDirFormat6, self).initialize_on_transport(transport)
1270
RepositoryFormat6().initialize(result, _internal=True)
1272
BzrBranchFormat4().initialize(result)
1274
WorkingTreeFormat2().initialize(result)
1275
except errors.NotLocalUrl:
1276
# emulate pre-check behaviour for working tree and silently
1281
def _open(self, transport):
1282
"""See BzrDirFormat._open."""
1283
return BzrDir6(transport, self)
1285
def __return_repository_format(self):
1286
"""Circular import protection."""
1287
from bzrlib.repository import RepositoryFormat6
1288
return RepositoryFormat6(self)
1289
repository_format = property(__return_repository_format)
1292
class BzrDirMetaFormat1(BzrDirFormat):
1293
"""Bzr meta control format 1
1295
This is the first format with split out working tree, branch and repository
1298
- Format 3 working trees [optional]
1299
- Format 5 branches [optional]
1300
- Format 7 repositories [optional]
1303
_lock_class = LockDir
1305
def get_converter(self, format=None):
1306
"""See BzrDirFormat.get_converter()."""
1308
format = BzrDirFormat.get_default_format()
1309
if not isinstance(self, format.__class__):
1310
# converting away from metadir is not implemented
1311
raise NotImplementedError(self.get_converter)
1312
return ConvertMetaToMeta(format)
1314
def get_format_string(self):
1315
"""See BzrDirFormat.get_format_string()."""
1316
return "Bazaar-NG meta directory, format 1\n"
1318
def get_format_description(self):
1319
"""See BzrDirFormat.get_format_description()."""
1320
return "Meta directory format 1"
1322
def _open(self, transport):
1323
"""See BzrDirFormat._open."""
1324
return BzrDirMeta1(transport, self)
1326
def __return_repository_format(self):
1327
"""Circular import protection."""
1328
if getattr(self, '_repository_format', None):
1329
return self._repository_format
1330
from bzrlib.repository import RepositoryFormat
1331
return RepositoryFormat.get_default_format()
1333
def __set_repository_format(self, value):
1334
"""Allow changint the repository format for metadir formats."""
1335
self._repository_format = value
1337
repository_format = property(__return_repository_format, __set_repository_format)
1340
BzrDirFormat.register_format(BzrDirFormat4())
1341
BzrDirFormat.register_format(BzrDirFormat5())
1342
BzrDirFormat.register_format(BzrDirFormat6())
1343
__default_format = BzrDirMetaFormat1()
1344
BzrDirFormat.register_format(__default_format)
1345
BzrDirFormat.set_default_format(__default_format)
1348
class BzrDirTestProviderAdapter(object):
1349
"""A tool to generate a suite testing multiple bzrdir formats at once.
1351
This is done by copying the test once for each transport and injecting
1352
the transport_server, transport_readonly_server, and bzrdir_format
1353
classes into each copy. Each copy is also given a new id() to make it
1357
def __init__(self, transport_server, transport_readonly_server, formats):
1358
self._transport_server = transport_server
1359
self._transport_readonly_server = transport_readonly_server
1360
self._formats = formats
1362
def adapt(self, test):
1363
result = TestSuite()
1364
for format in self._formats:
1365
new_test = deepcopy(test)
1366
new_test.transport_server = self._transport_server
1367
new_test.transport_readonly_server = self._transport_readonly_server
1368
new_test.bzrdir_format = format
1369
def make_new_test_id():
1370
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1371
return lambda: new_id
1372
new_test.id = make_new_test_id()
1373
result.addTest(new_test)
1377
class ScratchDir(BzrDir6):
1378
"""Special test class: a bzrdir that cleans up itself..
1380
>>> d = ScratchDir()
1381
>>> base = d.transport.base
1384
>>> b.transport.__del__()
1389
def __init__(self, files=[], dirs=[], transport=None):
1390
"""Make a test branch.
1392
This creates a temporary directory and runs init-tree in it.
1394
If any files are listed, they are created in the working copy.
1396
if transport is None:
1397
transport = bzrlib.transport.local.ScratchTransport()
1398
# local import for scope restriction
1399
BzrDirFormat6().initialize(transport.base)
1400
super(ScratchDir, self).__init__(transport, BzrDirFormat6())
1401
self.create_repository()
1402
self.create_branch()
1403
self.create_workingtree()
1405
super(ScratchDir, self).__init__(transport, BzrDirFormat6())
1407
# BzrBranch creates a clone to .bzr and then forgets about the
1408
# original transport. A ScratchTransport() deletes itself and
1409
# everything underneath it when it goes away, so we need to
1410
# grab a local copy to prevent that from happening
1411
self._transport = transport
1414
self._transport.mkdir(d)
1417
self._transport.put(f, 'content of %s' % f)
1421
>>> orig = ScratchDir(files=["file1", "file2"])
1422
>>> os.listdir(orig.base)
1423
[u'.bzr', u'file1', u'file2']
1424
>>> clone = orig.clone()
1425
>>> if os.name != 'nt':
1426
... os.path.samefile(orig.base, clone.base)
1428
... orig.base == clone.base
1431
>>> os.listdir(clone.base)
1432
[u'.bzr', u'file1', u'file2']
1434
from shutil import copytree
1435
from bzrlib.osutils import mkdtemp
1438
copytree(self.base, base, symlinks=True)
1440
transport=bzrlib.transport.local.ScratchTransport(base))
1443
class Converter(object):
1444
"""Converts a disk format object from one format to another."""
1446
def convert(self, to_convert, pb):
1447
"""Perform the conversion of to_convert, giving feedback via pb.
1449
:param to_convert: The disk object to convert.
1450
:param pb: a progress bar to use for progress information.
1453
def step(self, message):
1454
"""Update the pb by a step."""
1456
self.pb.update(message, self.count, self.total)
1459
class ConvertBzrDir4To5(Converter):
1460
"""Converts format 4 bzr dirs to format 5."""
1463
super(ConvertBzrDir4To5, self).__init__()
1464
self.converted_revs = set()
1465
self.absent_revisions = set()
1469
def convert(self, to_convert, pb):
1470
"""See Converter.convert()."""
1471
self.bzrdir = to_convert
1473
self.pb.note('starting upgrade from format 4 to 5')
1474
if isinstance(self.bzrdir.transport, LocalTransport):
1475
self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
1476
self._convert_to_weaves()
1477
return BzrDir.open(self.bzrdir.root_transport.base)
1479
def _convert_to_weaves(self):
1480
self.pb.note('note: upgrade may be faster if all store files are ungzipped first')
1483
stat = self.bzrdir.transport.stat('weaves')
1484
if not S_ISDIR(stat.st_mode):
1485
self.bzrdir.transport.delete('weaves')
1486
self.bzrdir.transport.mkdir('weaves')
1487
except errors.NoSuchFile:
1488
self.bzrdir.transport.mkdir('weaves')
1489
# deliberately not a WeaveFile as we want to build it up slowly.
1490
self.inv_weave = Weave('inventory')
1491
# holds in-memory weaves for all files
1492
self.text_weaves = {}
1493
self.bzrdir.transport.delete('branch-format')
1494
self.branch = self.bzrdir.open_branch()
1495
self._convert_working_inv()
1496
rev_history = self.branch.revision_history()
1497
# to_read is a stack holding the revisions we still need to process;
1498
# appending to it adds new highest-priority revisions
1499
self.known_revisions = set(rev_history)
1500
self.to_read = rev_history[-1:]
1502
rev_id = self.to_read.pop()
1503
if (rev_id not in self.revisions
1504
and rev_id not in self.absent_revisions):
1505
self._load_one_rev(rev_id)
1507
to_import = self._make_order()
1508
for i, rev_id in enumerate(to_import):
1509
self.pb.update('converting revision', i, len(to_import))
1510
self._convert_one_rev(rev_id)
1512
self._write_all_weaves()
1513
self._write_all_revs()
1514
self.pb.note('upgraded to weaves:')
1515
self.pb.note(' %6d revisions and inventories', len(self.revisions))
1516
self.pb.note(' %6d revisions not present', len(self.absent_revisions))
1517
self.pb.note(' %6d texts', self.text_count)
1518
self._cleanup_spare_files_after_format4()
1519
self.branch.control_files.put_utf8('branch-format', BzrDirFormat5().get_format_string())
1521
def _cleanup_spare_files_after_format4(self):
1522
# FIXME working tree upgrade foo.
1523
for n in 'merged-patches', 'pending-merged-patches':
1525
## assert os.path.getsize(p) == 0
1526
self.bzrdir.transport.delete(n)
1527
except errors.NoSuchFile:
1529
self.bzrdir.transport.delete_tree('inventory-store')
1530
self.bzrdir.transport.delete_tree('text-store')
1532
def _convert_working_inv(self):
1533
inv = serializer_v4.read_inventory(self.branch.control_files.get('inventory'))
1534
new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1535
# FIXME inventory is a working tree change.
1536
self.branch.control_files.put('inventory', new_inv_xml)
1538
def _write_all_weaves(self):
1539
controlweaves = WeaveStore(self.bzrdir.transport, prefixed=False)
1540
weave_transport = self.bzrdir.transport.clone('weaves')
1541
weaves = WeaveStore(weave_transport, prefixed=False)
1542
transaction = WriteTransaction()
1546
for file_id, file_weave in self.text_weaves.items():
1547
self.pb.update('writing weave', i, len(self.text_weaves))
1548
weaves._put_weave(file_id, file_weave, transaction)
1550
self.pb.update('inventory', 0, 1)
1551
controlweaves._put_weave('inventory', self.inv_weave, transaction)
1552
self.pb.update('inventory', 1, 1)
1556
def _write_all_revs(self):
1557
"""Write all revisions out in new form."""
1558
self.bzrdir.transport.delete_tree('revision-store')
1559
self.bzrdir.transport.mkdir('revision-store')
1560
revision_transport = self.bzrdir.transport.clone('revision-store')
1562
_revision_store = TextRevisionStore(TextStore(revision_transport,
1566
transaction = bzrlib.transactions.WriteTransaction()
1567
for i, rev_id in enumerate(self.converted_revs):
1568
self.pb.update('write revision', i, len(self.converted_revs))
1569
_revision_store.add_revision(self.revisions[rev_id], transaction)
1573
def _load_one_rev(self, rev_id):
1574
"""Load a revision object into memory.
1576
Any parents not either loaded or abandoned get queued to be
1578
self.pb.update('loading revision',
1579
len(self.revisions),
1580
len(self.known_revisions))
1581
if not self.branch.repository.has_revision(rev_id):
1583
self.pb.note('revision {%s} not present in branch; '
1584
'will be converted as a ghost',
1586
self.absent_revisions.add(rev_id)
1588
rev = self.branch.repository._revision_store.get_revision(rev_id,
1589
self.branch.repository.get_transaction())
1590
for parent_id in rev.parent_ids:
1591
self.known_revisions.add(parent_id)
1592
self.to_read.append(parent_id)
1593
self.revisions[rev_id] = rev
1595
def _load_old_inventory(self, rev_id):
1596
assert rev_id not in self.converted_revs
1597
old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
1598
inv = serializer_v4.read_inventory_from_string(old_inv_xml)
1599
rev = self.revisions[rev_id]
1600
if rev.inventory_sha1:
1601
assert rev.inventory_sha1 == sha_string(old_inv_xml), \
1602
'inventory sha mismatch for {%s}' % rev_id
1605
def _load_updated_inventory(self, rev_id):
1606
assert rev_id in self.converted_revs
1607
inv_xml = self.inv_weave.get_text(rev_id)
1608
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(inv_xml)
1611
def _convert_one_rev(self, rev_id):
1612
"""Convert revision and all referenced objects to new format."""
1613
rev = self.revisions[rev_id]
1614
inv = self._load_old_inventory(rev_id)
1615
present_parents = [p for p in rev.parent_ids
1616
if p not in self.absent_revisions]
1617
self._convert_revision_contents(rev, inv, present_parents)
1618
self._store_new_weave(rev, inv, present_parents)
1619
self.converted_revs.add(rev_id)
1621
def _store_new_weave(self, rev, inv, present_parents):
1622
# the XML is now updated with text versions
1626
if ie.kind == 'root_directory':
1628
assert hasattr(ie, 'revision'), \
1629
'no revision on {%s} in {%s}' % \
1630
(file_id, rev.revision_id)
1631
new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1632
new_inv_sha1 = sha_string(new_inv_xml)
1633
self.inv_weave.add_lines(rev.revision_id,
1635
new_inv_xml.splitlines(True))
1636
rev.inventory_sha1 = new_inv_sha1
1638
def _convert_revision_contents(self, rev, inv, present_parents):
1639
"""Convert all the files within a revision.
1641
Also upgrade the inventory to refer to the text revision ids."""
1642
rev_id = rev.revision_id
1643
mutter('converting texts of revision {%s}',
1645
parent_invs = map(self._load_updated_inventory, present_parents)
1648
self._convert_file_version(rev, ie, parent_invs)
1650
def _convert_file_version(self, rev, ie, parent_invs):
1651
"""Convert one version of one file.
1653
The file needs to be added into the weave if it is a merge
1654
of >=2 parents or if it's changed from its parent.
1656
if ie.kind == 'root_directory':
1658
file_id = ie.file_id
1659
rev_id = rev.revision_id
1660
w = self.text_weaves.get(file_id)
1663
self.text_weaves[file_id] = w
1664
text_changed = False
1665
previous_entries = ie.find_previous_heads(parent_invs,
1669
for old_revision in previous_entries:
1670
# if this fails, its a ghost ?
1671
assert old_revision in self.converted_revs
1672
self.snapshot_ie(previous_entries, ie, w, rev_id)
1674
assert getattr(ie, 'revision', None) is not None
1676
def snapshot_ie(self, previous_revisions, ie, w, rev_id):
1677
# TODO: convert this logic, which is ~= snapshot to
1678
# a call to:. This needs the path figured out. rather than a work_tree
1679
# a v4 revision_tree can be given, or something that looks enough like
1680
# one to give the file content to the entry if it needs it.
1681
# and we need something that looks like a weave store for snapshot to
1683
#ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
1684
if len(previous_revisions) == 1:
1685
previous_ie = previous_revisions.values()[0]
1686
if ie._unchanged(previous_ie):
1687
ie.revision = previous_ie.revision
1690
text = self.branch.repository.text_store.get(ie.text_id)
1691
file_lines = text.readlines()
1692
assert sha_strings(file_lines) == ie.text_sha1
1693
assert sum(map(len, file_lines)) == ie.text_size
1694
w.add_lines(rev_id, previous_revisions, file_lines)
1695
self.text_count += 1
1697
w.add_lines(rev_id, previous_revisions, [])
1698
ie.revision = rev_id
1700
def _make_order(self):
1701
"""Return a suitable order for importing revisions.
1703
The order must be such that an revision is imported after all
1704
its (present) parents.
1706
todo = set(self.revisions.keys())
1707
done = self.absent_revisions.copy()
1710
# scan through looking for a revision whose parents
1712
for rev_id in sorted(list(todo)):
1713
rev = self.revisions[rev_id]
1714
parent_ids = set(rev.parent_ids)
1715
if parent_ids.issubset(done):
1716
# can take this one now
1717
order.append(rev_id)
1723
class ConvertBzrDir5To6(Converter):
1724
"""Converts format 5 bzr dirs to format 6."""
1726
def convert(self, to_convert, pb):
1727
"""See Converter.convert()."""
1728
self.bzrdir = to_convert
1730
self.pb.note('starting upgrade from format 5 to 6')
1731
self._convert_to_prefixed()
1732
return BzrDir.open(self.bzrdir.root_transport.base)
1734
def _convert_to_prefixed(self):
1735
from bzrlib.store import TransportStore
1736
self.bzrdir.transport.delete('branch-format')
1737
for store_name in ["weaves", "revision-store"]:
1738
self.pb.note("adding prefixes to %s" % store_name)
1739
store_transport = self.bzrdir.transport.clone(store_name)
1740
store = TransportStore(store_transport, prefixed=True)
1741
for urlfilename in store_transport.list_dir('.'):
1742
filename = urlutils.unescape(urlfilename)
1743
if (filename.endswith(".weave") or
1744
filename.endswith(".gz") or
1745
filename.endswith(".sig")):
1746
file_id = os.path.splitext(filename)[0]
1749
prefix_dir = store.hash_prefix(file_id)
1750
# FIXME keep track of the dirs made RBC 20060121
1752
store_transport.move(filename, prefix_dir + '/' + filename)
1753
except errors.NoSuchFile: # catches missing dirs strangely enough
1754
store_transport.mkdir(prefix_dir)
1755
store_transport.move(filename, prefix_dir + '/' + filename)
1756
self.bzrdir._control_files.put_utf8('branch-format', BzrDirFormat6().get_format_string())
1759
class ConvertBzrDir6ToMeta(Converter):
1760
"""Converts format 6 bzr dirs to metadirs."""
1762
def convert(self, to_convert, pb):
1763
"""See Converter.convert()."""
1764
self.bzrdir = to_convert
1767
self.total = 20 # the steps we know about
1768
self.garbage_inventories = []
1770
self.pb.note('starting upgrade from format 6 to metadir')
1771
self.bzrdir._control_files.put_utf8('branch-format', "Converting to format 6")
1772
# its faster to move specific files around than to open and use the apis...
1773
# first off, nuke ancestry.weave, it was never used.
1775
self.step('Removing ancestry.weave')
1776
self.bzrdir.transport.delete('ancestry.weave')
1777
except errors.NoSuchFile:
1779
# find out whats there
1780
self.step('Finding branch files')
1781
last_revision = self.bzrdir.open_branch().last_revision()
1782
bzrcontents = self.bzrdir.transport.list_dir('.')
1783
for name in bzrcontents:
1784
if name.startswith('basis-inventory.'):
1785
self.garbage_inventories.append(name)
1786
# create new directories for repository, working tree and branch
1787
self.dir_mode = self.bzrdir._control_files._dir_mode
1788
self.file_mode = self.bzrdir._control_files._file_mode
1789
repository_names = [('inventory.weave', True),
1790
('revision-store', True),
1792
self.step('Upgrading repository ')
1793
self.bzrdir.transport.mkdir('repository', mode=self.dir_mode)
1794
self.make_lock('repository')
1795
# we hard code the formats here because we are converting into
1796
# the meta format. The meta format upgrader can take this to a
1797
# future format within each component.
1798
self.put_format('repository', bzrlib.repository.RepositoryFormat7())
1799
for entry in repository_names:
1800
self.move_entry('repository', entry)
1802
self.step('Upgrading branch ')
1803
self.bzrdir.transport.mkdir('branch', mode=self.dir_mode)
1804
self.make_lock('branch')
1805
self.put_format('branch', bzrlib.branch.BzrBranchFormat5())
1806
branch_files = [('revision-history', True),
1807
('branch-name', True),
1809
for entry in branch_files:
1810
self.move_entry('branch', entry)
1812
self.step('Upgrading working tree')
1813
self.bzrdir.transport.mkdir('checkout', mode=self.dir_mode)
1814
self.make_lock('checkout')
1815
self.put_format('checkout', bzrlib.workingtree.WorkingTreeFormat3())
1816
self.bzrdir.transport.delete_multi(self.garbage_inventories, self.pb)
1817
checkout_files = [('pending-merges', True),
1818
('inventory', True),
1819
('stat-cache', False)]
1820
for entry in checkout_files:
1821
self.move_entry('checkout', entry)
1822
if last_revision is not None:
1823
self.bzrdir._control_files.put_utf8('checkout/last-revision',
1825
self.bzrdir._control_files.put_utf8('branch-format', BzrDirMetaFormat1().get_format_string())
1826
return BzrDir.open(self.bzrdir.root_transport.base)
1828
def make_lock(self, name):
1829
"""Make a lock for the new control dir name."""
1830
self.step('Make %s lock' % name)
1831
ld = LockDir(self.bzrdir.transport,
1833
file_modebits=self.file_mode,
1834
dir_modebits=self.dir_mode)
1837
def move_entry(self, new_dir, entry):
1838
"""Move then entry name into new_dir."""
1840
mandatory = entry[1]
1841
self.step('Moving %s' % name)
1843
self.bzrdir.transport.move(name, '%s/%s' % (new_dir, name))
1844
except errors.NoSuchFile:
1848
def put_format(self, dirname, format):
1849
self.bzrdir._control_files.put_utf8('%s/format' % dirname, format.get_format_string())
1852
class ConvertMetaToMeta(Converter):
1853
"""Converts the components of metadirs."""
1855
def __init__(self, target_format):
1856
"""Create a metadir to metadir converter.
1858
:param target_format: The final metadir format that is desired.
1860
self.target_format = target_format
1862
def convert(self, to_convert, pb):
1863
"""See Converter.convert()."""
1864
self.bzrdir = to_convert
1868
self.step('checking repository format')
1870
repo = self.bzrdir.open_repository()
1871
except errors.NoRepositoryPresent:
1874
if not isinstance(repo._format, self.target_format.repository_format.__class__):
1875
from bzrlib.repository import CopyConverter
1876
self.pb.note('starting repository conversion')
1877
converter = CopyConverter(self.target_format.repository_format)
1878
converter.convert(repo, pb)