1
# Copyright (C) 2005, 2006 Canonical Ltd
1
# Copyright (C) 2005 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
from copy import deepcopy
19
from cStringIO import StringIO
24
from unittest import TestSuite
25
22
from warnings import warn
23
from cStringIO import StringIO
28
import bzrlib.bzrdir as bzrdir
29
from bzrlib.config import TreeConfig
30
from bzrlib.decorators import needs_read_lock, needs_write_lock
31
from bzrlib.delta import compare_trees
27
import bzrlib.inventory as inventory
28
from bzrlib.trace import mutter, note
29
from bzrlib.osutils import (isdir, quotefn,
30
rename, splitpath, sha_file, appendpath,
32
32
import bzrlib.errors as errors
33
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
34
NoSuchRevision, HistoryMissing, NotBranchError,
35
DivergedBranches, LockError,
36
UninitializableFormat,
35
DivergedBranches, LockError, UnlistableStore,
38
36
UnlistableBranch, NoSuchFile, NotVersionedError,
40
import bzrlib.inventory as inventory
38
from bzrlib.textui import show_status
39
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
42
from bzrlib.delta import compare_trees
43
from bzrlib.tree import EmptyTree, RevisionTree
41
44
from bzrlib.inventory import Inventory
42
from bzrlib.lockable_files import LockableFiles, TransportLock
43
from bzrlib.lockdir import LockDir
44
from bzrlib.osutils import (isdir, quotefn,
45
rename, splitpath, sha_file,
46
file_kind, abspath, normpath, pathjoin,
50
from bzrlib.repository import Repository
51
from bzrlib.revision import (
56
45
from bzrlib.store import copy_all
57
from bzrlib.symbol_versioning import *
58
from bzrlib.textui import show_status
59
from bzrlib.trace import mutter, note
46
from bzrlib.store.text import TextStore
47
from bzrlib.store.weave import WeaveStore
48
from bzrlib.testament import Testament
60
49
import bzrlib.transactions as transactions
61
50
from bzrlib.transport import Transport, get_transport
62
from bzrlib.tree import EmptyTree, RevisionTree
64
import bzrlib.urlutils as urlutils
53
from config import TreeConfig
68
56
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
69
57
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
70
58
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
73
# TODO: Maybe include checks for common corruption of newlines, etc?
59
## TODO: Maybe include checks for common corruption of newlines, etc?
75
62
# TODO: Some operations like log might retrieve the same revisions
76
63
# repeatedly to calculate deltas. We could perhaps have a weakref
77
64
# cache in memory to make this faster. In general anything can be
78
# cached in memory between lock and unlock operations. .. nb thats
79
# what the transaction identity map provides
65
# cached in memory between lock and unlock operations.
67
def find_branch(*ignored, **ignored_too):
68
# XXX: leave this here for about one release, then remove it
69
raise NotImplementedError('find_branch() is not supported anymore, '
70
'please use one of the new branch constructors')
73
def needs_read_lock(unbound):
74
"""Decorate unbound to take out and release a read lock."""
75
def decorated(self, *args, **kwargs):
78
return unbound(self, *args, **kwargs)
84
def needs_write_lock(unbound):
85
"""Decorate unbound to take out and release a write lock."""
86
def decorated(self, *args, **kwargs):
89
return unbound(self, *args, **kwargs)
82
94
######################################################################
197
194
raise NotImplementedError('abspath is abstract')
199
def bind(self, other):
200
"""Bind the local branch the other branch.
202
:param other: The branch to bind to
205
raise errors.UpgradeRequired(self.base)
208
def fetch(self, from_branch, last_revision=None, pb=None):
209
"""Copy revisions from from_branch into this branch.
211
:param from_branch: Where to copy from.
212
:param last_revision: What revision to stop at (None for at the end
214
:param pb: An optional progress bar to use.
216
Returns the copied revision count and the failed revisions in a tuple:
219
if self.base == from_branch.base:
222
nested_pb = bzrlib.ui.ui_factory.nested_progress_bar()
227
from_branch.lock_read()
229
if last_revision is None:
230
pb.update('get source history')
231
from_history = from_branch.revision_history()
233
last_revision = from_history[-1]
235
# no history in the source branch
236
last_revision = NULL_REVISION
237
return self.repository.fetch(from_branch.repository,
238
revision_id=last_revision,
241
if nested_pb is not None:
245
def get_bound_location(self):
246
"""Return the URL of the branch we are bound to.
248
Older format branches cannot bind, please be sure to use a metadir
253
def get_commit_builder(self, parents, config=None, timestamp=None,
254
timezone=None, committer=None, revprops=None,
256
"""Obtain a CommitBuilder for this branch.
258
:param parents: Revision ids of the parents of the new revision.
259
:param config: Optional configuration to use.
260
:param timestamp: Optional timestamp recorded for commit.
261
:param timezone: Optional timezone for timestamp.
262
:param committer: Optional committer to set for commit.
263
:param revprops: Optional dictionary of revision properties.
264
:param revision_id: Optional revision id.
268
config = bzrlib.config.BranchConfig(self)
270
return self.repository.get_commit_builder(self, parents, config,
271
timestamp, timezone, committer, revprops, revision_id)
273
def get_master_branch(self):
274
"""Return the branch we are bound to.
276
:return: Either a Branch, or None
196
def controlfilename(self, file_or_path):
197
"""Return location relative to branch."""
198
raise NotImplementedError('controlfilename is abstract')
200
def controlfile(self, file_or_path, mode='r'):
201
"""Open a control file for this branch.
203
There are two classes of file in the control directory: text
204
and binary. binary files are untranslated byte streams. Text
205
control files are stored with Unix newlines and in UTF-8, even
206
if the platform or locale defaults are different.
208
Controlfiles should almost never be opened in write mode but
209
rather should be atomically copied and replaced using atomicfile.
211
raise NotImplementedError('controlfile is abstract')
213
def put_controlfile(self, path, f, encode=True):
214
"""Write an entry as a controlfile.
216
:param path: The path to put the file, relative to the .bzr control
218
:param f: A file-like or string object whose contents should be copied.
219
:param encode: If true, encode the contents as utf-8
221
raise NotImplementedError('put_controlfile is abstract')
223
def put_controlfiles(self, files, encode=True):
224
"""Write several entries as controlfiles.
226
:param files: A list of [(path, file)] pairs, where the path is the directory
227
underneath the bzr control directory
228
:param encode: If true, encode the contents as utf-8
230
raise NotImplementedError('put_controlfiles is abstract')
280
232
def get_root_id(self):
281
233
"""Return the id of this branches root"""
282
234
raise NotImplementedError('get_root_id is abstract')
236
def set_root_id(self, file_id):
237
raise NotImplementedError('set_root_id is abstract')
284
239
def print_file(self, file, revision_id):
285
240
"""Print `file` to stdout."""
286
241
raise NotImplementedError('print_file is abstract')
470
481
if revno < 1 or revno > self.revno():
471
482
raise InvalidRevisionNumber(revno)
474
def clone(self, *args, **kwargs):
475
"""Clone this branch into to_bzrdir preserving all semantic values.
477
revision_id: if not None, the revision history in the new branch will
478
be truncated to end with revision_id.
480
# for API compatibility, until 0.8 releases we provide the old api:
481
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
482
# after 0.8 releases, the *args and **kwargs should be changed:
483
# def clone(self, to_bzrdir, revision_id=None):
484
if (kwargs.get('to_location', None) or
485
kwargs.get('revision', None) or
486
kwargs.get('basis_branch', None) or
487
(len(args) and isinstance(args[0], basestring))):
488
# backwards compatibility api:
489
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
490
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
493
basis_branch = args[2]
495
basis_branch = kwargs.get('basis_branch', None)
497
basis = basis_branch.bzrdir
502
revision_id = args[1]
504
revision_id = kwargs.get('revision', None)
509
# no default to raise if not provided.
510
url = kwargs.get('to_location')
511
return self.bzrdir.clone(url,
512
revision_id=revision_id,
513
basis=basis).open_branch()
515
# generate args by hand
517
revision_id = args[1]
519
revision_id = kwargs.get('revision_id', None)
523
# no default to raise if not provided.
524
to_bzrdir = kwargs.get('to_bzrdir')
525
result = self._format.initialize(to_bzrdir)
526
self.copy_content_into(result, revision_id=revision_id)
530
def sprout(self, to_bzrdir, revision_id=None):
531
"""Create a new line of development from the branch, into to_bzrdir.
533
revision_id: if not None, the revision history in the new branch will
534
be truncated to end with revision_id.
536
result = self._format.initialize(to_bzrdir)
537
self.copy_content_into(result, revision_id=revision_id)
538
result.set_parent(self.bzrdir.root_transport.base)
542
def copy_content_into(self, destination, revision_id=None):
543
"""Copy the content of self into destination.
545
revision_id: if not None, the revision history in the new branch will
546
be truncated to end with revision_id.
548
new_history = self.revision_history()
549
if revision_id is not None:
551
new_history = new_history[:new_history.index(revision_id) + 1]
553
rev = self.repository.get_revision(revision_id)
554
new_history = rev.get_history(self.repository)[1:]
555
destination.set_revision_history(new_history)
556
parent = self.get_parent()
558
destination.set_parent(parent)
562
"""Check consistency of the branch.
564
In particular this checks that revisions given in the revision-history
565
do actually match up in the revision graph, and that they're all
566
present in the repository.
568
:return: A BranchCheckResult.
570
mainline_parent_id = None
571
for revision_id in self.revision_history():
573
revision = self.repository.get_revision(revision_id)
574
except errors.NoSuchRevision, e:
575
raise BzrCheckError("mainline revision {%s} not in repository"
577
# In general the first entry on the revision history has no parents.
578
# But it's not illegal for it to have parents listed; this can happen
579
# in imports from Arch when the parents weren't reachable.
580
if mainline_parent_id is not None:
581
if mainline_parent_id not in revision.parent_ids:
582
raise BzrCheckError("previous revision {%s} not listed among "
584
% (mainline_parent_id, revision_id))
585
mainline_parent_id = revision_id
586
return BranchCheckResult(self)
589
class BranchFormat(object):
590
"""An encapsulation of the initialization and open routines for a format.
592
Formats provide three things:
593
* An initialization routine,
597
Formats are placed in an dict by their format string for reference
598
during branch opening. Its not required that these be instances, they
599
can be classes themselves with class methods - it simply depends on
600
whether state is needed for a given format or not.
602
Once a format is deprecated, just deprecate the initialize and open
603
methods on the format class. Do not deprecate the object, as the
604
object will be created every time regardless.
607
_default_format = None
608
"""The default format used for new branches."""
611
"""The known formats."""
614
def find_format(klass, a_bzrdir):
615
"""Return the format for the branch object in a_bzrdir."""
617
transport = a_bzrdir.get_branch_transport(None)
618
format_string = transport.get("format").read()
619
return klass._formats[format_string]
621
raise NotBranchError(path=transport.base)
623
raise errors.UnknownFormatError(format_string)
626
def get_default_format(klass):
627
"""Return the current default format."""
628
return klass._default_format
630
def get_format_string(self):
631
"""Return the ASCII format string that identifies this format."""
632
raise NotImplementedError(self.get_format_string)
634
def get_format_description(self):
635
"""Return the short format description for this format."""
636
raise NotImplementedError(self.get_format_string)
638
def initialize(self, a_bzrdir):
639
"""Create a branch of this format in a_bzrdir."""
640
raise NotImplementedError(self.initialize)
642
def is_supported(self):
643
"""Is this format supported?
645
Supported formats can be initialized and opened.
646
Unsupported formats may not support initialization or committing or
647
some other features depending on the reason for not being supported.
651
def open(self, a_bzrdir, _found=False):
652
"""Return the branch object for a_bzrdir
654
_found is a private parameter, do not use it. It is used to indicate
655
if format probing has already be done.
657
raise NotImplementedError(self.open)
660
def register_format(klass, format):
661
klass._formats[format.get_format_string()] = format
664
def set_default_format(klass, format):
665
klass._default_format = format
668
def unregister_format(klass, format):
669
assert klass._formats[format.get_format_string()] is format
670
del klass._formats[format.get_format_string()]
673
return self.get_format_string().rstrip()
676
class BzrBranchFormat4(BranchFormat):
677
"""Bzr branch format 4.
680
- a revision-history file.
681
- a branch-lock lock file [ to be shared with the bzrdir ]
684
def get_format_description(self):
685
"""See BranchFormat.get_format_description()."""
686
return "Branch format 4"
688
def initialize(self, a_bzrdir):
689
"""Create a branch of this format in a_bzrdir."""
690
mutter('creating branch in %s', a_bzrdir.transport.base)
691
branch_transport = a_bzrdir.get_branch_transport(self)
692
utf8_files = [('revision-history', ''),
695
control_files = LockableFiles(branch_transport, 'branch-lock',
697
control_files.create_lock()
698
control_files.lock_write()
700
for file, content in utf8_files:
701
control_files.put_utf8(file, content)
703
control_files.unlock()
704
return self.open(a_bzrdir, _found=True)
707
super(BzrBranchFormat4, self).__init__()
708
self._matchingbzrdir = bzrdir.BzrDirFormat6()
710
def open(self, a_bzrdir, _found=False):
711
"""Return the branch object for a_bzrdir
713
_found is a private parameter, do not use it. It is used to indicate
714
if format probing has already be done.
717
# we are being called directly and must probe.
718
raise NotImplementedError
719
return BzrBranch(_format=self,
720
_control_files=a_bzrdir._control_files,
722
_repository=a_bzrdir.open_repository())
725
return "Bazaar-NG branch format 4"
728
class BzrBranchFormat5(BranchFormat):
729
"""Bzr branch format 5.
732
- a revision-history file.
734
- a lock dir guarding the branch itself
735
- all of this stored in a branch/ subdirectory
736
- works with shared repositories.
738
This format is new in bzr 0.8.
741
def get_format_string(self):
742
"""See BranchFormat.get_format_string()."""
743
return "Bazaar-NG branch format 5\n"
745
def get_format_description(self):
746
"""See BranchFormat.get_format_description()."""
747
return "Branch format 5"
749
def initialize(self, a_bzrdir):
750
"""Create a branch of this format in a_bzrdir."""
751
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
752
branch_transport = a_bzrdir.get_branch_transport(self)
753
utf8_files = [('revision-history', ''),
756
control_files = LockableFiles(branch_transport, 'lock', LockDir)
757
control_files.create_lock()
758
control_files.lock_write()
759
control_files.put_utf8('format', self.get_format_string())
761
for file, content in utf8_files:
762
control_files.put_utf8(file, content)
764
control_files.unlock()
765
return self.open(a_bzrdir, _found=True, )
768
super(BzrBranchFormat5, self).__init__()
769
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
771
def open(self, a_bzrdir, _found=False):
772
"""Return the branch object for a_bzrdir
774
_found is a private parameter, do not use it. It is used to indicate
775
if format probing has already be done.
778
format = BranchFormat.find_format(a_bzrdir)
779
assert format.__class__ == self.__class__
780
transport = a_bzrdir.get_branch_transport(None)
781
control_files = LockableFiles(transport, 'lock', LockDir)
782
return BzrBranch5(_format=self,
783
_control_files=control_files,
785
_repository=a_bzrdir.find_repository())
788
return "Bazaar-NG Metadir branch format 5"
791
class BranchReferenceFormat(BranchFormat):
792
"""Bzr branch reference format.
794
Branch references are used in implementing checkouts, they
795
act as an alias to the real branch which is at some other url.
802
def get_format_string(self):
803
"""See BranchFormat.get_format_string()."""
804
return "Bazaar-NG Branch Reference Format 1\n"
806
def get_format_description(self):
807
"""See BranchFormat.get_format_description()."""
808
return "Checkout reference format 1"
810
def initialize(self, a_bzrdir, target_branch=None):
811
"""Create a branch of this format in a_bzrdir."""
812
if target_branch is None:
813
# this format does not implement branch itself, thus the implicit
814
# creation contract must see it as uninitializable
815
raise errors.UninitializableFormat(self)
816
mutter('creating branch reference in %s', a_bzrdir.transport.base)
817
branch_transport = a_bzrdir.get_branch_transport(self)
818
# FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
819
branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
820
branch_transport.put('format', StringIO(self.get_format_string()))
821
return self.open(a_bzrdir, _found=True)
824
super(BranchReferenceFormat, self).__init__()
825
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
827
def _make_reference_clone_function(format, a_branch):
828
"""Create a clone() routine for a branch dynamically."""
829
def clone(to_bzrdir, revision_id=None):
830
"""See Branch.clone()."""
831
return format.initialize(to_bzrdir, a_branch)
832
# cannot obey revision_id limits when cloning a reference ...
833
# FIXME RBC 20060210 either nuke revision_id for clone, or
834
# emit some sort of warning/error to the caller ?!
837
def open(self, a_bzrdir, _found=False):
838
"""Return the branch that the branch reference in a_bzrdir points at.
840
_found is a private parameter, do not use it. It is used to indicate
841
if format probing has already be done.
844
format = BranchFormat.find_format(a_bzrdir)
845
assert format.__class__ == self.__class__
846
transport = a_bzrdir.get_branch_transport(None)
847
real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
848
result = real_bzrdir.open_branch()
849
# this changes the behaviour of result.clone to create a new reference
850
# rather than a copy of the content of the branch.
851
# I did not use a proxy object because that needs much more extensive
852
# testing, and we are only changing one behaviour at the moment.
853
# If we decide to alter more behaviours - i.e. the implicit nickname
854
# then this should be refactored to introduce a tested proxy branch
855
# and a subclass of that for use in overriding clone() and ....
857
result.clone = self._make_reference_clone_function(result)
861
# formats which have no format string are not discoverable
862
# and not independently creatable, so are not registered.
863
__default_format = BzrBranchFormat5()
864
BranchFormat.register_format(__default_format)
865
BranchFormat.register_format(BranchReferenceFormat())
866
BranchFormat.set_default_format(__default_format)
867
_legacy_formats = [BzrBranchFormat4(),
484
def sign_revision(self, revision_id, gpg_strategy):
485
raise NotImplementedError('sign_revision is abstract')
487
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
488
raise NotImplementedError('store_revision_signature is abstract')
870
490
class BzrBranch(Branch):
871
491
"""A branch stored in the actual filesystem.
890
552
version is not applied. This is intended only for
891
553
upgrade/recovery type use; it's not guaranteed that
892
554
all operations will work on old format branches.
556
In the test suite, creation of new trees is tested using the
557
`ScratchBranch` class.
895
self.bzrdir = bzrdir.BzrDir.open(transport.base)
897
self.bzrdir = a_bzrdir
898
self._transport = self.bzrdir.transport.clone('..')
899
self._base = self._transport.base
900
self._format = _format
901
if _control_files is None:
902
raise BzrBadParameterMissing('_control_files')
903
self.control_files = _control_files
904
if deprecated_passed(init):
905
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
906
"deprecated as of bzr 0.8. Please use Branch.create().",
910
# this is slower than before deprecation, oh well never mind.
912
self._initialize(transport.base)
913
self._check_format(_format)
914
if deprecated_passed(relax_version_check):
915
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
916
"relax_version_check parameter is deprecated as of bzr 0.8. "
917
"Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
921
if (not relax_version_check
922
and not self._format.is_supported()):
923
raise errors.UnsupportedFormatError(
924
'sorry, branch format %r not supported' % fmt,
925
['use a different bzr version',
926
'or remove the .bzr directory'
927
' and "bzr init" again'])
928
if deprecated_passed(transport):
929
warn("BzrBranch.__init__(transport=XXX...): The transport "
930
"parameter is deprecated as of bzr 0.8. "
931
"Please use Branch.open, or bzrdir.open_branch().",
934
self.repository = _repository
559
assert isinstance(transport, Transport), \
560
"%r is not a Transport" % transport
561
self._transport = transport
564
self._check_format(relax_version_check)
566
def get_store(name, compressed=True, prefixed=False):
567
# FIXME: This approach of assuming stores are all entirely compressed
568
# or entirely uncompressed is tidy, but breaks upgrade from
569
# some existing branches where there's a mixture; we probably
570
# still want the option to look for both.
571
relpath = self._rel_controlfilename(name)
572
store = TextStore(self._transport.clone(relpath),
574
compressed=compressed)
575
#if self._transport.should_cache():
576
# cache_path = os.path.join(self.cache_root, name)
577
# os.mkdir(cache_path)
578
# store = bzrlib.store.CachedStore(store, cache_path)
580
def get_weave(name, prefixed=False):
581
relpath = self._rel_controlfilename(name)
582
ws = WeaveStore(self._transport.clone(relpath), prefixed=prefixed)
583
if self._transport.should_cache():
584
ws.enable_cache = True
587
if self._branch_format == 4:
588
self.inventory_store = get_store(u'inventory-store')
589
self.text_store = get_store(u'text-store')
590
self.revision_store = get_store(u'revision-store')
591
elif self._branch_format == 5:
592
self.control_weaves = get_weave(u'')
593
self.weave_store = get_weave(u'weaves')
594
self.revision_store = get_store(u'revision-store', compressed=False)
595
elif self._branch_format == 6:
596
self.control_weaves = get_weave(u'')
597
self.weave_store = get_weave(u'weaves', prefixed=True)
598
self.revision_store = get_store(u'revision-store', compressed=False,
600
self.revision_store.register_suffix('sig')
601
self._transaction = None
936
603
def __str__(self):
937
return '%s(%r)' % (self.__class__.__name__, self.base)
604
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
939
606
__repr__ = __str__
941
608
def __del__(self):
609
if self._lock_mode or self._lock:
610
# XXX: This should show something every time, and be suitable for
611
# headless operation and embedding
612
warn("branch %r was not explicitly unlocked" % self)
942
615
# TODO: It might be best to do this somewhere else,
943
616
# but it is nice for a Branch object to automatically
944
617
# cache it's information.
945
618
# Alternatively, we could have the Transport objects cache requests
946
619
# See the earlier discussion about how major objects (like Branch)
947
620
# should never expect their __del__ function to run.
948
# XXX: cache_root seems to be unused, 2006-01-13 mbp
949
621
if hasattr(self, 'cache_root') and self.cache_root is not None:
951
rmtree(self.cache_root)
623
shutil.rmtree(self.cache_root)
954
626
self.cache_root = None
956
628
def _get_base(self):
630
return self._transport.base
959
633
base = property(_get_base, doc="The URL for the root of this branch.")
961
635
def _finish_transaction(self):
962
636
"""Exit the current transaction."""
963
return self.control_files._finish_transaction()
637
if self._transaction is None:
638
raise errors.LockError('Branch %s is not in a transaction' %
640
transaction = self._transaction
641
self._transaction = None
965
644
def get_transaction(self):
966
"""Return the current active transaction.
968
If no transaction is active, this returns a passthrough object
969
for which all data is immediately flushed and no caching happens.
971
# this is an explicit function so that we can do tricky stuff
972
# when the storage in rev_storage is elsewhere.
973
# we probably need to hook the two 'lock a location' and
974
# 'have a transaction' together more delicately, so that
975
# we can have two locks (branch and storage) and one transaction
976
# ... and finishing the transaction unlocks both, but unlocking
977
# does not. - RBC 20051121
978
return self.control_files.get_transaction()
980
def _set_transaction(self, transaction):
645
"""See Branch.get_transaction."""
646
if self._transaction is None:
647
return transactions.PassThroughTransaction()
649
return self._transaction
651
def _set_transaction(self, new_transaction):
981
652
"""Set a new active transaction."""
982
return self.control_files._set_transaction(transaction)
653
if self._transaction is not None:
654
raise errors.LockError('Branch %s is in a transaction already.' %
656
self._transaction = new_transaction
658
def lock_write(self):
659
#mutter("lock write: %s (%s)", self, self._lock_count)
660
# TODO: Upgrade locking to support using a Transport,
661
# and potentially a remote locking protocol
663
if self._lock_mode != 'w':
664
raise LockError("can't upgrade to a write lock from %r" %
666
self._lock_count += 1
668
self._lock = self._transport.lock_write(
669
self._rel_controlfilename('branch-lock'))
670
self._lock_mode = 'w'
672
self._set_transaction(transactions.PassThroughTransaction())
675
#mutter("lock read: %s (%s)", self, self._lock_count)
677
assert self._lock_mode in ('r', 'w'), \
678
"invalid lock mode %r" % self._lock_mode
679
self._lock_count += 1
681
self._lock = self._transport.lock_read(
682
self._rel_controlfilename('branch-lock'))
683
self._lock_mode = 'r'
685
self._set_transaction(transactions.ReadOnlyTransaction())
686
# 5K may be excessive, but hey, its a knob.
687
self.get_transaction().set_cache_size(5000)
690
#mutter("unlock: %s (%s)", self, self._lock_count)
691
if not self._lock_mode:
692
raise LockError('branch %r is not locked' % (self))
694
if self._lock_count > 1:
695
self._lock_count -= 1
697
self._finish_transaction()
700
self._lock_mode = self._lock_count = None
984
702
def abspath(self, name):
985
703
"""See Branch.abspath."""
986
return self.control_files._transport.abspath(name)
988
def _check_format(self, format):
989
"""Identify the branch format if needed.
991
The format is stored as a reference to the format object in
992
self._format for code that needs to check it later.
994
The format parameter is either None or the branch format class
995
used to open this branch.
997
FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
704
return self._transport.abspath(name)
706
def _rel_controlfilename(self, file_or_path):
707
if not isinstance(file_or_path, basestring):
708
file_or_path = u'/'.join(file_or_path)
709
if file_or_path == '':
711
return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
713
def controlfilename(self, file_or_path):
714
"""See Branch.controlfilename."""
715
return self._transport.abspath(self._rel_controlfilename(file_or_path))
717
def controlfile(self, file_or_path, mode='r'):
718
"""See Branch.controlfile."""
721
relpath = self._rel_controlfilename(file_or_path)
722
#TODO: codecs.open() buffers linewise, so it was overloaded with
723
# a much larger buffer, do we need to do the same for getreader/getwriter?
725
return self._transport.get(relpath)
727
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
729
# XXX: Do we really want errors='replace'? Perhaps it should be
730
# an error, or at least reported, if there's incorrectly-encoded
731
# data inside a file.
732
# <https://launchpad.net/products/bzr/+bug/3823>
733
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
735
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
737
raise BzrError("invalid controlfile mode %r" % mode)
739
def put_controlfile(self, path, f, encode=True):
740
"""See Branch.put_controlfile."""
741
self.put_controlfiles([(path, f)], encode=encode)
743
def put_controlfiles(self, files, encode=True):
744
"""See Branch.put_controlfiles."""
747
for path, f in files:
749
if isinstance(f, basestring):
750
f = f.encode('utf-8', 'replace')
752
f = codecs.getwriter('utf-8')(f, errors='replace')
753
path = self._rel_controlfilename(path)
754
ctrl_files.append((path, f))
755
self._transport.put_multi(ctrl_files)
757
def _make_control(self):
758
from bzrlib.inventory import Inventory
759
from bzrlib.weavefile import write_weave_v5
760
from bzrlib.weave import Weave
762
# Create an empty inventory
764
# if we want per-tree root ids then this is the place to set
765
# them; they're not needed for now and so ommitted for
767
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
768
empty_inv = sio.getvalue()
770
bzrlib.weavefile.write_weave_v5(Weave(), sio)
771
empty_weave = sio.getvalue()
773
dirs = [[], 'revision-store', 'weaves']
775
"This is a Bazaar-NG control directory.\n"
776
"Do not change any files in this directory.\n"),
777
('branch-format', BZR_BRANCH_FORMAT_6),
778
('revision-history', ''),
781
('pending-merges', ''),
782
('inventory', empty_inv),
783
('inventory.weave', empty_weave),
784
('ancestry.weave', empty_weave)
786
cfn = self._rel_controlfilename
787
self._transport.mkdir_multi([cfn(d) for d in dirs])
788
self.put_controlfiles(files)
789
mutter('created control directory in ' + self._transport.base)
791
def _check_format(self, relax_version_check):
792
"""Check this branch format is supported.
794
The format level is stored, as an integer, in
795
self._branch_format for code that needs to check it later.
797
In the future, we might need different in-memory Branch
798
classes to support downlevel branches. But not yet.
1000
format = BzrBranchFormat.find_format(self.bzrdir)
1001
self._format = format
1002
mutter("got branch format %s", self._format)
801
fmt = self.controlfile('branch-format', 'r').read()
803
raise NotBranchError(path=self.base)
804
mutter("got branch format %r", fmt)
805
if fmt == BZR_BRANCH_FORMAT_6:
806
self._branch_format = 6
807
elif fmt == BZR_BRANCH_FORMAT_5:
808
self._branch_format = 5
809
elif fmt == BZR_BRANCH_FORMAT_4:
810
self._branch_format = 4
812
if (not relax_version_check
813
and self._branch_format not in (5, 6)):
814
raise errors.UnsupportedFormatError(
815
'sorry, branch format %r not supported' % fmt,
816
['use a different bzr version',
817
'or remove the .bzr directory'
818
' and "bzr init" again'])
1004
820
@needs_read_lock
1005
821
def get_root_id(self):
1006
822
"""See Branch.get_root_id."""
1007
tree = self.repository.revision_tree(self.last_revision())
1008
return tree.inventory.root.file_id
1010
def is_locked(self):
1011
return self.control_files.is_locked()
1013
def lock_write(self):
1014
# TODO: test for failed two phase locks. This is known broken.
1015
self.control_files.lock_write()
1016
self.repository.lock_write()
1018
def lock_read(self):
1019
# TODO: test for failed two phase locks. This is known broken.
1020
self.control_files.lock_read()
1021
self.repository.lock_read()
1024
# TODO: test for failed two phase locks. This is known broken.
1026
self.repository.unlock()
1028
self.control_files.unlock()
1030
def peek_lock_mode(self):
1031
if self.control_files._lock_count == 0:
1034
return self.control_files._lock_mode
1036
def get_physical_lock_status(self):
1037
return self.control_files.get_physical_lock_status()
823
inv = self.get_inventory(self.last_revision())
824
return inv.root.file_id
1039
826
@needs_read_lock
1040
827
def print_file(self, file, revision_id):
1041
828
"""See Branch.print_file."""
1042
return self.repository.print_file(file, revision_id)
829
tree = self.revision_tree(revision_id)
830
# use inventory as it was in that revision
831
file_id = tree.inventory.path2id(file)
834
revno = self.revision_id_to_revno(revision_id)
835
except errors.NoSuchRevision:
836
# TODO: This should not be BzrError,
837
# but NoSuchFile doesn't fit either
838
raise BzrError('%r is not present in revision %s'
839
% (file, revision_id))
841
raise BzrError('%r is not present in revision %s'
843
tree.print_file(file_id)
1044
845
@needs_write_lock
1045
846
def append_revision(self, *revision_ids):
1053
854
@needs_write_lock
1054
855
def set_revision_history(self, rev_history):
1055
856
"""See Branch.set_revision_history."""
1056
self.control_files.put_utf8(
1057
'revision-history', '\n'.join(rev_history))
1058
transaction = self.get_transaction()
1059
history = transaction.map.find_revision_history()
1060
if history is not None:
1061
# update the revision history in the identity map.
1062
history[:] = list(rev_history)
1063
# this call is disabled because revision_history is
1064
# not really an object yet, and the transaction is for objects.
1065
# transaction.register_dirty(history)
1067
transaction.map.add_revision_history(rev_history)
1068
# this call is disabled because revision_history is
1069
# not really an object yet, and the transaction is for objects.
1070
# transaction.register_clean(history)
1072
def get_revision_delta(self, revno):
1073
"""Return the delta for one revision.
1075
The delta is relative to its mainline predecessor, or the
1076
empty tree for revision 1.
1078
assert isinstance(revno, int)
1079
rh = self.revision_history()
1080
if not (1 <= revno <= len(rh)):
1081
raise InvalidRevisionNumber(revno)
1083
# revno is 1-based; list is 0-based
1085
new_tree = self.repository.revision_tree(rh[revno-1])
1087
old_tree = EmptyTree()
1089
old_tree = self.repository.revision_tree(rh[revno-2])
1090
return compare_trees(old_tree, new_tree)
857
old_revision = self.last_revision()
858
new_revision = rev_history[-1]
859
self.put_controlfile('revision-history', '\n'.join(rev_history))
861
self.working_tree().set_last_revision(new_revision, old_revision)
862
except NoWorkingTree:
863
mutter('Unable to set_last_revision without a working tree.')
865
def has_revision(self, revision_id):
866
"""See Branch.has_revision."""
867
return (revision_id is None
868
or self.revision_store.has_id(revision_id))
871
def _get_revision_xml_file(self, revision_id):
872
if not revision_id or not isinstance(revision_id, basestring):
873
raise InvalidRevisionId(revision_id=revision_id, branch=self)
875
return self.revision_store.get(revision_id)
876
except (IndexError, KeyError):
877
raise bzrlib.errors.NoSuchRevision(self, revision_id)
879
def get_revision_xml(self, revision_id):
880
"""See Branch.get_revision_xml."""
881
return self._get_revision_xml_file(revision_id).read()
883
def get_revision(self, revision_id):
884
"""See Branch.get_revision."""
885
xml_file = self._get_revision_xml_file(revision_id)
888
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
889
except SyntaxError, e:
890
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
894
assert r.revision_id == revision_id
897
def get_revision_sha1(self, revision_id):
898
"""See Branch.get_revision_sha1."""
899
# In the future, revision entries will be signed. At that
900
# point, it is probably best *not* to include the signature
901
# in the revision hash. Because that lets you re-sign
902
# the revision, (add signatures/remove signatures) and still
903
# have all hash pointers stay consistent.
904
# But for now, just hash the contents.
905
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
907
def get_ancestry(self, revision_id):
908
"""See Branch.get_ancestry."""
909
if revision_id is None:
911
w = self._get_inventory_weave()
912
return [None] + map(w.idx_to_name,
913
w.inclusions([w.lookup(revision_id)]))
915
def _get_inventory_weave(self):
916
return self.control_weaves.get_weave('inventory',
917
self.get_transaction())
919
def get_inventory(self, revision_id):
920
"""See Branch.get_inventory."""
921
xml = self.get_inventory_xml(revision_id)
922
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
924
def get_inventory_xml(self, revision_id):
925
"""See Branch.get_inventory_xml."""
927
assert isinstance(revision_id, basestring), type(revision_id)
928
iw = self._get_inventory_weave()
929
return iw.get_text(iw.lookup(revision_id))
931
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
933
def get_inventory_sha1(self, revision_id):
934
"""See Branch.get_inventory_sha1."""
935
return self.get_revision(revision_id).inventory_sha1
937
def get_revision_inventory(self, revision_id):
938
"""See Branch.get_revision_inventory."""
939
# TODO: Unify this with get_inventory()
940
# bzr 0.0.6 and later imposes the constraint that the inventory_id
941
# must be the same as its revision, so this is trivial.
942
if revision_id == None:
943
# This does not make sense: if there is no revision,
944
# then it is the current tree inventory surely ?!
945
# and thus get_root_id() is something that looks at the last
946
# commit on the branch, and the get_root_id is an inventory check.
947
raise NotImplementedError
948
# return Inventory(self.get_root_id())
950
return self.get_inventory(revision_id)
1092
952
@needs_read_lock
1093
953
def revision_history(self):
1098
958
mutter("cache hit for revision-history in %s", self)
1099
959
return list(history)
1100
960
history = [l.rstrip('\r\n') for l in
1101
self.control_files.get_utf8('revision-history').readlines()]
961
self.controlfile('revision-history', 'r').readlines()]
1102
962
transaction.map.add_revision_history(history)
1103
963
# this call is disabled because revision_history is
1104
964
# not really an object yet, and the transaction is for objects.
1105
965
# transaction.register_clean(history, precious=True)
1106
966
return list(history)
1109
968
def update_revisions(self, other, stop_revision=None):
1110
969
"""See Branch.update_revisions."""
970
from bzrlib.fetch import greedy_fetch
971
if stop_revision is None:
972
stop_revision = other.last_revision()
973
### Should this be checking is_ancestor instead of revision_history?
974
if (stop_revision is not None and
975
stop_revision in self.revision_history()):
977
greedy_fetch(to_branch=self, from_branch=other,
978
revision=stop_revision)
979
pullable_revs = self.pullable_revisions(other, stop_revision)
980
if len(pullable_revs) > 0:
981
self.append_revision(*pullable_revs)
983
def pullable_revisions(self, other, stop_revision):
984
"""See Branch.pullable_revisions."""
985
other_revno = other.revision_id_to_revno(stop_revision)
1113
if stop_revision is None:
1114
stop_revision = other.last_revision()
1115
if stop_revision is None:
1116
# if there are no commits, we're done.
1118
# whats the current last revision, before we fetch [and change it
1120
last_rev = self.last_revision()
1121
# we fetch here regardless of whether we need to so that we pickup
1123
self.fetch(other, stop_revision)
1124
my_ancestry = self.repository.get_ancestry(last_rev)
1125
if stop_revision in my_ancestry:
1126
# last_revision is a descendant of stop_revision
1128
# stop_revision must be a descendant of last_revision
1129
stop_graph = self.repository.get_revision_graph(stop_revision)
1130
if last_rev is not None and last_rev not in stop_graph:
1131
# our previous tip is not merged into stop_revision
1132
raise errors.DivergedBranches(self, other)
1133
# make a new revision history from the graph
1134
current_rev_id = stop_revision
1136
while current_rev_id not in (None, NULL_REVISION):
1137
new_history.append(current_rev_id)
1138
current_rev_id_parents = stop_graph[current_rev_id]
1140
current_rev_id = current_rev_id_parents[0]
1142
current_rev_id = None
1143
new_history.reverse()
1144
self.set_revision_history(new_history)
987
return self.missing_revisions(other, other_revno)
988
except DivergedBranches, e:
990
pullable_revs = get_intervening_revisions(self.last_revision(),
992
assert self.last_revision() not in pullable_revs
994
except bzrlib.errors.NotAncestor:
995
if is_ancestor(self.last_revision(), stop_revision, self):
1000
def revision_tree(self, revision_id):
1001
"""See Branch.revision_tree."""
1002
# TODO: refactor this to use an existing revision object
1003
# so we don't need to read it in twice.
1004
if revision_id == None or revision_id == NULL_REVISION:
1007
inv = self.get_revision_inventory(revision_id)
1008
return RevisionTree(self.weave_store, inv, revision_id)
1148
1010
def basis_tree(self):
1149
1011
"""See Branch.basis_tree."""
1150
return self.repository.revision_tree(self.last_revision())
1013
revision_id = self.revision_history()[-1]
1014
xml = self.working_tree().read_basis_inventory(revision_id)
1015
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1016
return RevisionTree(self.weave_store, inv, revision_id)
1017
except (IndexError, NoSuchFile, NoWorkingTree), e:
1018
return self.revision_tree(self.last_revision())
1152
@deprecated_method(zero_eight)
1153
1020
def working_tree(self):
1154
"""Create a Working tree object for this branch."""
1021
"""See Branch.working_tree."""
1155
1022
from bzrlib.workingtree import WorkingTree
1156
from bzrlib.transport.local import LocalTransport
1157
if (self.base.find('://') != -1 or
1158
not isinstance(self._transport, LocalTransport)):
1023
if self._transport.base.find('://') != -1:
1159
1024
raise NoWorkingTree(self.base)
1160
return self.bzrdir.open_workingtree()
1025
return WorkingTree(self.base, branch=self)
1162
1027
@needs_write_lock
1163
def pull(self, source, overwrite=False, stop_revision=None):
1028
def pull(self, source, overwrite=False):
1164
1029
"""See Branch.pull."""
1165
1030
source.lock_read()
1167
1032
old_count = len(self.revision_history())
1169
self.update_revisions(source,stop_revision)
1034
self.update_revisions(source)
1170
1035
except DivergedBranches:
1171
1036
if not overwrite:
1205
1068
def set_parent(self, url):
1206
1069
"""See Branch.set_parent."""
1207
1070
# TODO: Maybe delete old location files?
1208
# URLs should never be unicode, even on the local fs,
1209
# FIXUP this and get_parent in a future branch format bump:
1210
# read and rewrite the file, and have the new format code read
1211
# using .get not .get_utf8. RBC 20060125
1213
self.control_files._transport.delete('parent')
1215
if isinstance(url, unicode):
1217
url = url.encode('ascii')
1218
except UnicodeEncodeError:
1219
raise bzrlib.errors.InvalidURL(url,
1220
"Urls must be 7-bit ascii, "
1221
"use bzrlib.urlutils.escape")
1223
url = urlutils.relative_url(self.base, url)
1224
self.control_files.put('parent', url + '\n')
1071
from bzrlib.atomicfile import AtomicFile
1072
f = AtomicFile(self.controlfilename('parent'))
1226
1079
def tree_config(self):
1227
1080
return TreeConfig(self)
1230
class BzrBranch5(BzrBranch):
1231
"""A format 5 branch. This supports new features over plan branches.
1233
It has support for a master_branch which is the data for bound branches.
1082
def sign_revision(self, revision_id, gpg_strategy):
1083
"""See Branch.sign_revision."""
1084
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1085
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1088
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1089
"""See Branch.store_revision_signature."""
1090
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1094
class ScratchBranch(BzrBranch):
1095
"""Special test class: a branch that cleans up after itself.
1097
>>> b = ScratchBranch()
1101
>>> b._transport.__del__()
1241
super(BzrBranch5, self).__init__(_format=_format,
1242
_control_files=_control_files,
1244
_repository=_repository)
1247
def pull(self, source, overwrite=False, stop_revision=None):
1248
"""Updates branch.pull to be bound branch aware."""
1249
bound_location = self.get_bound_location()
1250
if source.base != bound_location:
1251
# not pulling from master, so we need to update master.
1252
master_branch = self.get_master_branch()
1254
master_branch.pull(source)
1255
source = master_branch
1256
return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
1258
def get_bound_location(self):
1260
return self.control_files.get_utf8('bound').read()[:-1]
1261
except errors.NoSuchFile:
1265
def get_master_branch(self):
1266
"""Return the branch we are bound to.
1268
:return: Either a Branch, or None
1270
This could memoise the branch, but if thats done
1271
it must be revalidated on each new lock.
1272
So for now we just don't memoise it.
1273
# RBC 20060304 review this decision.
1275
bound_loc = self.get_bound_location()
1279
return Branch.open(bound_loc)
1280
except (errors.NotBranchError, errors.ConnectionError), e:
1281
raise errors.BoundBranchConnectionFailure(
1285
def set_bound_location(self, location):
1286
"""Set the target where this branch is bound to.
1288
:param location: URL to the target branch
1291
self.control_files.put_utf8('bound', location+'\n')
1106
def __init__(self, files=[], dirs=[], transport=None):
1107
"""Make a test branch.
1109
This creates a temporary directory and runs init-tree in it.
1111
If any files are listed, they are created in the working copy.
1113
if transport is None:
1114
transport = bzrlib.transport.local.ScratchTransport()
1115
super(ScratchBranch, self).__init__(transport, init=True)
1294
self.control_files._transport.delete('bound')
1117
super(ScratchBranch, self).__init__(transport)
1120
self._transport.mkdir(d)
1123
self._transport.put(f, 'content of %s' % f)
1128
>>> orig = ScratchBranch(files=["file1", "file2"])
1129
>>> clone = orig.clone()
1130
>>> if os.name != 'nt':
1131
... os.path.samefile(orig.base, clone.base)
1133
... orig.base == clone.base
1136
>>> os.path.isfile(os.path.join(clone.base, "file1"))
1139
from shutil import copytree
1140
from tempfile import mkdtemp
1143
copytree(self.base, base, symlinks=True)
1144
return ScratchBranch(
1145
transport=bzrlib.transport.local.ScratchTransport(base))
1148
######################################################################
1152
def is_control_file(filename):
1153
## FIXME: better check
1154
filename = os.path.normpath(filename)
1155
while filename != '':
1156
head, tail = os.path.split(filename)
1157
## mutter('check %r for control file' % ((head, tail), ))
1158
if tail == bzrlib.BZRDIR:
1300
def bind(self, other):
1301
"""Bind the local branch the other branch.
1303
:param other: The branch to bind to
1306
# TODO: jam 20051230 Consider checking if the target is bound
1307
# It is debatable whether you should be able to bind to
1308
# a branch which is itself bound.
1309
# Committing is obviously forbidden,
1310
# but binding itself may not be.
1311
# Since we *have* to check at commit time, we don't
1312
# *need* to check here
1315
# we are now equal to or a suffix of other.
1317
# Since we have 'pulled' from the remote location,
1318
# now we should try to pull in the opposite direction
1319
# in case the local tree has more revisions than the
1321
# There may be a different check you could do here
1322
# rather than actually trying to install revisions remotely.
1323
# TODO: capture an exception which indicates the remote branch
1325
# If it is up-to-date, this probably should not be a failure
1327
# lock other for write so the revision-history syncing cannot race
1331
# if this does not error, other now has the same last rev we do
1332
# it can only error if the pull from other was concurrent with
1333
# a commit to other from someone else.
1335
# until we ditch revision-history, we need to sync them up:
1336
self.set_revision_history(other.revision_history())
1337
# now other and self are up to date with each other and have the
1338
# same revision-history.
1342
self.set_bound_location(other.base)
1346
"""If bound, unbind"""
1347
return self.set_bound_location(None)
1351
"""Synchronise this branch with the master branch if any.
1353
:return: None or the last_revision that was pivoted out during the
1356
master = self.get_master_branch()
1357
if master is not None:
1358
old_tip = self.last_revision()
1359
self.pull(master, overwrite=True)
1360
if old_tip in self.repository.get_ancestry(self.last_revision()):
1366
class BranchTestProviderAdapter(object):
1367
"""A tool to generate a suite testing multiple branch formats at once.
1369
This is done by copying the test once for each transport and injecting
1370
the transport_server, transport_readonly_server, and branch_format
1371
classes into each copy. Each copy is also given a new id() to make it
1375
def __init__(self, transport_server, transport_readonly_server, formats):
1376
self._transport_server = transport_server
1377
self._transport_readonly_server = transport_readonly_server
1378
self._formats = formats
1380
def adapt(self, test):
1381
result = TestSuite()
1382
for branch_format, bzrdir_format in self._formats:
1383
new_test = deepcopy(test)
1384
new_test.transport_server = self._transport_server
1385
new_test.transport_readonly_server = self._transport_readonly_server
1386
new_test.bzrdir_format = bzrdir_format
1387
new_test.branch_format = branch_format
1388
def make_new_test_id():
1389
new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1390
return lambda: new_id
1391
new_test.id = make_new_test_id()
1392
result.addTest(new_test)
1396
class BranchCheckResult(object):
1397
"""Results of checking branch consistency.
1402
def __init__(self, branch):
1403
self.branch = branch
1405
def report_results(self, verbose):
1406
"""Report the check results via trace.note.
1408
:param verbose: Requests more detailed display of what was checked,
1411
note('checked branch %s format %s',
1413
self.branch._format)
1416
######################################################################
1420
@deprecated_function(zero_eight)
1421
def ScratchBranch(*args, **kwargs):
1422
"""See bzrlib.bzrdir.ScratchDir."""
1423
d = ScratchDir(*args, **kwargs)
1424
return d.open_branch()
1427
@deprecated_function(zero_eight)
1428
def is_control_file(*args, **kwargs):
1429
"""See bzrlib.workingtree.is_control_file."""
1430
return bzrlib.workingtree.is_control_file(*args, **kwargs)
1160
if filename == head: