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."""
97
def _check_supported(format, allow_unsupported):
98
"""Check whether format is a supported format.
100
If allow_unsupported is True, this is a no-op.
102
if not allow_unsupported and not format.is_supported():
103
# see open_downlevel to open legacy branches.
104
raise errors.UnsupportedFormatError(format=format)
106
def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
107
"""Clone this bzrdir and its contents to url verbatim.
109
If urls last component does not exist, it will be created.
111
if revision_id is not None, then the clone operation may tune
112
itself to download less data.
113
:param force_new_repo: Do not use a shared repository for the target
114
even if one is available.
117
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
118
result = self._format.initialize(url)
120
local_repo = self.find_repository()
121
except errors.NoRepositoryPresent:
124
# may need to copy content in
126
result_repo = local_repo.clone(
128
revision_id=revision_id,
130
result_repo.set_make_working_trees(local_repo.make_working_trees())
133
result_repo = result.find_repository()
134
# fetch content this dir needs.
136
# XXX FIXME RBC 20060214 need tests for this when the basis
138
result_repo.fetch(basis_repo, revision_id=revision_id)
139
result_repo.fetch(local_repo, revision_id=revision_id)
140
except errors.NoRepositoryPresent:
141
# needed to make one anyway.
142
result_repo = local_repo.clone(
144
revision_id=revision_id,
146
result_repo.set_make_working_trees(local_repo.make_working_trees())
147
# 1 if there is a branch present
148
# make sure its content is available in the target repository
151
self.open_branch().clone(result, revision_id=revision_id)
152
except errors.NotBranchError:
155
self.open_workingtree().clone(result, basis=basis_tree)
156
except (errors.NoWorkingTree, errors.NotLocalUrl):
160
def _get_basis_components(self, basis):
161
"""Retrieve the basis components that are available at basis."""
163
return None, None, None
165
basis_tree = basis.open_workingtree()
166
basis_branch = basis_tree.branch
167
basis_repo = basis_branch.repository
168
except (errors.NoWorkingTree, errors.NotLocalUrl):
171
basis_branch = basis.open_branch()
172
basis_repo = basis_branch.repository
173
except errors.NotBranchError:
176
basis_repo = basis.open_repository()
177
except errors.NoRepositoryPresent:
179
return basis_repo, basis_branch, basis_tree
181
# TODO: This should be given a Transport, and should chdir up; otherwise
182
# this will open a new connection.
183
def _make_tail(self, url):
184
head, tail = urlutils.split(url)
185
if tail and tail != '.':
186
t = bzrlib.transport.get_transport(head)
189
except errors.FileExists:
192
# TODO: Should take a Transport
194
def create(cls, base):
195
"""Create a new BzrDir at the url 'base'.
197
This will call the current default formats initialize with base
198
as the only parameter.
200
If you need a specific format, consider creating an instance
201
of that and calling initialize().
203
if cls is not BzrDir:
204
raise AssertionError("BzrDir.create always creates the default format, "
205
"not one of %r" % cls)
206
head, tail = urlutils.split(base)
207
if tail and tail != '.':
208
t = bzrlib.transport.get_transport(head)
211
except errors.FileExists:
213
return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
215
def create_branch(self):
216
"""Create a branch in this BzrDir.
218
The bzrdirs format will control what branch format is created.
219
For more control see BranchFormatXX.create(a_bzrdir).
221
raise NotImplementedError(self.create_branch)
224
def create_branch_and_repo(base, force_new_repo=False):
225
"""Create a new BzrDir, Branch and Repository at the url 'base'.
227
This will use the current default BzrDirFormat, and use whatever
228
repository format that that uses via bzrdir.create_branch and
229
create_repository. If a shared repository is available that is used
232
The created Branch object is returned.
234
:param base: The URL to create the branch at.
235
:param force_new_repo: If True a new repository is always created.
237
bzrdir = BzrDir.create(base)
238
bzrdir._find_or_create_repository(force_new_repo)
239
return bzrdir.create_branch()
241
def _find_or_create_repository(self, force_new_repo):
242
"""Create a new repository if needed, returning the repository."""
244
return self.create_repository()
246
return self.find_repository()
247
except errors.NoRepositoryPresent:
248
return self.create_repository()
251
def create_branch_convenience(base, force_new_repo=False,
252
force_new_tree=None, format=None):
253
"""Create a new BzrDir, Branch and Repository at the url 'base'.
255
This is a convenience function - it will use an existing repository
256
if possible, can be told explicitly whether to create a working tree or
259
This will use the current default BzrDirFormat, and use whatever
260
repository format that that uses via bzrdir.create_branch and
261
create_repository. If a shared repository is available that is used
262
preferentially. Whatever repository is used, its tree creation policy
265
The created Branch object is returned.
266
If a working tree cannot be made due to base not being a file:// url,
267
no error is raised unless force_new_tree is True, in which case no
268
data is created on disk and NotLocalUrl is raised.
270
:param base: The URL to create the branch at.
271
:param force_new_repo: If True a new repository is always created.
272
:param force_new_tree: If True or False force creation of a tree or
273
prevent such creation respectively.
274
:param format: Override for the for the bzrdir format to create
277
# check for non local urls
278
t = get_transport(safe_unicode(base))
279
if not isinstance(t, LocalTransport):
280
raise errors.NotLocalUrl(base)
282
bzrdir = BzrDir.create(base)
284
bzrdir = format.initialize(base)
285
repo = bzrdir._find_or_create_repository(force_new_repo)
286
result = bzrdir.create_branch()
287
if force_new_tree or (repo.make_working_trees() and
288
force_new_tree is None):
290
bzrdir.create_workingtree()
291
except errors.NotLocalUrl:
296
def create_repository(base, shared=False):
297
"""Create a new BzrDir and Repository at the url 'base'.
299
This will use the current default BzrDirFormat, and use whatever
300
repository format that that uses for bzrdirformat.create_repository.
302
:param shared: Create a shared repository rather than a standalone
304
The Repository object is returned.
306
This must be overridden as an instance method in child classes, where
307
it should take no parameters and construct whatever repository format
308
that child class desires.
310
bzrdir = BzrDir.create(base)
311
return bzrdir.create_repository(shared)
314
def create_standalone_workingtree(base):
315
"""Create a new BzrDir, WorkingTree, Branch and Repository at 'base'.
317
'base' must be a local path or a file:// url.
319
This will use the current default BzrDirFormat, and use whatever
320
repository format that that uses for bzrdirformat.create_workingtree,
321
create_branch and create_repository.
323
:return: The WorkingTree object.
325
t = get_transport(safe_unicode(base))
326
if not isinstance(t, LocalTransport):
327
raise errors.NotLocalUrl(base)
328
bzrdir = BzrDir.create_branch_and_repo(safe_unicode(base),
329
force_new_repo=True).bzrdir
330
return bzrdir.create_workingtree()
332
def create_workingtree(self, revision_id=None):
333
"""Create a working tree at this BzrDir.
335
revision_id: create it as of this revision id.
337
raise NotImplementedError(self.create_workingtree)
339
def find_repository(self):
340
"""Find the repository that should be used for a_bzrdir.
342
This does not require a branch as we use it to find the repo for
343
new branches as well as to hook existing branches up to their
347
return self.open_repository()
348
except errors.NoRepositoryPresent:
350
next_transport = self.root_transport.clone('..')
352
# find the next containing bzrdir
354
found_bzrdir = BzrDir.open_containing_from_transport(
356
except errors.NotBranchError:
358
raise errors.NoRepositoryPresent(self)
359
# does it have a repository ?
361
repository = found_bzrdir.open_repository()
362
except errors.NoRepositoryPresent:
363
next_transport = found_bzrdir.root_transport.clone('..')
364
if (found_bzrdir.root_transport.base == next_transport.base):
365
# top of the file system
369
if ((found_bzrdir.root_transport.base ==
370
self.root_transport.base) or repository.is_shared()):
373
raise errors.NoRepositoryPresent(self)
374
raise errors.NoRepositoryPresent(self)
376
def get_branch_transport(self, branch_format):
377
"""Get the transport for use by branch format in this BzrDir.
379
Note that bzr dirs that do not support format strings will raise
380
IncompatibleFormat if the branch format they are given has
381
a format string, and vice versa.
383
If branch_format is None, the transport is returned with no
384
checking. if it is not None, then the returned transport is
385
guaranteed to point to an existing directory ready for use.
387
raise NotImplementedError(self.get_branch_transport)
389
def get_repository_transport(self, repository_format):
390
"""Get the transport for use by repository format in this BzrDir.
392
Note that bzr dirs that do not support format strings will raise
393
IncompatibleFormat if the repository format they are given has
394
a format string, and vice versa.
396
If repository_format is None, the transport is returned with no
397
checking. if it is not None, then the returned transport is
398
guaranteed to point to an existing directory ready for use.
400
raise NotImplementedError(self.get_repository_transport)
402
def get_workingtree_transport(self, tree_format):
403
"""Get the transport for use by workingtree format in this BzrDir.
405
Note that bzr dirs that do not support format strings will raise
406
IncompatibleFormat if the workingtree format they are given has
407
a format string, and vice versa.
409
If workingtree_format is None, the transport is returned with no
410
checking. if it is not None, then the returned transport is
411
guaranteed to point to an existing directory ready for use.
413
raise NotImplementedError(self.get_workingtree_transport)
415
def __init__(self, _transport, _format):
416
"""Initialize a Bzr control dir object.
418
Only really common logic should reside here, concrete classes should be
419
made with varying behaviours.
421
:param _format: the format that is creating this BzrDir instance.
422
:param _transport: the transport this dir is based at.
424
self._format = _format
425
self.transport = _transport.clone('.bzr')
426
self.root_transport = _transport
428
def is_control_filename(self, filename):
429
"""True if filename is the name of a path which is reserved for bzrdir's.
431
:param filename: A filename within the root transport of this bzrdir.
433
This is true IF and ONLY IF the filename is part of the namespace reserved
434
for bzr control dirs. Currently this is the '.bzr' directory in the root
435
of the root_transport. it is expected that plugins will need to extend
436
this in the future - for instance to make bzr talk with svn working
439
# this might be better on the BzrDirFormat class because it refers to
440
# all the possible bzrdir disk formats.
441
# This method is tested via the workingtree is_control_filename tests-
442
# it was extracted from WorkingTree.is_control_filename. If the methods
443
# contract is extended beyond the current trivial implementation please
444
# add new tests for it to the appropriate place.
445
return filename == '.bzr' or filename.startswith('.bzr/')
447
def needs_format_conversion(self, format=None):
448
"""Return true if this bzrdir needs convert_format run on it.
450
For instance, if the repository format is out of date but the
451
branch and working tree are not, this should return True.
453
:param format: Optional parameter indicating a specific desired
454
format we plan to arrive at.
456
raise NotImplementedError(self.needs_format_conversion)
459
def open_unsupported(base):
460
"""Open a branch which is not supported."""
461
return BzrDir.open(base, _unsupported=True)
464
def open(base, _unsupported=False):
465
"""Open an existing bzrdir, rooted at 'base' (url)
467
_unsupported is a private parameter to the BzrDir class.
469
t = get_transport(base)
470
return BzrDir.open_from_transport(t, _unsupported=_unsupported)
473
def open_from_transport(transport, _unsupported=False):
474
"""Open a bzrdir within a particular directory.
476
:param transport: Transport containing the bzrdir.
477
:param _unsupported: private.
479
format = BzrDirFormat.find_format(transport)
480
BzrDir._check_supported(format, _unsupported)
481
return format.open(transport, _found=True)
483
def open_branch(self, unsupported=False):
484
"""Open the branch object at this BzrDir if one is present.
486
If unsupported is True, then no longer supported branch formats can
489
TODO: static convenience version of this?
491
raise NotImplementedError(self.open_branch)
494
def open_containing(url):
495
"""Open an existing branch which contains url.
497
:param url: url to search from.
498
See open_containing_from_transport for more detail.
500
return BzrDir.open_containing_from_transport(get_transport(url))
503
def open_containing_from_transport(a_transport):
504
"""Open an existing branch which contains a_transport.base
506
This probes for a branch at a_transport, and searches upwards from there.
508
Basically we keep looking up until we find the control directory or
509
run into the root. If there isn't one, raises NotBranchError.
510
If there is one and it is either an unrecognised format or an unsupported
511
format, UnknownFormatError or UnsupportedFormatError are raised.
512
If there is one, it is returned, along with the unused portion of url.
514
:return: The BzrDir that contains the path, and a Unicode path
515
for the rest of the URL.
517
# this gets the normalised url back. I.e. '.' -> the full path.
518
url = a_transport.base
521
result = BzrDir.open_from_transport(a_transport)
522
return result, urlutils.unescape(a_transport.relpath(url))
523
except errors.NotBranchError, e:
525
new_t = a_transport.clone('..')
526
if new_t.base == a_transport.base:
527
# reached the root, whatever that may be
528
raise errors.NotBranchError(path=url)
531
def open_repository(self, _unsupported=False):
532
"""Open the repository object at this BzrDir if one is present.
534
This will not follow the Branch object pointer - its strictly a direct
535
open facility. Most client code should use open_branch().repository to
538
_unsupported is a private parameter, not part of the api.
539
TODO: static convenience version of this?
541
raise NotImplementedError(self.open_repository)
543
def open_workingtree(self, _unsupported=False):
544
"""Open the workingtree object at this BzrDir if one is present.
546
TODO: static convenience version of this?
548
raise NotImplementedError(self.open_workingtree)
550
def has_branch(self):
551
"""Tell if this bzrdir contains a branch.
553
Note: if you're going to open the branch, you should just go ahead
554
and try, and not ask permission first. (This method just opens the
555
branch and discards it, and that's somewhat expensive.)
560
except errors.NotBranchError:
563
def has_workingtree(self):
564
"""Tell if this bzrdir contains a working tree.
566
This will still raise an exception if the bzrdir has a workingtree that
567
is remote & inaccessible.
569
Note: if you're going to open the working tree, you should just go ahead
570
and try, and not ask permission first. (This method just opens the
571
workingtree and discards it, and that's somewhat expensive.)
574
self.open_workingtree()
576
except errors.NoWorkingTree:
579
def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
580
"""Create a copy of this bzrdir prepared for use as a new line of
583
If urls last component does not exist, it will be created.
585
Attributes related to the identity of the source branch like
586
branch nickname will be cleaned, a working tree is created
587
whether one existed before or not; and a local branch is always
590
if revision_id is not None, then the clone operation may tune
591
itself to download less data.
594
result = self._format.initialize(url)
595
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
597
source_branch = self.open_branch()
598
source_repository = source_branch.repository
599
except errors.NotBranchError:
602
source_repository = self.open_repository()
603
except errors.NoRepositoryPresent:
604
# copy the entire basis one if there is one
605
# but there is no repository.
606
source_repository = basis_repo
611
result_repo = result.find_repository()
612
except errors.NoRepositoryPresent:
614
if source_repository is None and result_repo is not None:
616
elif source_repository is None and result_repo is None:
617
# no repo available, make a new one
618
result.create_repository()
619
elif source_repository is not None and result_repo is None:
620
# have source, and want to make a new target repo
621
# we don't clone the repo because that preserves attributes
622
# like is_shared(), and we have not yet implemented a
623
# repository sprout().
624
result_repo = result.create_repository()
625
if result_repo is not None:
626
# fetch needed content into target.
628
# XXX FIXME RBC 20060214 need tests for this when the basis
630
result_repo.fetch(basis_repo, revision_id=revision_id)
631
if source_repository is not None:
632
result_repo.fetch(source_repository, revision_id=revision_id)
633
if source_branch is not None:
634
source_branch.sprout(result, revision_id=revision_id)
636
result.create_branch()
637
# TODO: jam 20060426 we probably need a test in here in the
638
# case that the newly sprouted branch is a remote one
639
if result_repo is None or result_repo.make_working_trees():
640
result.create_workingtree()
644
class BzrDirPreSplitOut(BzrDir):
645
"""A common class for the all-in-one formats."""
647
def __init__(self, _transport, _format):
648
"""See BzrDir.__init__."""
649
super(BzrDirPreSplitOut, self).__init__(_transport, _format)
650
assert self._format._lock_class == TransportLock
651
assert self._format._lock_file_name == 'branch-lock'
652
self._control_files = LockableFiles(self.get_branch_transport(None),
653
self._format._lock_file_name,
654
self._format._lock_class)
656
def break_lock(self):
657
"""Pre-splitout bzrdirs do not suffer from stale locks."""
658
raise NotImplementedError(self.break_lock)
660
def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
661
"""See BzrDir.clone()."""
662
from bzrlib.workingtree import WorkingTreeFormat2
664
result = self._format._initialize_for_clone(url)
665
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
666
self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
667
from_branch = self.open_branch()
668
from_branch.clone(result, revision_id=revision_id)
670
self.open_workingtree().clone(result, basis=basis_tree)
671
except errors.NotLocalUrl:
672
# make a new one, this format always has to have one.
674
WorkingTreeFormat2().initialize(result)
675
except errors.NotLocalUrl:
676
# but we cannot do it for remote trees.
677
to_branch = result.open_branch()
678
WorkingTreeFormat2().stub_initialize_remote(to_branch.control_files)
681
def create_branch(self):
682
"""See BzrDir.create_branch."""
683
return self.open_branch()
685
def create_repository(self, shared=False):
686
"""See BzrDir.create_repository."""
688
raise errors.IncompatibleFormat('shared repository', self._format)
689
return self.open_repository()
691
def create_workingtree(self, revision_id=None):
692
"""See BzrDir.create_workingtree."""
693
# this looks buggy but is not -really-
694
# clone and sprout will have set the revision_id
695
# and that will have set it for us, its only
696
# specific uses of create_workingtree in isolation
697
# that can do wonky stuff here, and that only
698
# happens for creating checkouts, which cannot be
699
# done on this format anyway. So - acceptable wart.
700
result = self.open_workingtree()
701
if revision_id is not None:
702
if revision_id == bzrlib.revision.NULL_REVISION:
703
result.set_parent_ids([])
705
result.set_parent_ids([revision_id])
708
def get_branch_transport(self, branch_format):
709
"""See BzrDir.get_branch_transport()."""
710
if branch_format is None:
711
return self.transport
713
branch_format.get_format_string()
714
except NotImplementedError:
715
return self.transport
716
raise errors.IncompatibleFormat(branch_format, self._format)
718
def get_repository_transport(self, repository_format):
719
"""See BzrDir.get_repository_transport()."""
720
if repository_format is None:
721
return self.transport
723
repository_format.get_format_string()
724
except NotImplementedError:
725
return self.transport
726
raise errors.IncompatibleFormat(repository_format, self._format)
728
def get_workingtree_transport(self, workingtree_format):
729
"""See BzrDir.get_workingtree_transport()."""
730
if workingtree_format is None:
731
return self.transport
733
workingtree_format.get_format_string()
734
except NotImplementedError:
735
return self.transport
736
raise errors.IncompatibleFormat(workingtree_format, self._format)
738
def needs_format_conversion(self, format=None):
739
"""See BzrDir.needs_format_conversion()."""
740
# if the format is not the same as the system default,
741
# an upgrade is needed.
743
format = BzrDirFormat.get_default_format()
744
return not isinstance(self._format, format.__class__)
746
def open_branch(self, unsupported=False):
747
"""See BzrDir.open_branch."""
748
from bzrlib.branch import BzrBranchFormat4
749
format = BzrBranchFormat4()
750
self._check_supported(format, unsupported)
751
return format.open(self, _found=True)
753
def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
754
"""See BzrDir.sprout()."""
755
from bzrlib.workingtree import WorkingTreeFormat2
757
result = self._format._initialize_for_clone(url)
758
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
760
self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
761
except errors.NoRepositoryPresent:
764
self.open_branch().sprout(result, revision_id=revision_id)
765
except errors.NotBranchError:
767
# we always want a working tree
768
WorkingTreeFormat2().initialize(result)
772
class BzrDir4(BzrDirPreSplitOut):
773
"""A .bzr version 4 control object.
775
This is a deprecated format and may be removed after sept 2006.
778
def create_repository(self, shared=False):
779
"""See BzrDir.create_repository."""
780
return self._format.repository_format.initialize(self, shared)
782
def needs_format_conversion(self, format=None):
783
"""Format 4 dirs are always in need of conversion."""
786
def open_repository(self):
787
"""See BzrDir.open_repository."""
788
from bzrlib.repository import RepositoryFormat4
789
return RepositoryFormat4().open(self, _found=True)
792
class BzrDir5(BzrDirPreSplitOut):
793
"""A .bzr version 5 control object.
795
This is a deprecated format and may be removed after sept 2006.
798
def open_repository(self):
799
"""See BzrDir.open_repository."""
800
from bzrlib.repository import RepositoryFormat5
801
return RepositoryFormat5().open(self, _found=True)
803
def open_workingtree(self, _unsupported=False):
804
"""See BzrDir.create_workingtree."""
805
from bzrlib.workingtree import WorkingTreeFormat2
806
return WorkingTreeFormat2().open(self, _found=True)
809
class BzrDir6(BzrDirPreSplitOut):
810
"""A .bzr version 6 control object.
812
This is a deprecated format and may be removed after sept 2006.
815
def open_repository(self):
816
"""See BzrDir.open_repository."""
817
from bzrlib.repository import RepositoryFormat6
818
return RepositoryFormat6().open(self, _found=True)
820
def open_workingtree(self, _unsupported=False):
821
"""See BzrDir.create_workingtree."""
822
from bzrlib.workingtree import WorkingTreeFormat2
823
return WorkingTreeFormat2().open(self, _found=True)
826
class BzrDirMeta1(BzrDir):
827
"""A .bzr meta version 1 control object.
829
This is the first control object where the
830
individual aspects are really split out: there are separate repository,
831
workingtree and branch subdirectories and any subset of the three can be
832
present within a BzrDir.
835
def can_convert_format(self):
836
"""See BzrDir.can_convert_format()."""
839
def create_branch(self):
840
"""See BzrDir.create_branch."""
841
from bzrlib.branch import BranchFormat
842
return BranchFormat.get_default_format().initialize(self)
844
def create_repository(self, shared=False):
845
"""See BzrDir.create_repository."""
846
return self._format.repository_format.initialize(self, shared)
848
def create_workingtree(self, revision_id=None):
849
"""See BzrDir.create_workingtree."""
850
from bzrlib.workingtree import WorkingTreeFormat
851
return WorkingTreeFormat.get_default_format().initialize(self, revision_id)
853
def _get_mkdir_mode(self):
854
"""Figure out the mode to use when creating a bzrdir subdir."""
855
temp_control = LockableFiles(self.transport, '', TransportLock)
856
return temp_control._dir_mode
858
def get_branch_transport(self, branch_format):
859
"""See BzrDir.get_branch_transport()."""
860
if branch_format is None:
861
return self.transport.clone('branch')
863
branch_format.get_format_string()
864
except NotImplementedError:
865
raise errors.IncompatibleFormat(branch_format, self._format)
867
self.transport.mkdir('branch', mode=self._get_mkdir_mode())
868
except errors.FileExists:
870
return self.transport.clone('branch')
872
def get_repository_transport(self, repository_format):
873
"""See BzrDir.get_repository_transport()."""
874
if repository_format is None:
875
return self.transport.clone('repository')
877
repository_format.get_format_string()
878
except NotImplementedError:
879
raise errors.IncompatibleFormat(repository_format, self._format)
881
self.transport.mkdir('repository', mode=self._get_mkdir_mode())
882
except errors.FileExists:
884
return self.transport.clone('repository')
886
def get_workingtree_transport(self, workingtree_format):
887
"""See BzrDir.get_workingtree_transport()."""
888
if workingtree_format is None:
889
return self.transport.clone('checkout')
891
workingtree_format.get_format_string()
892
except NotImplementedError:
893
raise errors.IncompatibleFormat(workingtree_format, self._format)
895
self.transport.mkdir('checkout', mode=self._get_mkdir_mode())
896
except errors.FileExists:
898
return self.transport.clone('checkout')
900
def needs_format_conversion(self, format=None):
901
"""See BzrDir.needs_format_conversion()."""
903
format = BzrDirFormat.get_default_format()
904
if not isinstance(self._format, format.__class__):
905
# it is not a meta dir format, conversion is needed.
907
# we might want to push this down to the repository?
909
if not isinstance(self.open_repository()._format,
910
format.repository_format.__class__):
911
# the repository needs an upgrade.
913
except errors.NoRepositoryPresent:
915
# currently there are no other possible conversions for meta1 formats.
918
def open_branch(self, unsupported=False):
919
"""See BzrDir.open_branch."""
920
from bzrlib.branch import BranchFormat
921
format = BranchFormat.find_format(self)
922
self._check_supported(format, unsupported)
923
return format.open(self, _found=True)
925
def open_repository(self, unsupported=False):
926
"""See BzrDir.open_repository."""
927
from bzrlib.repository import RepositoryFormat
928
format = RepositoryFormat.find_format(self)
929
self._check_supported(format, unsupported)
930
return format.open(self, _found=True)
932
def open_workingtree(self, unsupported=False):
933
"""See BzrDir.open_workingtree."""
934
from bzrlib.workingtree import WorkingTreeFormat
935
format = WorkingTreeFormat.find_format(self)
936
self._check_supported(format, unsupported)
937
return format.open(self, _found=True)
940
class BzrDirFormat(object):
941
"""An encapsulation of the initialization and open routines for a format.
943
Formats provide three things:
944
* An initialization routine,
948
Formats are placed in an dict by their format string for reference
949
during bzrdir opening. These should be subclasses of BzrDirFormat
952
Once a format is deprecated, just deprecate the initialize and open
953
methods on the format class. Do not deprecate the object, as the
954
object will be created every system load.
957
_default_format = None
958
"""The default format used for new .bzr dirs."""
961
"""The known formats."""
963
_control_formats = []
964
"""The registered control formats - .bzr, ....
966
This is a list of BzrDirFormat objects.
969
_lock_file_name = 'branch-lock'
971
# _lock_class must be set in subclasses to the lock type, typ.
972
# TransportLock or LockDir
975
def find_format(klass, transport):
976
"""Return the format present at transport."""
977
for format in klass._control_formats:
979
return format.probe_transport(transport)
980
except errors.NotBranchError:
981
# this format does not find a control dir here.
983
raise errors.NotBranchError(path=transport.base)
986
def probe_transport(klass, transport):
987
"""Return the .bzrdir style transport present at URL."""
989
format_string = transport.get(".bzr/branch-format").read()
990
except errors.NoSuchFile:
991
raise errors.NotBranchError(path=transport.base)
994
return klass._formats[format_string]
996
raise errors.UnknownFormatError(format=format_string)
999
def get_default_format(klass):
1000
"""Return the current default format."""
1001
return klass._default_format
1003
def get_format_string(self):
1004
"""Return the ASCII format string that identifies this format."""
1005
raise NotImplementedError(self.get_format_string)
1007
def get_format_description(self):
1008
"""Return the short description for this format."""
1009
raise NotImplementedError(self.get_format_description)
1011
def get_converter(self, format=None):
1012
"""Return the converter to use to convert bzrdirs needing converts.
1014
This returns a bzrlib.bzrdir.Converter object.
1016
This should return the best upgrader to step this format towards the
1017
current default format. In the case of plugins we can/should provide
1018
some means for them to extend the range of returnable converters.
1020
:param format: Optional format to override the default format of the
1023
raise NotImplementedError(self.get_converter)
1025
def initialize(self, url):
1026
"""Create a bzr control dir at this url and return an opened copy.
1028
Subclasses should typically override initialize_on_transport
1029
instead of this method.
1031
return self.initialize_on_transport(get_transport(url))
1033
def initialize_on_transport(self, transport):
1034
"""Initialize a new bzrdir in the base directory of a Transport."""
1035
# Since we don't have a .bzr directory, inherit the
1036
# mode from the root directory
1037
temp_control = LockableFiles(transport, '', TransportLock)
1038
temp_control._transport.mkdir('.bzr',
1039
# FIXME: RBC 20060121 don't peek under
1041
mode=temp_control._dir_mode)
1042
file_mode = temp_control._file_mode
1044
mutter('created control directory in ' + transport.base)
1045
control = transport.clone('.bzr')
1046
utf8_files = [('README',
1047
"This is a Bazaar-NG control directory.\n"
1048
"Do not change any files in this directory.\n"),
1049
('branch-format', self.get_format_string()),
1051
# NB: no need to escape relative paths that are url safe.
1052
control_files = LockableFiles(control, self._lock_file_name,
1054
control_files.create_lock()
1055
control_files.lock_write()
1057
for file, content in utf8_files:
1058
control_files.put_utf8(file, content)
1060
control_files.unlock()
1061
return self.open(transport, _found=True)
1063
def is_supported(self):
1064
"""Is this format supported?
1066
Supported formats must be initializable and openable.
1067
Unsupported formats may not support initialization or committing or
1068
some other features depending on the reason for not being supported.
1073
def known_formats(klass):
1074
"""Return all the known formats.
1076
Concrete formats should override _known_formats.
1078
# There is double indirection here to make sure that control
1079
# formats used by more than one dir format will only be probed
1080
# once. This can otherwise be quite expensive for remote connections.
1082
for format in klass._control_formats:
1083
result.update(format._known_formats())
1087
def _known_formats(klass):
1088
"""Return the known format instances for this control format."""
1089
return set(klass._formats.values())
1091
def open(self, transport, _found=False):
1092
"""Return an instance of this format for the dir transport points at.
1094
_found is a private parameter, do not use it.
1097
assert isinstance(BzrDirFormat.find_format(transport),
1099
return self._open(transport)
1101
def _open(self, transport):
1102
"""Template method helper for opening BzrDirectories.
1104
This performs the actual open and any additional logic or parameter
1107
raise NotImplementedError(self._open)
1110
def register_format(klass, format):
1111
klass._formats[format.get_format_string()] = format
1114
def register_control_format(klass, format):
1115
"""Register a format that does not use '.bzrdir' for its control dir.
1117
TODO: This should be pulled up into a 'ControlDirFormat' base class
1118
which BzrDirFormat can inherit from, and renamed to register_format
1119
there. It has been done without that for now for simplicity of
1122
klass._control_formats.append(format)
1125
def set_default_format(klass, format):
1126
klass._default_format = format
1129
return self.get_format_string()[:-1]
1132
def unregister_format(klass, format):
1133
assert klass._formats[format.get_format_string()] is format
1134
del klass._formats[format.get_format_string()]
1137
def unregister_control_format(klass, format):
1138
klass._control_formats.remove(format)
1141
# register BzrDirFormat as a control format
1142
BzrDirFormat.register_control_format(BzrDirFormat)
1145
class BzrDirFormat4(BzrDirFormat):
1146
"""Bzr dir format 4.
1148
This format is a combined format for working tree, branch and repository.
1150
- Format 1 working trees [always]
1151
- Format 4 branches [always]
1152
- Format 4 repositories [always]
1154
This format is deprecated: it indexes texts using a text it which is
1155
removed in format 5; write support for this format has been removed.
1158
_lock_class = TransportLock
1160
def get_format_string(self):
1161
"""See BzrDirFormat.get_format_string()."""
1162
return "Bazaar-NG branch, format 0.0.4\n"
1164
def get_format_description(self):
1165
"""See BzrDirFormat.get_format_description()."""
1166
return "All-in-one format 4"
1168
def get_converter(self, format=None):
1169
"""See BzrDirFormat.get_converter()."""
1170
# there is one and only one upgrade path here.
1171
return ConvertBzrDir4To5()
1173
def initialize_on_transport(self, transport):
1174
"""Format 4 branches cannot be created."""
1175
raise errors.UninitializableFormat(self)
1177
def is_supported(self):
1178
"""Format 4 is not supported.
1180
It is not supported because the model changed from 4 to 5 and the
1181
conversion logic is expensive - so doing it on the fly was not
1186
def _open(self, transport):
1187
"""See BzrDirFormat._open."""
1188
return BzrDir4(transport, self)
1190
def __return_repository_format(self):
1191
"""Circular import protection."""
1192
from bzrlib.repository import RepositoryFormat4
1193
return RepositoryFormat4(self)
1194
repository_format = property(__return_repository_format)
1197
class BzrDirFormat5(BzrDirFormat):
1198
"""Bzr control format 5.
1200
This format is a combined format for working tree, branch and repository.
1202
- Format 2 working trees [always]
1203
- Format 4 branches [always]
1204
- Format 5 repositories [always]
1205
Unhashed stores in the repository.
1208
_lock_class = TransportLock
1210
def get_format_string(self):
1211
"""See BzrDirFormat.get_format_string()."""
1212
return "Bazaar-NG branch, format 5\n"
1214
def get_format_description(self):
1215
"""See BzrDirFormat.get_format_description()."""
1216
return "All-in-one format 5"
1218
def get_converter(self, format=None):
1219
"""See BzrDirFormat.get_converter()."""
1220
# there is one and only one upgrade path here.
1221
return ConvertBzrDir5To6()
1223
def _initialize_for_clone(self, url):
1224
return self.initialize_on_transport(get_transport(url), _cloning=True)
1226
def initialize_on_transport(self, transport, _cloning=False):
1227
"""Format 5 dirs always have working tree, branch and repository.
1229
Except when they are being cloned.
1231
from bzrlib.branch import BzrBranchFormat4
1232
from bzrlib.repository import RepositoryFormat5
1233
from bzrlib.workingtree import WorkingTreeFormat2
1234
result = (super(BzrDirFormat5, self).initialize_on_transport(transport))
1235
RepositoryFormat5().initialize(result, _internal=True)
1237
branch = BzrBranchFormat4().initialize(result)
1239
WorkingTreeFormat2().initialize(result)
1240
except errors.NotLocalUrl:
1241
# Even though we can't access the working tree, we need to
1242
# create its control files.
1243
WorkingTreeFormat2().stub_initialize_remote(branch.control_files)
1246
def _open(self, transport):
1247
"""See BzrDirFormat._open."""
1248
return BzrDir5(transport, self)
1250
def __return_repository_format(self):
1251
"""Circular import protection."""
1252
from bzrlib.repository import RepositoryFormat5
1253
return RepositoryFormat5(self)
1254
repository_format = property(__return_repository_format)
1257
class BzrDirFormat6(BzrDirFormat):
1258
"""Bzr control format 6.
1260
This format is a combined format for working tree, branch and repository.
1262
- Format 2 working trees [always]
1263
- Format 4 branches [always]
1264
- Format 6 repositories [always]
1267
_lock_class = TransportLock
1269
def get_format_string(self):
1270
"""See BzrDirFormat.get_format_string()."""
1271
return "Bazaar-NG branch, format 6\n"
1273
def get_format_description(self):
1274
"""See BzrDirFormat.get_format_description()."""
1275
return "All-in-one format 6"
1277
def get_converter(self, format=None):
1278
"""See BzrDirFormat.get_converter()."""
1279
# there is one and only one upgrade path here.
1280
return ConvertBzrDir6ToMeta()
1282
def _initialize_for_clone(self, url):
1283
return self.initialize_on_transport(get_transport(url), _cloning=True)
1285
def initialize_on_transport(self, transport, _cloning=False):
1286
"""Format 6 dirs always have working tree, branch and repository.
1288
Except when they are being cloned.
1290
from bzrlib.branch import BzrBranchFormat4
1291
from bzrlib.repository import RepositoryFormat6
1292
from bzrlib.workingtree import WorkingTreeFormat2
1293
result = super(BzrDirFormat6, self).initialize_on_transport(transport)
1294
RepositoryFormat6().initialize(result, _internal=True)
1296
branch = BzrBranchFormat4().initialize(result)
1298
WorkingTreeFormat2().initialize(result)
1299
except errors.NotLocalUrl:
1300
# Even though we can't access the working tree, we need to
1301
# create its control files.
1302
WorkingTreeFormat2().stub_initialize_remote(branch.control_files)
1305
def _open(self, transport):
1306
"""See BzrDirFormat._open."""
1307
return BzrDir6(transport, self)
1309
def __return_repository_format(self):
1310
"""Circular import protection."""
1311
from bzrlib.repository import RepositoryFormat6
1312
return RepositoryFormat6(self)
1313
repository_format = property(__return_repository_format)
1316
class BzrDirMetaFormat1(BzrDirFormat):
1317
"""Bzr meta control format 1
1319
This is the first format with split out working tree, branch and repository
1322
- Format 3 working trees [optional]
1323
- Format 5 branches [optional]
1324
- Format 7 repositories [optional]
1327
_lock_class = LockDir
1329
def get_converter(self, format=None):
1330
"""See BzrDirFormat.get_converter()."""
1332
format = BzrDirFormat.get_default_format()
1333
if not isinstance(self, format.__class__):
1334
# converting away from metadir is not implemented
1335
raise NotImplementedError(self.get_converter)
1336
return ConvertMetaToMeta(format)
1338
def get_format_string(self):
1339
"""See BzrDirFormat.get_format_string()."""
1340
return "Bazaar-NG meta directory, format 1\n"
1342
def get_format_description(self):
1343
"""See BzrDirFormat.get_format_description()."""
1344
return "Meta directory format 1"
1346
def _open(self, transport):
1347
"""See BzrDirFormat._open."""
1348
return BzrDirMeta1(transport, self)
1350
def __return_repository_format(self):
1351
"""Circular import protection."""
1352
if getattr(self, '_repository_format', None):
1353
return self._repository_format
1354
from bzrlib.repository import RepositoryFormat
1355
return RepositoryFormat.get_default_format()
1357
def __set_repository_format(self, value):
1358
"""Allow changint the repository format for metadir formats."""
1359
self._repository_format = value
1361
repository_format = property(__return_repository_format, __set_repository_format)
1364
BzrDirFormat.register_format(BzrDirFormat4())
1365
BzrDirFormat.register_format(BzrDirFormat5())
1366
BzrDirFormat.register_format(BzrDirFormat6())
1367
__default_format = BzrDirMetaFormat1()
1368
BzrDirFormat.register_format(__default_format)
1369
BzrDirFormat.set_default_format(__default_format)
1372
class BzrDirTestProviderAdapter(object):
1373
"""A tool to generate a suite testing multiple bzrdir formats at once.
1375
This is done by copying the test once for each transport and injecting
1376
the transport_server, transport_readonly_server, and bzrdir_format
1377
classes into each copy. Each copy is also given a new id() to make it
1381
def __init__(self, transport_server, transport_readonly_server, formats):
1382
self._transport_server = transport_server
1383
self._transport_readonly_server = transport_readonly_server
1384
self._formats = formats
1386
def adapt(self, test):
1387
result = TestSuite()
1388
for format in self._formats:
1389
new_test = deepcopy(test)
1390
new_test.transport_server = self._transport_server
1391
new_test.transport_readonly_server = self._transport_readonly_server
1392
new_test.bzrdir_format = format
1393
def make_new_test_id():
1394
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1395
return lambda: new_id
1396
new_test.id = make_new_test_id()
1397
result.addTest(new_test)
1401
class Converter(object):
1402
"""Converts a disk format object from one format to another."""
1404
def convert(self, to_convert, pb):
1405
"""Perform the conversion of to_convert, giving feedback via pb.
1407
:param to_convert: The disk object to convert.
1408
:param pb: a progress bar to use for progress information.
1411
def step(self, message):
1412
"""Update the pb by a step."""
1414
self.pb.update(message, self.count, self.total)
1417
class ConvertBzrDir4To5(Converter):
1418
"""Converts format 4 bzr dirs to format 5."""
1421
super(ConvertBzrDir4To5, self).__init__()
1422
self.converted_revs = set()
1423
self.absent_revisions = set()
1427
def convert(self, to_convert, pb):
1428
"""See Converter.convert()."""
1429
self.bzrdir = to_convert
1431
self.pb.note('starting upgrade from format 4 to 5')
1432
if isinstance(self.bzrdir.transport, LocalTransport):
1433
self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
1434
self._convert_to_weaves()
1435
return BzrDir.open(self.bzrdir.root_transport.base)
1437
def _convert_to_weaves(self):
1438
self.pb.note('note: upgrade may be faster if all store files are ungzipped first')
1441
stat = self.bzrdir.transport.stat('weaves')
1442
if not S_ISDIR(stat.st_mode):
1443
self.bzrdir.transport.delete('weaves')
1444
self.bzrdir.transport.mkdir('weaves')
1445
except errors.NoSuchFile:
1446
self.bzrdir.transport.mkdir('weaves')
1447
# deliberately not a WeaveFile as we want to build it up slowly.
1448
self.inv_weave = Weave('inventory')
1449
# holds in-memory weaves for all files
1450
self.text_weaves = {}
1451
self.bzrdir.transport.delete('branch-format')
1452
self.branch = self.bzrdir.open_branch()
1453
self._convert_working_inv()
1454
rev_history = self.branch.revision_history()
1455
# to_read is a stack holding the revisions we still need to process;
1456
# appending to it adds new highest-priority revisions
1457
self.known_revisions = set(rev_history)
1458
self.to_read = rev_history[-1:]
1460
rev_id = self.to_read.pop()
1461
if (rev_id not in self.revisions
1462
and rev_id not in self.absent_revisions):
1463
self._load_one_rev(rev_id)
1465
to_import = self._make_order()
1466
for i, rev_id in enumerate(to_import):
1467
self.pb.update('converting revision', i, len(to_import))
1468
self._convert_one_rev(rev_id)
1470
self._write_all_weaves()
1471
self._write_all_revs()
1472
self.pb.note('upgraded to weaves:')
1473
self.pb.note(' %6d revisions and inventories', len(self.revisions))
1474
self.pb.note(' %6d revisions not present', len(self.absent_revisions))
1475
self.pb.note(' %6d texts', self.text_count)
1476
self._cleanup_spare_files_after_format4()
1477
self.branch.control_files.put_utf8('branch-format', BzrDirFormat5().get_format_string())
1479
def _cleanup_spare_files_after_format4(self):
1480
# FIXME working tree upgrade foo.
1481
for n in 'merged-patches', 'pending-merged-patches':
1483
## assert os.path.getsize(p) == 0
1484
self.bzrdir.transport.delete(n)
1485
except errors.NoSuchFile:
1487
self.bzrdir.transport.delete_tree('inventory-store')
1488
self.bzrdir.transport.delete_tree('text-store')
1490
def _convert_working_inv(self):
1491
inv = serializer_v4.read_inventory(self.branch.control_files.get('inventory'))
1492
new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1493
# FIXME inventory is a working tree change.
1494
self.branch.control_files.put('inventory', StringIO(new_inv_xml))
1496
def _write_all_weaves(self):
1497
controlweaves = WeaveStore(self.bzrdir.transport, prefixed=False)
1498
weave_transport = self.bzrdir.transport.clone('weaves')
1499
weaves = WeaveStore(weave_transport, prefixed=False)
1500
transaction = WriteTransaction()
1504
for file_id, file_weave in self.text_weaves.items():
1505
self.pb.update('writing weave', i, len(self.text_weaves))
1506
weaves._put_weave(file_id, file_weave, transaction)
1508
self.pb.update('inventory', 0, 1)
1509
controlweaves._put_weave('inventory', self.inv_weave, transaction)
1510
self.pb.update('inventory', 1, 1)
1514
def _write_all_revs(self):
1515
"""Write all revisions out in new form."""
1516
self.bzrdir.transport.delete_tree('revision-store')
1517
self.bzrdir.transport.mkdir('revision-store')
1518
revision_transport = self.bzrdir.transport.clone('revision-store')
1520
_revision_store = TextRevisionStore(TextStore(revision_transport,
1524
transaction = bzrlib.transactions.WriteTransaction()
1525
for i, rev_id in enumerate(self.converted_revs):
1526
self.pb.update('write revision', i, len(self.converted_revs))
1527
_revision_store.add_revision(self.revisions[rev_id], transaction)
1531
def _load_one_rev(self, rev_id):
1532
"""Load a revision object into memory.
1534
Any parents not either loaded or abandoned get queued to be
1536
self.pb.update('loading revision',
1537
len(self.revisions),
1538
len(self.known_revisions))
1539
if not self.branch.repository.has_revision(rev_id):
1541
self.pb.note('revision {%s} not present in branch; '
1542
'will be converted as a ghost',
1544
self.absent_revisions.add(rev_id)
1546
rev = self.branch.repository._revision_store.get_revision(rev_id,
1547
self.branch.repository.get_transaction())
1548
for parent_id in rev.parent_ids:
1549
self.known_revisions.add(parent_id)
1550
self.to_read.append(parent_id)
1551
self.revisions[rev_id] = rev
1553
def _load_old_inventory(self, rev_id):
1554
assert rev_id not in self.converted_revs
1555
old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
1556
inv = serializer_v4.read_inventory_from_string(old_inv_xml)
1557
rev = self.revisions[rev_id]
1558
if rev.inventory_sha1:
1559
assert rev.inventory_sha1 == sha_string(old_inv_xml), \
1560
'inventory sha mismatch for {%s}' % rev_id
1563
def _load_updated_inventory(self, rev_id):
1564
assert rev_id in self.converted_revs
1565
inv_xml = self.inv_weave.get_text(rev_id)
1566
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(inv_xml)
1569
def _convert_one_rev(self, rev_id):
1570
"""Convert revision and all referenced objects to new format."""
1571
rev = self.revisions[rev_id]
1572
inv = self._load_old_inventory(rev_id)
1573
present_parents = [p for p in rev.parent_ids
1574
if p not in self.absent_revisions]
1575
self._convert_revision_contents(rev, inv, present_parents)
1576
self._store_new_weave(rev, inv, present_parents)
1577
self.converted_revs.add(rev_id)
1579
def _store_new_weave(self, rev, inv, present_parents):
1580
# the XML is now updated with text versions
1582
entries = inv.iter_entries()
1584
for path, ie in entries:
1585
assert getattr(ie, 'revision', None) is not None, \
1586
'no revision on {%s} in {%s}' % \
1587
(file_id, rev.revision_id)
1588
new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1589
new_inv_sha1 = sha_string(new_inv_xml)
1590
self.inv_weave.add_lines(rev.revision_id,
1592
new_inv_xml.splitlines(True))
1593
rev.inventory_sha1 = new_inv_sha1
1595
def _convert_revision_contents(self, rev, inv, present_parents):
1596
"""Convert all the files within a revision.
1598
Also upgrade the inventory to refer to the text revision ids."""
1599
rev_id = rev.revision_id
1600
mutter('converting texts of revision {%s}',
1602
parent_invs = map(self._load_updated_inventory, present_parents)
1603
entries = inv.iter_entries()
1605
for path, ie in entries:
1606
self._convert_file_version(rev, ie, parent_invs)
1608
def _convert_file_version(self, rev, ie, parent_invs):
1609
"""Convert one version of one file.
1611
The file needs to be added into the weave if it is a merge
1612
of >=2 parents or if it's changed from its parent.
1614
file_id = ie.file_id
1615
rev_id = rev.revision_id
1616
w = self.text_weaves.get(file_id)
1619
self.text_weaves[file_id] = w
1620
text_changed = False
1621
previous_entries = ie.find_previous_heads(parent_invs,
1625
for old_revision in previous_entries:
1626
# if this fails, its a ghost ?
1627
assert old_revision in self.converted_revs
1628
self.snapshot_ie(previous_entries, ie, w, rev_id)
1630
assert getattr(ie, 'revision', None) is not None
1632
def snapshot_ie(self, previous_revisions, ie, w, rev_id):
1633
# TODO: convert this logic, which is ~= snapshot to
1634
# a call to:. This needs the path figured out. rather than a work_tree
1635
# a v4 revision_tree can be given, or something that looks enough like
1636
# one to give the file content to the entry if it needs it.
1637
# and we need something that looks like a weave store for snapshot to
1639
#ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
1640
if len(previous_revisions) == 1:
1641
previous_ie = previous_revisions.values()[0]
1642
if ie._unchanged(previous_ie):
1643
ie.revision = previous_ie.revision
1646
text = self.branch.repository.text_store.get(ie.text_id)
1647
file_lines = text.readlines()
1648
assert sha_strings(file_lines) == ie.text_sha1
1649
assert sum(map(len, file_lines)) == ie.text_size
1650
w.add_lines(rev_id, previous_revisions, file_lines)
1651
self.text_count += 1
1653
w.add_lines(rev_id, previous_revisions, [])
1654
ie.revision = rev_id
1656
def _make_order(self):
1657
"""Return a suitable order for importing revisions.
1659
The order must be such that an revision is imported after all
1660
its (present) parents.
1662
todo = set(self.revisions.keys())
1663
done = self.absent_revisions.copy()
1666
# scan through looking for a revision whose parents
1668
for rev_id in sorted(list(todo)):
1669
rev = self.revisions[rev_id]
1670
parent_ids = set(rev.parent_ids)
1671
if parent_ids.issubset(done):
1672
# can take this one now
1673
order.append(rev_id)
1679
class ConvertBzrDir5To6(Converter):
1680
"""Converts format 5 bzr dirs to format 6."""
1682
def convert(self, to_convert, pb):
1683
"""See Converter.convert()."""
1684
self.bzrdir = to_convert
1686
self.pb.note('starting upgrade from format 5 to 6')
1687
self._convert_to_prefixed()
1688
return BzrDir.open(self.bzrdir.root_transport.base)
1690
def _convert_to_prefixed(self):
1691
from bzrlib.store import TransportStore
1692
self.bzrdir.transport.delete('branch-format')
1693
for store_name in ["weaves", "revision-store"]:
1694
self.pb.note("adding prefixes to %s" % store_name)
1695
store_transport = self.bzrdir.transport.clone(store_name)
1696
store = TransportStore(store_transport, prefixed=True)
1697
for urlfilename in store_transport.list_dir('.'):
1698
filename = urlutils.unescape(urlfilename)
1699
if (filename.endswith(".weave") or
1700
filename.endswith(".gz") or
1701
filename.endswith(".sig")):
1702
file_id = os.path.splitext(filename)[0]
1705
prefix_dir = store.hash_prefix(file_id)
1706
# FIXME keep track of the dirs made RBC 20060121
1708
store_transport.move(filename, prefix_dir + '/' + filename)
1709
except errors.NoSuchFile: # catches missing dirs strangely enough
1710
store_transport.mkdir(prefix_dir)
1711
store_transport.move(filename, prefix_dir + '/' + filename)
1712
self.bzrdir._control_files.put_utf8('branch-format', BzrDirFormat6().get_format_string())
1715
class ConvertBzrDir6ToMeta(Converter):
1716
"""Converts format 6 bzr dirs to metadirs."""
1718
def convert(self, to_convert, pb):
1719
"""See Converter.convert()."""
1720
self.bzrdir = to_convert
1723
self.total = 20 # the steps we know about
1724
self.garbage_inventories = []
1726
self.pb.note('starting upgrade from format 6 to metadir')
1727
self.bzrdir._control_files.put_utf8('branch-format', "Converting to format 6")
1728
# its faster to move specific files around than to open and use the apis...
1729
# first off, nuke ancestry.weave, it was never used.
1731
self.step('Removing ancestry.weave')
1732
self.bzrdir.transport.delete('ancestry.weave')
1733
except errors.NoSuchFile:
1735
# find out whats there
1736
self.step('Finding branch files')
1737
last_revision = self.bzrdir.open_branch().last_revision()
1738
bzrcontents = self.bzrdir.transport.list_dir('.')
1739
for name in bzrcontents:
1740
if name.startswith('basis-inventory.'):
1741
self.garbage_inventories.append(name)
1742
# create new directories for repository, working tree and branch
1743
self.dir_mode = self.bzrdir._control_files._dir_mode
1744
self.file_mode = self.bzrdir._control_files._file_mode
1745
repository_names = [('inventory.weave', True),
1746
('revision-store', True),
1748
self.step('Upgrading repository ')
1749
self.bzrdir.transport.mkdir('repository', mode=self.dir_mode)
1750
self.make_lock('repository')
1751
# we hard code the formats here because we are converting into
1752
# the meta format. The meta format upgrader can take this to a
1753
# future format within each component.
1754
self.put_format('repository', bzrlib.repository.RepositoryFormat7())
1755
for entry in repository_names:
1756
self.move_entry('repository', entry)
1758
self.step('Upgrading branch ')
1759
self.bzrdir.transport.mkdir('branch', mode=self.dir_mode)
1760
self.make_lock('branch')
1761
self.put_format('branch', bzrlib.branch.BzrBranchFormat5())
1762
branch_files = [('revision-history', True),
1763
('branch-name', True),
1765
for entry in branch_files:
1766
self.move_entry('branch', entry)
1768
checkout_files = [('pending-merges', True),
1769
('inventory', True),
1770
('stat-cache', False)]
1771
# If a mandatory checkout file is not present, the branch does not have
1772
# a functional checkout. Do not create a checkout in the converted
1774
for name, mandatory in checkout_files:
1775
if mandatory and name not in bzrcontents:
1776
has_checkout = False
1780
if not has_checkout:
1781
self.pb.note('No working tree.')
1782
# If some checkout files are there, we may as well get rid of them.
1783
for name, mandatory in checkout_files:
1784
if name in bzrcontents:
1785
self.bzrdir.transport.delete(name)
1787
self.step('Upgrading working tree')
1788
self.bzrdir.transport.mkdir('checkout', mode=self.dir_mode)
1789
self.make_lock('checkout')
1791
'checkout', bzrlib.workingtree.WorkingTreeFormat3())
1792
self.bzrdir.transport.delete_multi(
1793
self.garbage_inventories, self.pb)
1794
for entry in checkout_files:
1795
self.move_entry('checkout', entry)
1796
if last_revision is not None:
1797
self.bzrdir._control_files.put_utf8(
1798
'checkout/last-revision', last_revision)
1799
self.bzrdir._control_files.put_utf8(
1800
'branch-format', BzrDirMetaFormat1().get_format_string())
1801
return BzrDir.open(self.bzrdir.root_transport.base)
1803
def make_lock(self, name):
1804
"""Make a lock for the new control dir name."""
1805
self.step('Make %s lock' % name)
1806
ld = LockDir(self.bzrdir.transport,
1808
file_modebits=self.file_mode,
1809
dir_modebits=self.dir_mode)
1812
def move_entry(self, new_dir, entry):
1813
"""Move then entry name into new_dir."""
1815
mandatory = entry[1]
1816
self.step('Moving %s' % name)
1818
self.bzrdir.transport.move(name, '%s/%s' % (new_dir, name))
1819
except errors.NoSuchFile:
1823
def put_format(self, dirname, format):
1824
self.bzrdir._control_files.put_utf8('%s/format' % dirname, format.get_format_string())
1827
class ConvertMetaToMeta(Converter):
1828
"""Converts the components of metadirs."""
1830
def __init__(self, target_format):
1831
"""Create a metadir to metadir converter.
1833
:param target_format: The final metadir format that is desired.
1835
self.target_format = target_format
1837
def convert(self, to_convert, pb):
1838
"""See Converter.convert()."""
1839
self.bzrdir = to_convert
1843
self.step('checking repository format')
1845
repo = self.bzrdir.open_repository()
1846
except errors.NoRepositoryPresent:
1849
if not isinstance(repo._format, self.target_format.repository_format.__class__):
1850
from bzrlib.repository import CopyConverter
1851
self.pb.note('starting repository conversion')
1852
converter = CopyConverter(self.target_format.repository_format)
1853
converter.convert(repo, pb)