1
# Copyright (C) 2005 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
31
from bzrlib.osutils import safe_unicode
32
from bzrlib.osutils import (
39
from bzrlib.store.text import TextStore
40
from bzrlib.store.weave import WeaveStore
41
from bzrlib.symbol_versioning import *
42
from bzrlib.trace import mutter
43
from bzrlib.transactions import PassThroughTransaction
44
from bzrlib.transport import get_transport
45
from bzrlib.transport.local import LocalTransport
46
from bzrlib.weave import Weave
47
from bzrlib.weavefile import read_weave, write_weave
48
from bzrlib.xml4 import serializer_v4
49
from bzrlib.xml5 import serializer_v5
53
"""A .bzr control diretory.
55
BzrDir instances let you create or open any of the things that can be
56
found within .bzr - checkouts, branches and repositories.
59
the transport which this bzr dir is rooted at (i.e. file:///.../.bzr/)
61
a transport connected to the directory this bzr was opened from.
64
def can_update_format(self):
65
"""Return true if this bzrdir is one whose format we can update."""
68
def _check_supported(self, format, allow_unsupported):
69
"""Check whether format is a supported format.
71
If allow_unsupported is True, this is a no-op.
73
if not allow_unsupported and not format.is_supported():
74
raise errors.UnsupportedFormatError(format)
76
def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
77
"""Clone this bzrdir and its contents to url verbatim.
79
If urls last component does not exist, it will be created.
81
if revision_id is not None, then the clone operation may tune
82
itself to download less data.
83
:param force_new_repo: Do not use a shared repository for the target
84
even if one is available.
87
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
88
result = self._format.initialize(url)
90
local_repo = self.find_repository()
91
except errors.NoRepositoryPresent:
94
# may need to copy content in
96
local_repo.clone(result, revision_id=revision_id, basis=basis_repo)
99
result_repo = result.find_repository()
100
# fetch content this dir needs.
102
# XXX FIXME RBC 20060214 need tests for this when the basis
104
result_repo.fetch(basis_repo, revision_id=revision_id)
105
result_repo.fetch(local_repo, revision_id=revision_id)
106
except errors.NoRepositoryPresent:
107
# needed to make one anyway.
108
local_repo.clone(result, revision_id=revision_id, basis=basis_repo)
109
# 1 if there is a branch present
110
# make sure its content is available in the target repository
113
self.open_branch().clone(result, revision_id=revision_id)
114
except errors.NotBranchError:
117
self.open_workingtree().clone(result, basis=basis_tree)
118
except (errors.NoWorkingTree, errors.NotLocalUrl):
122
def _get_basis_components(self, basis):
123
"""Retrieve the basis components that are available at basis."""
125
return None, None, None
127
basis_tree = basis.open_workingtree()
128
basis_branch = basis_tree.branch
129
basis_repo = basis_branch.repository
130
except (errors.NoWorkingTree, errors.NotLocalUrl):
133
basis_branch = basis.open_branch()
134
basis_repo = basis_branch.repository
135
except errors.NotBranchError:
138
basis_repo = basis.open_repository()
139
except errors.NoRepositoryPresent:
141
return basis_repo, basis_branch, basis_tree
143
def _make_tail(self, url):
144
segments = url.split('/')
145
if segments and segments[-1] not in ('', '.'):
146
parent = '/'.join(segments[:-1])
147
t = bzrlib.transport.get_transport(parent)
149
t.mkdir(segments[-1])
150
except errors.FileExists:
155
"""Create a new BzrDir at the url 'base'.
157
This will call the current default formats initialize with base
158
as the only parameter.
160
If you need a specific format, consider creating an instance
161
of that and calling initialize().
163
segments = base.split('/')
164
if segments and segments[-1] not in ('', '.'):
165
parent = '/'.join(segments[:-1])
166
t = bzrlib.transport.get_transport(parent)
168
t.mkdir(segments[-1])
169
except errors.FileExists:
171
return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
173
def create_branch(self):
174
"""Create a branch in this BzrDir.
176
The bzrdirs format will control what branch format is created.
177
For more control see BranchFormatXX.create(a_bzrdir).
179
raise NotImplementedError(self.create_branch)
182
def create_branch_and_repo(base, force_new_repo=False):
183
"""Create a new BzrDir, Branch and Repository at the url 'base'.
185
This will use the current default BzrDirFormat, and use whatever
186
repository format that that uses via bzrdir.create_branch and
187
create_repository. If a shared repository is available that is used
190
The created Branch object is returned.
192
:param base: The URL to create the branch at.
193
:param force_new_repo: If True a new repository is always created.
195
bzrdir = BzrDir.create(base)
196
bzrdir._find_or_create_repository(force_new_repo)
197
return bzrdir.create_branch()
199
def _find_or_create_repository(self, force_new_repo):
200
"""Create a new repository if needed, returning the repository."""
202
return self.create_repository()
204
return self.find_repository()
205
except errors.NoRepositoryPresent:
206
return self.create_repository()
209
def create_branch_convenience(base, force_new_repo=False, force_new_tree=None):
210
"""Create a new BzrDir, Branch and Repository at the url 'base'.
212
This is a convenience function - it will use an existing repository
213
if possible, can be told explicitly whether to create a working tree or
216
This will use the current default BzrDirFormat, and use whatever
217
repository format that that uses via bzrdir.create_branch and
218
create_repository. If a shared repository is available that is used
219
preferentially. Whatever repository is used, its tree creation policy
222
The created Branch object is returned.
223
If a working tree cannot be made due to base not being a file:// url,
226
:param base: The URL to create the branch at.
227
:param force_new_repo: If True a new repository is always created.
228
:param force_new_tree: If True or False force creation of a tree or
229
prevent such creation respectively.
231
bzrdir = BzrDir.create(base)
232
repo = bzrdir._find_or_create_repository(force_new_repo)
233
result = bzrdir.create_branch()
234
if force_new_tree or (repo.make_working_trees() and
235
force_new_tree is None):
236
bzrdir.create_workingtree()
240
def create_repository(base, shared=False):
241
"""Create a new BzrDir and Repository at the url 'base'.
243
This will use the current default BzrDirFormat, and use whatever
244
repository format that that uses for bzrdirformat.create_repository.
246
;param shared: Create a shared repository rather than a standalone
248
The Repository object is returned.
250
This must be overridden as an instance method in child classes, where
251
it should take no parameters and construct whatever repository format
252
that child class desires.
254
bzrdir = BzrDir.create(base)
255
return bzrdir.create_repository()
258
def create_standalone_workingtree(base):
259
"""Create a new BzrDir, WorkingTree, Branch and Repository at 'base'.
261
'base' must be a local path or a file:// url.
263
This will use the current default BzrDirFormat, and use whatever
264
repository format that that uses for bzrdirformat.create_workingtree,
265
create_branch and create_repository.
267
The WorkingTree object is returned.
269
t = get_transport(safe_unicode(base))
270
if not isinstance(t, LocalTransport):
271
raise errors.NotLocalUrl(base)
272
bzrdir = BzrDir.create_branch_and_repo(safe_unicode(base),
273
force_new_repo=True).bzrdir
274
return bzrdir.create_workingtree()
276
def create_workingtree(self, revision_id=None):
277
"""Create a working tree at this BzrDir.
279
revision_id: create it as of this revision id.
281
raise NotImplementedError(self.create_workingtree)
283
def find_repository(self):
284
"""Find the repository that should be used for a_bzrdir.
286
This does not require a branch as we use it to find the repo for
287
new branches as well as to hook existing branches up to their
291
return self.open_repository()
292
except errors.NoRepositoryPresent:
294
next_transport = self.root_transport.clone('..')
297
found_bzrdir = BzrDir.open_containing_from_transport(
299
except errors.NotBranchError:
300
raise errors.NoRepositoryPresent(self)
302
repository = found_bzrdir.open_repository()
303
except errors.NoRepositoryPresent:
304
next_transport = found_bzrdir.root_transport.clone('..')
306
if ((found_bzrdir.root_transport.base ==
307
self.root_transport.base) or repository.is_shared()):
310
raise errors.NoRepositoryPresent(self)
311
raise errors.NoRepositoryPresent(self)
313
def get_branch_transport(self, branch_format):
314
"""Get the transport for use by branch format in this BzrDir.
316
Note that bzr dirs that do not support format strings will raise
317
IncompatibleFormat if the branch format they are given has
318
a format string, and vice verca.
320
If branch_format is None, the transport is returned with no
321
checking. if it is not None, then the returned transport is
322
guaranteed to point to an existing directory ready for use.
324
raise NotImplementedError(self.get_branch_transport)
326
def get_repository_transport(self, repository_format):
327
"""Get the transport for use by repository format in this BzrDir.
329
Note that bzr dirs that do not support format strings will raise
330
IncompatibleFormat if the repository format they are given has
331
a format string, and vice verca.
333
If repository_format is None, the transport is returned with no
334
checking. if it is not None, then the returned transport is
335
guaranteed to point to an existing directory ready for use.
337
raise NotImplementedError(self.get_repository_transport)
339
def get_workingtree_transport(self, tree_format):
340
"""Get the transport for use by workingtree format in this BzrDir.
342
Note that bzr dirs that do not support format strings will raise
343
IncompatibleFormat if the workingtree format they are given has
344
a format string, and vice verca.
346
If workingtree_format is None, the transport is returned with no
347
checking. if it is not None, then the returned transport is
348
guaranteed to point to an existing directory ready for use.
350
raise NotImplementedError(self.get_workingtree_transport)
352
def __init__(self, _transport, _format):
353
"""Initialize a Bzr control dir object.
355
Only really common logic should reside here, concrete classes should be
356
made with varying behaviours.
358
:param _format: the format that is creating this BzrDir instance.
359
:param _transport: the transport this dir is based at.
361
self._format = _format
362
self.transport = _transport.clone('.bzr')
363
self.root_transport = _transport
365
def needs_format_update(self):
366
"""Return true if this bzrdir needs update_format run on it.
368
For instance, if the repository format is out of date but the
369
branch and working tree are not, this should return True.
371
# for now, if the format is not the same as the system default,
372
# an upgrade is needed.
373
return not isinstance(self._format, BzrDirFormat.get_default_format().__class__)
376
def open_unsupported(base):
377
"""Open a branch which is not supported."""
378
return BzrDir.open(base, _unsupported=True)
381
def open(base, _unsupported=False):
382
"""Open an existing bzrdir, rooted at 'base' (url)
384
_unsupported is a private parameter to the BzrDir class.
386
t = get_transport(base)
387
mutter("trying to open %r with transport %r", base, t)
388
format = BzrDirFormat.find_format(t)
389
if not _unsupported and not format.is_supported():
390
# see open_downlevel to open legacy branches.
391
raise errors.UnsupportedFormatError(
392
'sorry, format %s not supported' % format,
393
['use a different bzr version',
394
'or remove the .bzr directory'
395
' and "bzr init" again'])
396
return format.open(t, _found=True)
398
def open_branch(self, unsupported=False):
399
"""Open the branch object at this BzrDir if one is present.
401
If unsupported is True, then no longer supported branch formats can
404
TODO: static convenience version of this?
406
raise NotImplementedError(self.open_branch)
409
def open_containing(url):
410
"""Open an existing branch which contains url.
412
:param url: url to search from.
413
See open_containing_from_transport for more detail.
415
return BzrDir.open_containing_from_transport(get_transport(url))
418
def open_containing_from_transport(a_transport):
419
"""Open an existing branch which contains a_transport.base
421
This probes for a branch at a_transport, and searches upwards from there.
423
Basically we keep looking up until we find the control directory or
424
run into the root. If there isn't one, raises NotBranchError.
425
If there is one and it is either an unrecognised format or an unsupported
426
format, UnknownFormatError or UnsupportedFormatError are raised.
427
If there is one, it is returned, along with the unused portion of url.
429
# this gets the normalised url back. I.e. '.' -> the full path.
430
url = a_transport.base
433
format = BzrDirFormat.find_format(a_transport)
434
return format.open(a_transport), a_transport.relpath(url)
435
except errors.NotBranchError, e:
436
mutter('not a branch in: %r %s', a_transport.base, e)
437
new_t = a_transport.clone('..')
438
if new_t.base == a_transport.base:
439
# reached the root, whatever that may be
440
raise errors.NotBranchError(path=url)
443
def open_repository(self, _unsupported=False):
444
"""Open the repository object at this BzrDir if one is present.
446
This will not follow the Branch object pointer - its strictly a direct
447
open facility. Most client code should use open_branch().repository to
450
_unsupported is a private parameter, not part of the api.
451
TODO: static convenience version of this?
453
raise NotImplementedError(self.open_repository)
455
def open_workingtree(self, _unsupported=False):
456
"""Open the workingtree object at this BzrDir if one is present.
458
TODO: static convenience version of this?
460
raise NotImplementedError(self.open_workingtree)
462
def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
463
"""Create a copy of this bzrdir prepared for use as a new line of
466
If urls last component does not exist, it will be created.
468
Attributes related to the identity of the source branch like
469
branch nickname will be cleaned, a working tree is created
470
whether one existed before or not; and a local branch is always
473
if revision_id is not None, then the clone operation may tune
474
itself to download less data.
477
result = self._format.initialize(url)
478
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
480
source_branch = self.open_branch()
481
source_repository = source_branch.repository
482
except errors.NotBranchError:
485
source_repository = self.open_repository()
486
except errors.NoRepositoryPresent:
487
# copy the entire basis one if there is one
488
# but there is no repository.
489
source_repository = basis_repo
494
result_repo = result.find_repository()
495
except errors.NoRepositoryPresent:
497
if source_repository is None and result_repo is not None:
499
elif source_repository is None and result_repo is None:
500
# no repo available, make a new one
501
result.create_repository()
502
elif source_repository is not None and result_repo is None:
503
# have soure, and want to make a new target repo
504
source_repository.clone(result,
505
revision_id=revision_id,
508
# fetch needed content into target.
510
# XXX FIXME RBC 20060214 need tests for this when the basis
512
result_repo.fetch(basis_repo, revision_id=revision_id)
513
result_repo.fetch(source_repository, revision_id=revision_id)
514
if source_branch is not None:
515
source_branch.sprout(result, revision_id=revision_id)
517
result.create_branch()
519
self.open_workingtree().clone(result,
520
revision_id=revision_id,
522
except (errors.NoWorkingTree, errors.NotLocalUrl):
523
result.create_workingtree()
527
class BzrDirPreSplitOut(BzrDir):
528
"""A common class for the all-in-one formats."""
530
def __init__(self, _transport, _format):
531
"""See BzrDir.__init__."""
532
super(BzrDirPreSplitOut, self).__init__(_transport, _format)
533
self._control_files = LockableFiles(self.get_branch_transport(None),
536
def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
537
"""See BzrDir.clone()."""
538
from bzrlib.workingtree import WorkingTreeFormat2
540
result = self._format.initialize(url, _cloning=True)
541
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
542
self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
543
self.open_branch().clone(result, revision_id=revision_id)
545
self.open_workingtree().clone(result, basis=basis_tree)
546
except errors.NotLocalUrl:
547
# make a new one, this format always has to have one.
548
WorkingTreeFormat2().initialize(result)
551
def create_branch(self):
552
"""See BzrDir.create_branch."""
553
return self.open_branch()
555
def create_repository(self, shared=False):
556
"""See BzrDir.create_repository."""
558
raise errors.IncompatibleFormat('shared repository', self._format)
559
return self.open_repository()
561
def create_workingtree(self, revision_id=None):
562
"""See BzrDir.create_workingtree."""
563
# this looks buggy but is not -really-
564
# clone and sprout will have set the revision_id
565
# and that will have set it for us, its only
566
# specific uses of create_workingtree in isolation
567
# that can do wonky stuff here, and that only
568
# happens for creating checkouts, which cannot be
569
# done on this format anyway. So - acceptable wart.
570
result = self.open_workingtree()
571
if revision_id is not None:
572
result.set_last_revision(revision_id)
575
def get_branch_transport(self, branch_format):
576
"""See BzrDir.get_branch_transport()."""
577
if branch_format is None:
578
return self.transport
580
branch_format.get_format_string()
581
except NotImplementedError:
582
return self.transport
583
raise errors.IncompatibleFormat(branch_format, self._format)
585
def get_repository_transport(self, repository_format):
586
"""See BzrDir.get_repository_transport()."""
587
if repository_format is None:
588
return self.transport
590
repository_format.get_format_string()
591
except NotImplementedError:
592
return self.transport
593
raise errors.IncompatibleFormat(repository_format, self._format)
595
def get_workingtree_transport(self, workingtree_format):
596
"""See BzrDir.get_workingtree_transport()."""
597
if workingtree_format is None:
598
return self.transport
600
workingtree_format.get_format_string()
601
except NotImplementedError:
602
return self.transport
603
raise errors.IncompatibleFormat(workingtree_format, self._format)
605
def open_branch(self, unsupported=False):
606
"""See BzrDir.open_branch."""
607
from bzrlib.branch import BzrBranchFormat4
608
format = BzrBranchFormat4()
609
self._check_supported(format, unsupported)
610
return format.open(self, _found=True)
612
def sprout(self, url, revision_id=None, basis=None):
613
"""See BzrDir.sprout()."""
614
from bzrlib.workingtree import WorkingTreeFormat2
616
result = self._format.initialize(url, _cloning=True)
617
basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
619
self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
620
except errors.NoRepositoryPresent:
623
self.open_branch().sprout(result, revision_id=revision_id)
624
except errors.NotBranchError:
627
self.open_workingtree().clone(result, basis=basis_tree)
628
except (errors.NotBranchError, errors.NotLocalUrl):
629
# we always want a working tree
630
WorkingTreeFormat2().initialize(result)
634
class BzrDir4(BzrDirPreSplitOut):
635
"""A .bzr version 4 control object.
637
This is a deprecated format and may be removed after sept 2006.
640
def create_repository(self, shared=False):
641
"""See BzrDir.create_repository."""
642
from bzrlib.repository import RepositoryFormat4
643
return RepositoryFormat4().initialize(self, shared)
645
def needs_format_update(self):
646
"""Format 4 dirs are always in need of updating."""
649
def open_repository(self):
650
"""See BzrDir.open_repository."""
651
from bzrlib.repository import RepositoryFormat4
652
return RepositoryFormat4().open(self, _found=True)
655
class BzrDir5(BzrDirPreSplitOut):
656
"""A .bzr version 5 control object.
658
This is a deprecated format and may be removed after sept 2006.
661
def open_repository(self):
662
"""See BzrDir.open_repository."""
663
from bzrlib.repository import RepositoryFormat5
664
return RepositoryFormat5().open(self, _found=True)
666
def open_workingtree(self, _unsupported=False):
667
"""See BzrDir.create_workingtree."""
668
from bzrlib.workingtree import WorkingTreeFormat2
669
return WorkingTreeFormat2().open(self, _found=True)
672
class BzrDir6(BzrDirPreSplitOut):
673
"""A .bzr version 6 control object.
675
This is a deprecated format and may be removed after sept 2006.
678
def open_repository(self):
679
"""See BzrDir.open_repository."""
680
from bzrlib.repository import RepositoryFormat6
681
return RepositoryFormat6().open(self, _found=True)
683
def open_workingtree(self, _unsupported=False):
684
"""See BzrDir.create_workingtree."""
685
from bzrlib.workingtree import WorkingTreeFormat2
686
return WorkingTreeFormat2().open(self, _found=True)
689
class BzrDirMeta1(BzrDir):
690
"""A .bzr meta version 1 control object.
692
This is the first control object where the
693
individual formats are really split out.
696
def create_branch(self):
697
"""See BzrDir.create_branch."""
698
from bzrlib.branch import BranchFormat
699
return BranchFormat.get_default_format().initialize(self)
701
def create_repository(self, shared=False):
702
"""See BzrDir.create_repository."""
703
from bzrlib.repository import RepositoryFormat
704
return RepositoryFormat.get_default_format().initialize(self, shared)
706
def create_workingtree(self, revision_id=None):
707
"""See BzrDir.create_workingtree."""
708
from bzrlib.workingtree import WorkingTreeFormat
709
return WorkingTreeFormat.get_default_format().initialize(self, revision_id)
711
def get_branch_transport(self, branch_format):
712
"""See BzrDir.get_branch_transport()."""
713
if branch_format is None:
714
return self.transport.clone('branch')
716
branch_format.get_format_string()
717
except NotImplementedError:
718
raise errors.IncompatibleFormat(branch_format, self._format)
720
self.transport.mkdir('branch')
721
except errors.FileExists:
723
return self.transport.clone('branch')
725
def get_repository_transport(self, repository_format):
726
"""See BzrDir.get_repository_transport()."""
727
if repository_format is None:
728
return self.transport.clone('repository')
730
repository_format.get_format_string()
731
except NotImplementedError:
732
raise errors.IncompatibleFormat(repository_format, self._format)
734
self.transport.mkdir('repository')
735
except errors.FileExists:
737
return self.transport.clone('repository')
739
def get_workingtree_transport(self, workingtree_format):
740
"""See BzrDir.get_workingtree_transport()."""
741
if workingtree_format is None:
742
return self.transport.clone('checkout')
744
workingtree_format.get_format_string()
745
except NotImplementedError:
746
raise errors.IncompatibleFormat(workingtree_format, self._format)
748
self.transport.mkdir('checkout')
749
except errors.FileExists:
751
return self.transport.clone('checkout')
753
def open_branch(self, unsupported=False):
754
"""See BzrDir.open_branch."""
755
from bzrlib.branch import BranchFormat
756
format = BranchFormat.find_format(self)
757
self._check_supported(format, unsupported)
758
return format.open(self, _found=True)
760
def open_repository(self, unsupported=False):
761
"""See BzrDir.open_repository."""
762
from bzrlib.repository import RepositoryFormat
763
format = RepositoryFormat.find_format(self)
764
self._check_supported(format, unsupported)
765
return format.open(self, _found=True)
767
def open_workingtree(self, unsupported=False):
768
"""See BzrDir.open_workingtree."""
769
from bzrlib.workingtree import WorkingTreeFormat
770
format = WorkingTreeFormat.find_format(self)
771
self._check_supported(format, unsupported)
772
return format.open(self, _found=True)
775
class BzrDirFormat(object):
776
"""An encapsulation of the initialization and open routines for a format.
778
Formats provide three things:
779
* An initialization routine,
783
Formats are placed in an dict by their format string for reference
784
during bzrdir opening. These should be subclasses of BzrDirFormat
787
Once a format is deprecated, just deprecate the initialize and open
788
methods on the format class. Do not deprecate the object, as the
789
object will be created every system load.
792
_default_format = None
793
"""The default format used for new .bzr dirs."""
796
"""The known formats."""
799
def find_format(klass, transport):
800
"""Return the format registered for URL."""
802
format_string = transport.get(".bzr/branch-format").read()
803
return klass._formats[format_string]
804
except errors.NoSuchFile:
805
raise errors.NotBranchError(path=transport.base)
807
raise errors.UnknownFormatError(format_string)
810
def get_default_format(klass):
811
"""Return the current default format."""
812
return klass._default_format
814
def get_format_string(self):
815
"""Return the ASCII format string that identifies this format."""
816
raise NotImplementedError(self.get_format_string)
818
def initialize(self, url):
819
"""Create a bzr control dir at this url and return an opened copy."""
820
# Since we don't have a .bzr directory, inherit the
821
# mode from the root directory
822
t = get_transport(url)
823
temp_control = LockableFiles(t, '')
824
temp_control._transport.mkdir('.bzr',
825
# FIXME: RBC 20060121 dont peek under
827
mode=temp_control._dir_mode)
828
file_mode = temp_control._file_mode
830
mutter('created control directory in ' + t.base)
831
control = t.clone('.bzr')
832
lock_file = 'branch-lock'
833
utf8_files = [('README',
834
"This is a Bazaar-NG control directory.\n"
835
"Do not change any files in this directory.\n"),
836
('branch-format', self.get_format_string()),
838
# NB: no need to escape relative paths that are url safe.
839
control.put(lock_file, StringIO(), mode=file_mode)
840
control_files = LockableFiles(control, lock_file)
841
control_files.lock_write()
843
for file, content in utf8_files:
844
control_files.put_utf8(file, content)
846
control_files.unlock()
847
return self.open(t, _found=True)
849
def is_supported(self):
850
"""Is this format supported?
852
Supported formats must be initializable and openable.
853
Unsupported formats may not support initialization or committing or
854
some other features depending on the reason for not being supported.
858
def open(self, transport, _found=False):
859
"""Return an instance of this format for the dir transport points at.
861
_found is a private parameter, do not use it.
864
assert isinstance(BzrDirFormat.find_format(transport),
866
return self._open(transport)
868
def _open(self, transport):
869
"""Template method helper for opening BzrDirectories.
871
This performs the actual open and any additional logic or parameter
874
raise NotImplementedError(self._open)
877
def register_format(klass, format):
878
klass._formats[format.get_format_string()] = format
881
def set_default_format(klass, format):
882
klass._default_format = format
885
return self.get_format_string()[:-1]
888
def unregister_format(klass, format):
889
assert klass._formats[format.get_format_string()] is format
890
del klass._formats[format.get_format_string()]
893
class BzrDirFormat4(BzrDirFormat):
896
This format is a combined format for working tree, branch and repository.
898
- Format 1 working trees [always]
899
- Format 4 branches [always]
900
- Format 4 repositories [always]
902
This format is deprecated: it indexes texts using a text it which is
903
removed in format 5; write support for this format has been removed.
906
def get_format_string(self):
907
"""See BzrDirFormat.get_format_string()."""
908
return "Bazaar-NG branch, format 0.0.4\n"
910
def initialize(self, url):
911
"""Format 4 branches cannot be created."""
912
raise errors.UninitializableFormat(self)
914
def is_supported(self):
915
"""Format 4 is not supported.
917
It is not supported because the model changed from 4 to 5 and the
918
conversion logic is expensive - so doing it on the fly was not
923
def _open(self, transport):
924
"""See BzrDirFormat._open."""
925
return BzrDir4(transport, self)
928
class BzrDirFormat5(BzrDirFormat):
929
"""Bzr control format 5.
931
This format is a combined format for working tree, branch and repository.
933
- Format 2 working trees [always]
934
- Format 4 branches [always]
935
- Format 5 repositories [always]
936
Unhashed stores in the repository.
939
def get_format_string(self):
940
"""See BzrDirFormat.get_format_string()."""
941
return "Bazaar-NG branch, format 5\n"
943
def initialize(self, url, _cloning=False):
944
"""Format 5 dirs always have working tree, branch and repository.
946
Except when they are being cloned.
948
from bzrlib.branch import BzrBranchFormat4
949
from bzrlib.repository import RepositoryFormat5
950
from bzrlib.workingtree import WorkingTreeFormat2
951
result = super(BzrDirFormat5, self).initialize(url)
952
RepositoryFormat5().initialize(result, _internal=True)
954
BzrBranchFormat4().initialize(result)
955
WorkingTreeFormat2().initialize(result)
958
def _open(self, transport):
959
"""See BzrDirFormat._open."""
960
return BzrDir5(transport, self)
963
class BzrDirFormat6(BzrDirFormat):
964
"""Bzr control format 6.
966
This format is a combined format for working tree, branch and repository.
968
- Format 2 working trees [always]
969
- Format 4 branches [always]
970
- Format 6 repositories [always]
973
def get_format_string(self):
974
"""See BzrDirFormat.get_format_string()."""
975
return "Bazaar-NG branch, format 6\n"
977
def initialize(self, url, _cloning=False):
978
"""Format 6 dirs always have working tree, branch and repository.
980
Except when they are being cloned.
982
from bzrlib.branch import BzrBranchFormat4
983
from bzrlib.repository import RepositoryFormat6
984
from bzrlib.workingtree import WorkingTreeFormat2
985
result = super(BzrDirFormat6, self).initialize(url)
986
RepositoryFormat6().initialize(result, _internal=True)
988
BzrBranchFormat4().initialize(result)
990
WorkingTreeFormat2().initialize(result)
991
except errors.NotLocalUrl:
992
# emulate pre-check behaviour for working tree and silently
997
def _open(self, transport):
998
"""See BzrDirFormat._open."""
999
return BzrDir6(transport, self)
1002
class BzrDirMetaFormat1(BzrDirFormat):
1003
"""Bzr meta control format 1
1005
This is the first format with split out working tree, branch and repository
1008
- Format 3 working trees [optional]
1009
- Format 5 branches [optional]
1010
- Format 7 repositories [optional]
1013
def get_format_string(self):
1014
"""See BzrDirFormat.get_format_string()."""
1015
return "Bazaar-NG meta directory, format 1\n"
1017
def _open(self, transport):
1018
"""See BzrDirFormat._open."""
1019
return BzrDirMeta1(transport, self)
1022
BzrDirFormat.register_format(BzrDirFormat4())
1023
BzrDirFormat.register_format(BzrDirFormat5())
1024
BzrDirFormat.register_format(BzrDirMetaFormat1())
1025
__default_format = BzrDirFormat6()
1026
BzrDirFormat.register_format(__default_format)
1027
BzrDirFormat.set_default_format(__default_format)
1030
class BzrDirTestProviderAdapter(object):
1031
"""A tool to generate a suite testing multiple bzrdir formats at once.
1033
This is done by copying the test once for each transport and injecting
1034
the transport_server, transport_readonly_server, and bzrdir_format
1035
classes into each copy. Each copy is also given a new id() to make it
1039
def __init__(self, transport_server, transport_readonly_server, formats):
1040
self._transport_server = transport_server
1041
self._transport_readonly_server = transport_readonly_server
1042
self._formats = formats
1044
def adapt(self, test):
1045
result = TestSuite()
1046
for format in self._formats:
1047
new_test = deepcopy(test)
1048
new_test.transport_server = self._transport_server
1049
new_test.transport_readonly_server = self._transport_readonly_server
1050
new_test.bzrdir_format = format
1051
def make_new_test_id():
1052
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1053
return lambda: new_id
1054
new_test.id = make_new_test_id()
1055
result.addTest(new_test)
1059
class ScratchDir(BzrDir6):
1060
"""Special test class: a bzrdir that cleans up itself..
1062
>>> d = ScratchDir()
1063
>>> base = d.transport.base
1066
>>> b.transport.__del__()
1071
def __init__(self, files=[], dirs=[], transport=None):
1072
"""Make a test branch.
1074
This creates a temporary directory and runs init-tree in it.
1076
If any files are listed, they are created in the working copy.
1078
if transport is None:
1079
transport = bzrlib.transport.local.ScratchTransport()
1080
# local import for scope restriction
1081
BzrDirFormat6().initialize(transport.base)
1082
super(ScratchDir, self).__init__(transport, BzrDirFormat6())
1083
self.create_repository()
1084
self.create_branch()
1085
self.create_workingtree()
1087
super(ScratchDir, self).__init__(transport, BzrDirFormat6())
1089
# BzrBranch creates a clone to .bzr and then forgets about the
1090
# original transport. A ScratchTransport() deletes itself and
1091
# everything underneath it when it goes away, so we need to
1092
# grab a local copy to prevent that from happening
1093
self._transport = transport
1096
self._transport.mkdir(d)
1099
self._transport.put(f, 'content of %s' % f)
1103
>>> orig = ScratchDir(files=["file1", "file2"])
1104
>>> os.listdir(orig.base)
1105
[u'.bzr', u'file1', u'file2']
1106
>>> clone = orig.clone()
1107
>>> if os.name != 'nt':
1108
... os.path.samefile(orig.base, clone.base)
1110
... orig.base == clone.base
1113
>>> os.listdir(clone.base)
1114
[u'.bzr', u'file1', u'file2']
1116
from shutil import copytree
1117
from bzrlib.osutils import mkdtemp
1120
copytree(self.base, base, symlinks=True)
1122
transport=bzrlib.transport.local.ScratchTransport(base))
1125
class Converter(object):
1126
"""Converts a disk format object from one format to another."""
1128
def __init__(self, pb):
1129
"""Create a converter.
1131
:param pb: a progress bar to use for progress information.
1136
class ConvertBzrDir4To5(Converter):
1137
"""Converts format 4 bzr dirs to format 5."""
1139
def __init__(self, to_convert, pb):
1140
"""Create a converter.
1142
:param to_convert: The disk object to convert.
1143
:param pb: a progress bar to use for progress information.
1145
super(ConvertBzrDir4To5, self).__init__(pb)
1146
self.bzrdir = to_convert
1147
self.converted_revs = set()
1148
self.absent_revisions = set()
1153
"""See Converter.convert()."""
1154
self.pb.note('starting upgrade from format 4 to 5')
1155
if isinstance(self.bzrdir.transport, LocalTransport):
1156
self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
1157
self._convert_to_weaves()
1158
return BzrDir.open(self.bzrdir.root_transport.base)
1160
def _convert_to_weaves(self):
1161
self.pb.note('note: upgrade may be faster if all store files are ungzipped first')
1164
stat = self.bzrdir.transport.stat('weaves')
1165
if not S_ISDIR(stat.st_mode):
1166
self.bzrdir.transport.delete('weaves')
1167
self.bzrdir.transport.mkdir('weaves')
1168
except errors.NoSuchFile:
1169
self.bzrdir.transport.mkdir('weaves')
1170
self.inv_weave = Weave('inventory')
1171
# holds in-memory weaves for all files
1172
self.text_weaves = {}
1173
self.bzrdir.transport.delete('branch-format')
1174
self.branch = self.bzrdir.open_branch()
1175
self._convert_working_inv()
1176
rev_history = self.branch.revision_history()
1177
# to_read is a stack holding the revisions we still need to process;
1178
# appending to it adds new highest-priority revisions
1179
self.known_revisions = set(rev_history)
1180
self.to_read = rev_history[-1:]
1182
rev_id = self.to_read.pop()
1183
if (rev_id not in self.revisions
1184
and rev_id not in self.absent_revisions):
1185
self._load_one_rev(rev_id)
1187
to_import = self._make_order()
1188
for i, rev_id in enumerate(to_import):
1189
self.pb.update('converting revision', i, len(to_import))
1190
self._convert_one_rev(rev_id)
1192
self._write_all_weaves()
1193
self._write_all_revs()
1194
self.pb.note('upgraded to weaves:')
1195
self.pb.note(' %6d revisions and inventories', len(self.revisions))
1196
self.pb.note(' %6d revisions not present', len(self.absent_revisions))
1197
self.pb.note(' %6d texts', self.text_count)
1198
self._cleanup_spare_files_after_format4()
1199
self.branch.control_files.put_utf8('branch-format', BzrDirFormat5().get_format_string())
1201
def _cleanup_spare_files_after_format4(self):
1202
# FIXME working tree upgrade foo.
1203
for n in 'merged-patches', 'pending-merged-patches':
1205
## assert os.path.getsize(p) == 0
1206
self.bzrdir.transport.delete(n)
1207
except errors.NoSuchFile:
1209
self.bzrdir.transport.delete_tree('inventory-store')
1210
self.bzrdir.transport.delete_tree('text-store')
1212
def _convert_working_inv(self):
1213
inv = serializer_v4.read_inventory(self.branch.control_files.get('inventory'))
1214
new_inv_xml = serializer_v5.write_inventory_to_string(inv)
1215
# FIXME inventory is a working tree change.
1216
self.branch.control_files.put('inventory', new_inv_xml)
1218
def _write_all_weaves(self):
1219
controlweaves = WeaveStore(self.bzrdir.transport, prefixed=False)
1220
weave_transport = self.bzrdir.transport.clone('weaves')
1221
weaves = WeaveStore(weave_transport, prefixed=False)
1222
transaction = PassThroughTransaction()
1224
controlweaves.put_weave('inventory', self.inv_weave, transaction)
1227
for file_id, file_weave in self.text_weaves.items():
1228
self.pb.update('writing weave', i, len(self.text_weaves))
1229
weaves.put_weave(file_id, file_weave, transaction)
1234
def _write_all_revs(self):
1235
"""Write all revisions out in new form."""
1236
self.bzrdir.transport.delete_tree('revision-store')
1237
self.bzrdir.transport.mkdir('revision-store')
1238
revision_transport = self.bzrdir.transport.clone('revision-store')
1240
revision_store = TextStore(revision_transport,
1244
for i, rev_id in enumerate(self.converted_revs):
1245
self.pb.update('write revision', i, len(self.converted_revs))
1246
rev_tmp = StringIO()
1247
serializer_v5.write_revision(self.revisions[rev_id], rev_tmp)
1249
revision_store.add(rev_tmp, rev_id)
1254
def _load_one_rev(self, rev_id):
1255
"""Load a revision object into memory.
1257
Any parents not either loaded or abandoned get queued to be
1259
self.pb.update('loading revision',
1260
len(self.revisions),
1261
len(self.known_revisions))
1262
if not self.branch.repository.revision_store.has_id(rev_id):
1264
self.pb.note('revision {%s} not present in branch; '
1265
'will be converted as a ghost',
1267
self.absent_revisions.add(rev_id)
1269
rev_xml = self.branch.repository.revision_store.get(rev_id).read()
1270
rev = serializer_v4.read_revision_from_string(rev_xml)
1271
for parent_id in rev.parent_ids:
1272
self.known_revisions.add(parent_id)
1273
self.to_read.append(parent_id)
1274
self.revisions[rev_id] = rev
1277
def _load_old_inventory(self, rev_id):
1278
assert rev_id not in self.converted_revs
1279
old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
1280
inv = serializer_v4.read_inventory_from_string(old_inv_xml)
1281
rev = self.revisions[rev_id]
1282
if rev.inventory_sha1:
1283
assert rev.inventory_sha1 == sha_string(old_inv_xml), \
1284
'inventory sha mismatch for {%s}' % rev_id
1288
def _load_updated_inventory(self, rev_id):
1289
assert rev_id in self.converted_revs
1290
inv_xml = self.inv_weave.get_text(rev_id)
1291
inv = serializer_v5.read_inventory_from_string(inv_xml)
1295
def _convert_one_rev(self, rev_id):
1296
"""Convert revision and all referenced objects to new format."""
1297
rev = self.revisions[rev_id]
1298
inv = self._load_old_inventory(rev_id)
1299
present_parents = [p for p in rev.parent_ids
1300
if p not in self.absent_revisions]
1301
self._convert_revision_contents(rev, inv, present_parents)
1302
self._store_new_weave(rev, inv, present_parents)
1303
self.converted_revs.add(rev_id)
1306
def _store_new_weave(self, rev, inv, present_parents):
1307
# the XML is now updated with text versions
1311
if ie.kind == 'root_directory':
1313
assert hasattr(ie, 'revision'), \
1314
'no revision on {%s} in {%s}' % \
1315
(file_id, rev.revision_id)
1316
new_inv_xml = serializer_v5.write_inventory_to_string(inv)
1317
new_inv_sha1 = sha_string(new_inv_xml)
1318
self.inv_weave.add(rev.revision_id,
1320
new_inv_xml.splitlines(True),
1322
rev.inventory_sha1 = new_inv_sha1
1324
def _convert_revision_contents(self, rev, inv, present_parents):
1325
"""Convert all the files within a revision.
1327
Also upgrade the inventory to refer to the text revision ids."""
1328
rev_id = rev.revision_id
1329
mutter('converting texts of revision {%s}',
1331
parent_invs = map(self._load_updated_inventory, present_parents)
1334
self._convert_file_version(rev, ie, parent_invs)
1336
def _convert_file_version(self, rev, ie, parent_invs):
1337
"""Convert one version of one file.
1339
The file needs to be added into the weave if it is a merge
1340
of >=2 parents or if it's changed from its parent.
1342
if ie.kind == 'root_directory':
1344
file_id = ie.file_id
1345
rev_id = rev.revision_id
1346
w = self.text_weaves.get(file_id)
1349
self.text_weaves[file_id] = w
1350
text_changed = False
1351
previous_entries = ie.find_previous_heads(parent_invs, w)
1352
for old_revision in previous_entries:
1353
# if this fails, its a ghost ?
1354
assert old_revision in self.converted_revs
1355
self.snapshot_ie(previous_entries, ie, w, rev_id)
1357
assert getattr(ie, 'revision', None) is not None
1359
def snapshot_ie(self, previous_revisions, ie, w, rev_id):
1360
# TODO: convert this logic, which is ~= snapshot to
1361
# a call to:. This needs the path figured out. rather than a work_tree
1362
# a v4 revision_tree can be given, or something that looks enough like
1363
# one to give the file content to the entry if it needs it.
1364
# and we need something that looks like a weave store for snapshot to
1366
#ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
1367
if len(previous_revisions) == 1:
1368
previous_ie = previous_revisions.values()[0]
1369
if ie._unchanged(previous_ie):
1370
ie.revision = previous_ie.revision
1372
parent_indexes = map(w.lookup, previous_revisions)
1374
text = self.branch.repository.text_store.get(ie.text_id)
1375
file_lines = text.readlines()
1376
assert sha_strings(file_lines) == ie.text_sha1
1377
assert sum(map(len, file_lines)) == ie.text_size
1378
w.add(rev_id, parent_indexes, file_lines, ie.text_sha1)
1379
self.text_count += 1
1381
w.add(rev_id, parent_indexes, [], None)
1382
ie.revision = rev_id
1383
##mutter('import text {%s} of {%s}',
1384
## ie.text_id, file_id)
1386
def _make_order(self):
1387
"""Return a suitable order for importing revisions.
1389
The order must be such that an revision is imported after all
1390
its (present) parents.
1392
todo = set(self.revisions.keys())
1393
done = self.absent_revisions.copy()
1396
# scan through looking for a revision whose parents
1398
for rev_id in sorted(list(todo)):
1399
rev = self.revisions[rev_id]
1400
parent_ids = set(rev.parent_ids)
1401
if parent_ids.issubset(done):
1402
# can take this one now
1409
class ConvertBzrDir5To6(Converter):
1410
"""Converts format 5 bzr dirs to format 6."""
1412
def __init__(self, to_convert, pb):
1413
"""Create a converter.
1415
:param to_convert: The disk object to convert.
1416
:param pb: a progress bar to use for progress information.
1418
super(ConvertBzrDir5To6, self).__init__(pb)
1419
self.bzrdir = to_convert
1422
"""See Converter.convert()."""
1423
self.pb.note('starting upgrade from format 5 to 6')
1424
self._convert_to_prefixed()
1425
return BzrDir.open(self.bzrdir.root_transport.base)
1427
def _convert_to_prefixed(self):
1428
from bzrlib.store import hash_prefix
1429
self.bzrdir.transport.delete('branch-format')
1430
for store_name in ["weaves", "revision-store"]:
1431
self.pb.note("adding prefixes to %s" % store_name)
1432
store_transport = self.bzrdir.transport.clone(store_name)
1433
for filename in store_transport.list_dir('.'):
1434
if (filename.endswith(".weave") or
1435
filename.endswith(".gz") or
1436
filename.endswith(".sig")):
1437
file_id = os.path.splitext(filename)[0]
1440
prefix_dir = hash_prefix(file_id)
1441
# FIXME keep track of the dirs made RBC 20060121
1443
store_transport.move(filename, prefix_dir + '/' + filename)
1444
except errors.NoSuchFile: # catches missing dirs strangely enough
1445
store_transport.mkdir(prefix_dir)
1446
store_transport.move(filename, prefix_dir + '/' + filename)
1447
self.bzrdir._control_files.put_utf8('branch-format', BzrDirFormat6().get_format_string())