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
27
import xml.sax.saxutils
29
raise ImportError("We were unable to import 'xml.sax.saxutils',"
30
" most likely you have an xml.pyc or xml.pyo file"
31
" lying around in your bzrlib directory."
23
from cStringIO import StringIO
36
import bzrlib.bzrdir as bzrdir
37
from bzrlib.config import TreeConfig
38
from bzrlib.decorators import needs_read_lock, needs_write_lock
39
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,
40
32
import bzrlib.errors as errors
41
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
42
34
NoSuchRevision, HistoryMissing, NotBranchError,
43
DivergedBranches, LockError,
44
UninitializableFormat,
35
DivergedBranches, LockError, UnlistableStore,
46
36
UnlistableBranch, NoSuchFile, NotVersionedError,
48
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
49
44
from bzrlib.inventory import Inventory
50
from bzrlib.lockable_files import LockableFiles
51
from bzrlib.osutils import (isdir, quotefn,
52
rename, splitpath, sha_file,
53
file_kind, abspath, normpath, pathjoin,
56
from bzrlib.textui import show_status
57
from bzrlib.trace import mutter, note
58
from bzrlib.tree import EmptyTree, RevisionTree
59
from bzrlib.repository import Repository
60
from bzrlib.revision import (
61
get_intervening_revisions,
66
45
from bzrlib.store import copy_all
67
from bzrlib.symbol_versioning import *
46
from bzrlib.store.text import TextStore
47
from bzrlib.store.weave import WeaveStore
48
from bzrlib.testament import Testament
68
49
import bzrlib.transactions as transactions
69
50
from bzrlib.transport import Transport, get_transport
70
from bzrlib.tree import EmptyTree, RevisionTree
53
from config import TreeConfig
75
56
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
76
57
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
77
58
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
80
# TODO: Maybe include checks for common corruption of newlines, etc?
59
## TODO: Maybe include checks for common corruption of newlines, etc?
82
62
# TODO: Some operations like log might retrieve the same revisions
83
63
# repeatedly to calculate deltas. We could perhaps have a weakref
84
64
# cache in memory to make this faster. In general anything can be
85
# cached in memory between lock and unlock operations. .. nb thats
86
# 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)
89
94
######################################################################
184
194
raise NotImplementedError('abspath is abstract')
187
def fetch(self, from_branch, last_revision=None, pb=None):
188
"""Copy revisions from from_branch into this branch.
190
:param from_branch: Where to copy from.
191
:param last_revision: What revision to stop at (None for at the end
193
:param pb: An optional progress bar to use.
195
Returns the copied revision count and the failed revisions in a tuple:
198
if self.base == from_branch.base:
199
raise Exception("can't fetch from a branch to itself %s, %s" %
200
(self.base, to_branch.base))
202
pb = bzrlib.ui.ui_factory.progress_bar()
204
from_branch.lock_read()
206
if last_revision is None:
207
pb.update('get source history')
208
from_history = from_branch.revision_history()
210
last_revision = from_history[-1]
212
# no history in the source branch
213
last_revision = NULL_REVISION
214
return self.repository.fetch(from_branch.repository,
215
revision_id=last_revision,
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')
220
232
def get_root_id(self):
221
233
"""Return the id of this branches root"""
222
234
raise NotImplementedError('get_root_id is abstract')
236
def set_root_id(self, file_id):
237
raise NotImplementedError('set_root_id is abstract')
224
239
def print_file(self, file, revision_id):
225
240
"""Print `file` to stdout."""
226
241
raise NotImplementedError('print_file is abstract')
231
246
def set_revision_history(self, rev_history):
232
247
raise NotImplementedError('set_revision_history is abstract')
249
def has_revision(self, revision_id):
250
"""True if this branch has a copy of the revision.
252
This does not necessarily imply the revision is merge
253
or on the mainline."""
254
raise NotImplementedError('has_revision is abstract')
256
def get_revision_xml(self, revision_id):
257
raise NotImplementedError('get_revision_xml is abstract')
259
def get_revision(self, revision_id):
260
"""Return the Revision object for a named revision"""
261
raise NotImplementedError('get_revision is abstract')
263
def get_revision_delta(self, revno):
264
"""Return the delta for one revision.
266
The delta is relative to its mainline predecessor, or the
267
empty tree for revision 1.
269
assert isinstance(revno, int)
270
rh = self.revision_history()
271
if not (1 <= revno <= len(rh)):
272
raise InvalidRevisionNumber(revno)
274
# revno is 1-based; list is 0-based
276
new_tree = self.revision_tree(rh[revno-1])
278
old_tree = EmptyTree()
280
old_tree = self.revision_tree(rh[revno-2])
282
return compare_trees(old_tree, new_tree)
284
def get_revision_sha1(self, revision_id):
285
"""Hash the stored value of a revision, and return it."""
286
raise NotImplementedError('get_revision_sha1 is abstract')
288
def get_ancestry(self, revision_id):
289
"""Return a list of revision-ids integrated by a revision.
291
This currently returns a list, but the ordering is not guaranteed:
294
raise NotImplementedError('get_ancestry is abstract')
296
def get_inventory(self, revision_id):
297
"""Get Inventory object by hash."""
298
raise NotImplementedError('get_inventory is abstract')
300
def get_inventory_xml(self, revision_id):
301
"""Get inventory XML as a file object."""
302
raise NotImplementedError('get_inventory_xml is abstract')
304
def get_inventory_sha1(self, revision_id):
305
"""Return the sha1 hash of the inventory entry."""
306
raise NotImplementedError('get_inventory_sha1 is abstract')
308
def get_revision_inventory(self, revision_id):
309
"""Return inventory of a past revision."""
310
raise NotImplementedError('get_revision_inventory is abstract')
234
312
def revision_history(self):
235
313
"""Return sequence of revision hashes on to this branch."""
236
314
raise NotImplementedError('revision_history is abstract')
396
481
if revno < 1 or revno > self.revno():
397
482
raise InvalidRevisionNumber(revno)
400
def clone(self, *args, **kwargs):
401
"""Clone this branch into to_bzrdir preserving all semantic values.
403
revision_id: if not None, the revision history in the new branch will
404
be truncated to end with revision_id.
406
# for API compatability, until 0.8 releases we provide the old api:
407
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
408
# after 0.8 releases, the *args and **kwargs should be changed:
409
# def clone(self, to_bzrdir, revision_id=None):
410
if (kwargs.get('to_location', None) or
411
kwargs.get('revision', None) or
412
kwargs.get('basis_branch', None) or
413
(len(args) and isinstance(args[0], basestring))):
414
# backwards compatability api:
415
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
416
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
419
basis_branch = args[2]
421
basis_branch = kwargs.get('basis_branch', None)
423
basis = basis_branch.bzrdir
428
revision_id = args[1]
430
revision_id = kwargs.get('revision', None)
435
# no default to raise if not provided.
436
url = kwargs.get('to_location')
437
return self.bzrdir.clone(url,
438
revision_id=revision_id,
439
basis=basis).open_branch()
441
# generate args by hand
443
revision_id = args[1]
445
revision_id = kwargs.get('revision_id', None)
449
# no default to raise if not provided.
450
to_bzrdir = kwargs.get('to_bzrdir')
451
result = self._format.initialize(to_bzrdir)
452
self.copy_content_into(result, revision_id=revision_id)
456
def sprout(self, to_bzrdir, revision_id=None):
457
"""Create a new line of development from the branch, into to_bzrdir.
459
revision_id: if not None, the revision history in the new branch will
460
be truncated to end with revision_id.
462
result = self._format.initialize(to_bzrdir)
463
self.copy_content_into(result, revision_id=revision_id)
464
result.set_parent(self.bzrdir.root_transport.base)
468
def copy_content_into(self, destination, revision_id=None):
469
"""Copy the content of self into destination.
471
revision_id: if not None, the revision history in the new branch will
472
be truncated to end with revision_id.
474
new_history = self.revision_history()
475
if revision_id is not None:
477
new_history = new_history[:new_history.index(revision_id) + 1]
479
rev = self.repository.get_revision(revision_id)
480
new_history = rev.get_history(self.repository)[1:]
481
destination.set_revision_history(new_history)
482
parent = self.get_parent()
484
destination.set_parent(parent)
487
class BranchFormat(object):
488
"""An encapsulation of the initialization and open routines for a format.
490
Formats provide three things:
491
* An initialization routine,
495
Formats are placed in an dict by their format string for reference
496
during branch opening. Its not required that these be instances, they
497
can be classes themselves with class methods - it simply depends on
498
whether state is needed for a given format or not.
500
Once a format is deprecated, just deprecate the initialize and open
501
methods on the format class. Do not deprecate the object, as the
502
object will be created every time regardless.
505
_default_format = None
506
"""The default format used for new branches."""
509
"""The known formats."""
512
def find_format(klass, a_bzrdir):
513
"""Return the format for the branch object in a_bzrdir."""
515
transport = a_bzrdir.get_branch_transport(None)
516
format_string = transport.get("format").read()
517
return klass._formats[format_string]
519
raise NotBranchError(path=transport.base)
521
raise errors.UnknownFormatError(format_string)
524
def get_default_format(klass):
525
"""Return the current default format."""
526
return klass._default_format
528
def get_format_string(self):
529
"""Return the ASCII format string that identifies this format."""
530
raise NotImplementedError(self.get_format_string)
532
def initialize(self, a_bzrdir):
533
"""Create a branch of this format in a_bzrdir."""
534
raise NotImplementedError(self.initialized)
536
def is_supported(self):
537
"""Is this format supported?
539
Supported formats can be initialized and opened.
540
Unsupported formats may not support initialization or committing or
541
some other features depending on the reason for not being supported.
545
def open(self, a_bzrdir, _found=False):
546
"""Return the branch object for a_bzrdir
548
_found is a private parameter, do not use it. It is used to indicate
549
if format probing has already be done.
551
raise NotImplementedError(self.open)
554
def register_format(klass, format):
555
klass._formats[format.get_format_string()] = format
558
def set_default_format(klass, format):
559
klass._default_format = format
562
def unregister_format(klass, format):
563
assert klass._formats[format.get_format_string()] is format
564
del klass._formats[format.get_format_string()]
567
class BzrBranchFormat4(BranchFormat):
568
"""Bzr branch format 4.
571
- a revision-history file.
572
- a branch-lock lock file [ to be shared with the bzrdir ]
575
def initialize(self, a_bzrdir):
576
"""Create a branch of this format in a_bzrdir."""
577
mutter('creating branch in %s', a_bzrdir.transport.base)
578
branch_transport = a_bzrdir.get_branch_transport(self)
579
utf8_files = [('revision-history', ''),
582
control_files = LockableFiles(branch_transport, 'branch-lock')
583
control_files.lock_write()
585
for file, content in utf8_files:
586
control_files.put_utf8(file, content)
588
control_files.unlock()
589
return self.open(a_bzrdir, _found=True)
592
super(BzrBranchFormat4, self).__init__()
593
self._matchingbzrdir = bzrdir.BzrDirFormat6()
595
def open(self, a_bzrdir, _found=False):
596
"""Return the branch object for a_bzrdir
598
_found is a private parameter, do not use it. It is used to indicate
599
if format probing has already be done.
602
# we are being called directly and must probe.
603
raise NotImplementedError
604
return BzrBranch(_format=self,
605
_control_files=a_bzrdir._control_files,
607
_repository=a_bzrdir.open_repository())
610
class BzrBranchFormat5(BranchFormat):
611
"""Bzr branch format 5.
614
- a revision-history file.
617
- works with shared repositories.
620
def get_format_string(self):
621
"""See BranchFormat.get_format_string()."""
622
return "Bazaar-NG branch format 5\n"
624
def initialize(self, a_bzrdir):
625
"""Create a branch of this format in a_bzrdir."""
626
mutter('creating branch in %s', a_bzrdir.transport.base)
627
branch_transport = a_bzrdir.get_branch_transport(self)
629
utf8_files = [('revision-history', ''),
633
branch_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
634
control_files = LockableFiles(branch_transport, 'lock')
635
control_files.lock_write()
636
control_files.put_utf8('format', self.get_format_string())
638
for file, content in utf8_files:
639
control_files.put_utf8(file, content)
641
control_files.unlock()
642
return self.open(a_bzrdir, _found=True, )
645
super(BzrBranchFormat5, self).__init__()
646
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
648
def open(self, a_bzrdir, _found=False):
649
"""Return the branch object for a_bzrdir
651
_found is a private parameter, do not use it. It is used to indicate
652
if format probing has already be done.
655
format = BranchFormat.find_format(a_bzrdir)
656
assert format.__class__ == self.__class__
657
transport = a_bzrdir.get_branch_transport(None)
658
control_files = LockableFiles(transport, 'lock')
659
return BzrBranch(_format=self,
660
_control_files=control_files,
662
_repository=a_bzrdir.find_repository())
665
class BranchReferenceFormat(BranchFormat):
666
"""Bzr branch reference format.
668
Branch references are used in implementing checkouts, they
669
act as an alias to the real branch which is at some other url.
676
def get_format_string(self):
677
"""See BranchFormat.get_format_string()."""
678
return "Bazaar-NG Branch Reference Format 1\n"
680
def initialize(self, a_bzrdir, target_branch=None):
681
"""Create a branch of this format in a_bzrdir."""
682
if target_branch is None:
683
# this format does not implement branch itself, thus the implicit
684
# creation contract must see it as uninitializable
685
raise errors.UninitializableFormat(self)
686
mutter('creating branch reference in %s', a_bzrdir.transport.base)
687
branch_transport = a_bzrdir.get_branch_transport(self)
688
# FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
689
branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
690
branch_transport.put('format', StringIO(self.get_format_string()))
691
return self.open(a_bzrdir, _found=True)
694
super(BranchReferenceFormat, self).__init__()
695
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
697
def _make_reference_clone_function(format, a_branch):
698
"""Create a clone() routine for a branch dynamically."""
699
def clone(to_bzrdir, revision_id=None):
700
"""See Branch.clone()."""
701
return format.initialize(to_bzrdir, a_branch)
702
# cannot obey revision_id limits when cloning a reference ...
703
# FIXME RBC 20060210 either nuke revision_id for clone, or
704
# emit some sort of warning/error to the caller ?!
707
def open(self, a_bzrdir, _found=False):
708
"""Return the branch that the branch reference in a_bzrdir points at.
710
_found is a private parameter, do not use it. It is used to indicate
711
if format probing has already be done.
714
format = BranchFormat.find_format(a_bzrdir)
715
assert format.__class__ == self.__class__
716
transport = a_bzrdir.get_branch_transport(None)
717
real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
718
result = real_bzrdir.open_branch()
719
# this changes the behaviour of result.clone to create a new reference
720
# rather than a copy of the content of the branch.
721
# I did not use a proxy object because that needs much more extensive
722
# testing, and we are only changing one behaviour at the moment.
723
# If we decide to alter more behaviours - i.e. the implicit nickname
724
# then this should be refactored to introduce a tested proxy branch
725
# and a subclass of that for use in overriding clone() and ....
727
result.clone = self._make_reference_clone_function(result)
731
# formats which have no format string are not discoverable
732
# and not independently creatable, so are not registered.
733
__default_format = BzrBranchFormat5()
734
BranchFormat.register_format(__default_format)
735
BranchFormat.register_format(BranchReferenceFormat())
736
BranchFormat.set_default_format(__default_format)
737
_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')
740
490
class BzrBranch(Branch):
741
491
"""A branch stored in the actual filesystem.
770
552
version is not applied. This is intended only for
771
553
upgrade/recovery type use; it's not guaranteed that
772
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.
775
self.bzrdir = bzrdir.BzrDir.open(transport.base)
777
self.bzrdir = a_bzrdir
778
self._transport = self.bzrdir.transport.clone('..')
779
self._base = self._transport.base
780
self._format = _format
781
if _control_files is None:
782
raise BzrBadParameterMissing('_control_files')
783
self.control_files = _control_files
784
if deprecated_passed(init):
785
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
786
"deprecated as of bzr 0.8. Please use Branch.create().",
790
# this is slower than before deprecation, oh well never mind.
792
self._initialize(transport.base)
793
self._check_format(_format)
794
if deprecated_passed(relax_version_check):
795
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
796
"relax_version_check parameter is deprecated as of bzr 0.8. "
797
"Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
801
if (not relax_version_check
802
and not self._format.is_supported()):
803
raise errors.UnsupportedFormatError(
804
'sorry, branch format %r not supported' % fmt,
805
['use a different bzr version',
806
'or remove the .bzr directory'
807
' and "bzr init" again'])
808
if deprecated_passed(transport):
809
warn("BzrBranch.__init__(transport=XXX...): The transport "
810
"parameter is deprecated as of bzr 0.8. "
811
"Please use Branch.open, or bzrdir.open_branch().",
814
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(unicode(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)
581
def get_weave(name, prefixed=False):
582
relpath = self._rel_controlfilename(unicode(name))
583
ws = WeaveStore(self._transport.clone(relpath), prefixed=prefixed)
584
if self._transport.should_cache():
585
ws.enable_cache = True
588
if self._branch_format == 4:
589
self.inventory_store = get_store('inventory-store')
590
self.text_store = get_store('text-store')
591
self.revision_store = get_store('revision-store')
592
elif self._branch_format == 5:
593
self.control_weaves = get_weave(u'')
594
self.weave_store = get_weave(u'weaves')
595
self.revision_store = get_store(u'revision-store', compressed=False)
596
elif self._branch_format == 6:
597
self.control_weaves = get_weave(u'')
598
self.weave_store = get_weave(u'weaves', prefixed=True)
599
self.revision_store = get_store(u'revision-store', compressed=False,
601
self.revision_store.register_suffix('sig')
602
self._transaction = None
816
604
def __str__(self):
817
return '%s(%r)' % (self.__class__.__name__, self.base)
605
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
819
607
__repr__ = __str__
821
609
def __del__(self):
610
if self._lock_mode or self._lock:
611
# XXX: This should show something every time, and be suitable for
612
# headless operation and embedding
613
warn("branch %r was not explicitly unlocked" % self)
822
616
# TODO: It might be best to do this somewhere else,
823
617
# but it is nice for a Branch object to automatically
824
618
# cache it's information.
825
619
# Alternatively, we could have the Transport objects cache requests
826
620
# See the earlier discussion about how major objects (like Branch)
827
621
# should never expect their __del__ function to run.
828
# XXX: cache_root seems to be unused, 2006-01-13 mbp
829
622
if hasattr(self, 'cache_root') and self.cache_root is not None:
831
624
shutil.rmtree(self.cache_root)
834
627
self.cache_root = None
836
629
def _get_base(self):
631
return self._transport.base
839
634
base = property(_get_base, doc="The URL for the root of this branch.")
841
636
def _finish_transaction(self):
842
637
"""Exit the current transaction."""
843
return self.control_files._finish_transaction()
638
if self._transaction is None:
639
raise errors.LockError('Branch %s is not in a transaction' %
641
transaction = self._transaction
642
self._transaction = None
845
645
def get_transaction(self):
846
"""Return the current active transaction.
848
If no transaction is active, this returns a passthrough object
849
for which all data is immediately flushed and no caching happens.
851
# this is an explicit function so that we can do tricky stuff
852
# when the storage in rev_storage is elsewhere.
853
# we probably need to hook the two 'lock a location' and
854
# 'have a transaction' together more delicately, so that
855
# we can have two locks (branch and storage) and one transaction
856
# ... and finishing the transaction unlocks both, but unlocking
857
# does not. - RBC 20051121
858
return self.control_files.get_transaction()
860
def _set_transaction(self, transaction):
646
"""See Branch.get_transaction."""
647
if self._transaction is None:
648
return transactions.PassThroughTransaction()
650
return self._transaction
652
def _set_transaction(self, new_transaction):
861
653
"""Set a new active transaction."""
862
return self.control_files._set_transaction(transaction)
654
if self._transaction is not None:
655
raise errors.LockError('Branch %s is in a transaction already.' %
657
self._transaction = new_transaction
659
def lock_write(self):
660
#mutter("lock write: %s (%s)", self, self._lock_count)
661
# TODO: Upgrade locking to support using a Transport,
662
# and potentially a remote locking protocol
664
if self._lock_mode != 'w':
665
raise LockError("can't upgrade to a write lock from %r" %
667
self._lock_count += 1
669
self._lock = self._transport.lock_write(
670
self._rel_controlfilename('branch-lock'))
671
self._lock_mode = 'w'
673
self._set_transaction(transactions.PassThroughTransaction())
676
#mutter("lock read: %s (%s)", self, self._lock_count)
678
assert self._lock_mode in ('r', 'w'), \
679
"invalid lock mode %r" % self._lock_mode
680
self._lock_count += 1
682
self._lock = self._transport.lock_read(
683
self._rel_controlfilename('branch-lock'))
684
self._lock_mode = 'r'
686
self._set_transaction(transactions.ReadOnlyTransaction())
687
# 5K may be excessive, but hey, its a knob.
688
self.get_transaction().set_cache_size(5000)
691
#mutter("unlock: %s (%s)", self, self._lock_count)
692
if not self._lock_mode:
693
raise LockError('branch %r is not locked' % (self))
695
if self._lock_count > 1:
696
self._lock_count -= 1
698
self._finish_transaction()
701
self._lock_mode = self._lock_count = None
864
703
def abspath(self, name):
865
704
"""See Branch.abspath."""
866
return self.control_files._transport.abspath(name)
868
def _check_format(self, format):
869
"""Identify the branch format if needed.
871
The format is stored as a reference to the format object in
872
self._format for code that needs to check it later.
874
The format parameter is either None or the branch format class
875
used to open this branch.
877
FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
705
return self._transport.abspath(name)
707
def _rel_controlfilename(self, file_or_path):
708
if not isinstance(file_or_path, basestring):
709
file_or_path = u'/'.join(file_or_path)
710
if file_or_path == '':
712
return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
714
def controlfilename(self, file_or_path):
715
"""See Branch.controlfilename."""
716
return self._transport.abspath(self._rel_controlfilename(file_or_path))
718
def controlfile(self, file_or_path, mode='r'):
719
"""See Branch.controlfile."""
722
relpath = self._rel_controlfilename(file_or_path)
723
#TODO: codecs.open() buffers linewise, so it was overloaded with
724
# a much larger buffer, do we need to do the same for getreader/getwriter?
726
return self._transport.get(relpath)
728
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
730
# XXX: Do we really want errors='replace'? Perhaps it should be
731
# an error, or at least reported, if there's incorrectly-encoded
732
# data inside a file.
733
# <https://launchpad.net/products/bzr/+bug/3823>
734
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
736
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
738
raise BzrError("invalid controlfile mode %r" % mode)
740
def put_controlfile(self, path, f, encode=True):
741
"""See Branch.put_controlfile."""
742
self.put_controlfiles([(path, f)], encode=encode)
744
def put_controlfiles(self, files, encode=True):
745
"""See Branch.put_controlfiles."""
748
for path, f in files:
750
if isinstance(f, basestring):
751
f = f.encode('utf-8', 'replace')
753
f = codecs.getwriter('utf-8')(f, errors='replace')
754
path = self._rel_controlfilename(path)
755
ctrl_files.append((path, f))
756
self._transport.put_multi(ctrl_files)
758
def _make_control(self):
759
from bzrlib.inventory import Inventory
760
from bzrlib.weavefile import write_weave_v5
761
from bzrlib.weave import Weave
763
# Create an empty inventory
765
# if we want per-tree root ids then this is the place to set
766
# them; they're not needed for now and so ommitted for
768
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
769
empty_inv = sio.getvalue()
771
bzrlib.weavefile.write_weave_v5(Weave(), sio)
772
empty_weave = sio.getvalue()
774
dirs = [[], 'revision-store', 'weaves']
776
"This is a Bazaar-NG control directory.\n"
777
"Do not change any files in this directory.\n"),
778
('branch-format', BZR_BRANCH_FORMAT_6),
779
('revision-history', ''),
782
('pending-merges', ''),
783
('inventory', empty_inv),
784
('inventory.weave', empty_weave),
785
('ancestry.weave', empty_weave)
787
cfn = self._rel_controlfilename
788
self._transport.mkdir_multi([cfn(d) for d in dirs])
789
self.put_controlfiles(files)
790
mutter('created control directory in ' + self._transport.base)
792
def _check_format(self, relax_version_check):
793
"""Check this branch format is supported.
795
The format level is stored, as an integer, in
796
self._branch_format for code that needs to check it later.
798
In the future, we might need different in-memory Branch
799
classes to support downlevel branches. But not yet.
880
format = BzrBranchFormat.find_format(self.bzrdir)
881
self._format = format
882
mutter("got branch format %s", self._format)
802
fmt = self.controlfile('branch-format', 'r').read()
804
raise NotBranchError(path=self.base)
805
mutter("got branch format %r", fmt)
806
if fmt == BZR_BRANCH_FORMAT_6:
807
self._branch_format = 6
808
elif fmt == BZR_BRANCH_FORMAT_5:
809
self._branch_format = 5
810
elif fmt == BZR_BRANCH_FORMAT_4:
811
self._branch_format = 4
813
if (not relax_version_check
814
and self._branch_format not in (5, 6)):
815
raise errors.UnsupportedFormatError(
816
'sorry, branch format %r not supported' % fmt,
817
['use a different bzr version',
818
'or remove the .bzr directory'
819
' and "bzr init" again'])
885
822
def get_root_id(self):
886
823
"""See Branch.get_root_id."""
887
tree = self.repository.revision_tree(self.last_revision())
888
return tree.inventory.root.file_id
890
def lock_write(self):
891
# TODO: test for failed two phase locks. This is known broken.
892
self.control_files.lock_write()
893
self.repository.lock_write()
896
# TODO: test for failed two phase locks. This is known broken.
897
self.control_files.lock_read()
898
self.repository.lock_read()
901
# TODO: test for failed two phase locks. This is known broken.
902
self.repository.unlock()
903
self.control_files.unlock()
905
def peek_lock_mode(self):
906
if self.control_files._lock_count == 0:
909
return self.control_files._lock_mode
824
inv = self.get_inventory(self.last_revision())
825
return inv.root.file_id
912
828
def print_file(self, file, revision_id):
913
829
"""See Branch.print_file."""
914
return self.repository.print_file(file, revision_id)
830
tree = self.revision_tree(revision_id)
831
# use inventory as it was in that revision
832
file_id = tree.inventory.path2id(file)
835
revno = self.revision_id_to_revno(revision_id)
836
except errors.NoSuchRevision:
837
# TODO: This should not be BzrError,
838
# but NoSuchFile doesn't fit either
839
raise BzrError('%r is not present in revision %s'
840
% (file, revision_id))
842
raise BzrError('%r is not present in revision %s'
844
tree.print_file(file_id)
916
846
@needs_write_lock
917
847
def append_revision(self, *revision_ids):
925
855
@needs_write_lock
926
856
def set_revision_history(self, rev_history):
927
857
"""See Branch.set_revision_history."""
928
self.control_files.put_utf8(
929
'revision-history', '\n'.join(rev_history))
931
def get_revision_delta(self, revno):
932
"""Return the delta for one revision.
934
The delta is relative to its mainline predecessor, or the
935
empty tree for revision 1.
937
assert isinstance(revno, int)
938
rh = self.revision_history()
939
if not (1 <= revno <= len(rh)):
940
raise InvalidRevisionNumber(revno)
942
# revno is 1-based; list is 0-based
944
new_tree = self.repository.revision_tree(rh[revno-1])
946
old_tree = EmptyTree()
858
old_revision = self.last_revision()
859
new_revision = rev_history[-1]
860
self.put_controlfile('revision-history', '\n'.join(rev_history))
862
self.working_tree().set_last_revision(new_revision, old_revision)
863
except NoWorkingTree:
864
mutter('Unable to set_last_revision without a working tree.')
866
def has_revision(self, revision_id):
867
"""See Branch.has_revision."""
868
return (revision_id is None
869
or self.revision_store.has_id(revision_id))
872
def _get_revision_xml_file(self, revision_id):
873
if not revision_id or not isinstance(revision_id, basestring):
874
raise InvalidRevisionId(revision_id=revision_id, branch=self)
876
return self.revision_store.get(revision_id)
877
except (IndexError, KeyError):
878
raise bzrlib.errors.NoSuchRevision(self, revision_id)
880
def get_revision_xml(self, revision_id):
881
"""See Branch.get_revision_xml."""
882
return self._get_revision_xml_file(revision_id).read()
884
def get_revision(self, revision_id):
885
"""See Branch.get_revision."""
886
xml_file = self._get_revision_xml_file(revision_id)
889
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
890
except SyntaxError, e:
891
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
895
assert r.revision_id == revision_id
898
def get_revision_sha1(self, revision_id):
899
"""See Branch.get_revision_sha1."""
900
# In the future, revision entries will be signed. At that
901
# point, it is probably best *not* to include the signature
902
# in the revision hash. Because that lets you re-sign
903
# the revision, (add signatures/remove signatures) and still
904
# have all hash pointers stay consistent.
905
# But for now, just hash the contents.
906
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
908
def get_ancestry(self, revision_id):
909
"""See Branch.get_ancestry."""
910
if revision_id is None:
912
w = self._get_inventory_weave()
913
return [None] + map(w.idx_to_name,
914
w.inclusions([w.lookup(revision_id)]))
916
def _get_inventory_weave(self):
917
return self.control_weaves.get_weave('inventory',
918
self.get_transaction())
920
def get_inventory(self, revision_id):
921
"""See Branch.get_inventory."""
922
xml = self.get_inventory_xml(revision_id)
923
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
925
def get_inventory_xml(self, revision_id):
926
"""See Branch.get_inventory_xml."""
928
assert isinstance(revision_id, basestring), type(revision_id)
929
iw = self._get_inventory_weave()
930
return iw.get_text(iw.lookup(revision_id))
932
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
934
def get_inventory_sha1(self, revision_id):
935
"""See Branch.get_inventory_sha1."""
936
return self.get_revision(revision_id).inventory_sha1
938
def get_revision_inventory(self, revision_id):
939
"""See Branch.get_revision_inventory."""
940
# TODO: Unify this with get_inventory()
941
# bzr 0.0.6 and later imposes the constraint that the inventory_id
942
# must be the same as its revision, so this is trivial.
943
if revision_id == None:
944
# This does not make sense: if there is no revision,
945
# then it is the current tree inventory surely ?!
946
# and thus get_root_id() is something that looks at the last
947
# commit on the branch, and the get_root_id is an inventory check.
948
raise NotImplementedError
949
# return Inventory(self.get_root_id())
948
old_tree = self.repository.revision_tree(rh[revno-2])
949
return compare_trees(old_tree, new_tree)
951
return self.get_inventory(revision_id)
952
954
def revision_history(self):
953
955
"""See Branch.revision_history."""
954
# FIXME are transactions bound to control files ? RBC 20051121
955
956
transaction = self.get_transaction()
956
957
history = transaction.map.find_revision_history()
957
958
if history is not None:
958
959
mutter("cache hit for revision-history in %s", self)
959
960
return list(history)
960
961
history = [l.rstrip('\r\n') for l in
961
self.control_files.get_utf8('revision-history').readlines()]
962
self.controlfile('revision-history', 'r').readlines()]
962
963
transaction.map.add_revision_history(history)
963
964
# this call is disabled because revision_history is
964
965
# not really an object yet, and the transaction is for objects.
1054
1069
def set_parent(self, url):
1055
1070
"""See Branch.set_parent."""
1056
1071
# TODO: Maybe delete old location files?
1057
# URLs should never be unicode, even on the local fs,
1058
# FIXUP this and get_parent in a future branch format bump:
1059
# read and rewrite the file, and have the new format code read
1060
# using .get not .get_utf8. RBC 20060125
1061
self.control_files.put_utf8('parent', url + '\n')
1072
from bzrlib.atomicfile import AtomicFile
1073
f = AtomicFile(self.controlfilename('parent'))
1063
1080
def tree_config(self):
1064
1081
return TreeConfig(self)
1066
def _get_truncated_history(self, revision_id):
1067
history = self.revision_history()
1068
if revision_id is None:
1071
idx = history.index(revision_id)
1073
raise InvalidRevisionId(revision_id=revision, branch=self)
1074
return history[:idx+1]
1077
def _clone_weave(self, to_location, revision=None, basis_branch=None):
1079
from bzrlib.workingtree import WorkingTree
1080
assert isinstance(to_location, basestring)
1081
if basis_branch is not None:
1082
note("basis_branch is not supported for fast weave copy yet.")
1084
history = self._get_truncated_history(revision)
1085
if not bzrlib.osutils.lexists(to_location):
1086
os.mkdir(to_location)
1087
bzrdir_to = self.bzrdir._format.initialize(to_location)
1088
self.repository.clone(bzrdir_to)
1089
branch_to = bzrdir_to.create_branch()
1090
mutter("copy branch from %s to %s", self, branch_to)
1092
# FIXME duplicate code with base .clone().
1093
# .. would template method be useful here? RBC 20051207
1094
branch_to.set_parent(self.base)
1095
branch_to.append_revision(*history)
1096
WorkingTree.create(branch_to, branch_to.base)
1101
class BranchTestProviderAdapter(object):
1102
"""A tool to generate a suite testing multiple branch formats at once.
1104
This is done by copying the test once for each transport and injecting
1105
the transport_server, transport_readonly_server, and branch_format
1106
classes into each copy. Each copy is also given a new id() to make it
1083
def sign_revision(self, revision_id, gpg_strategy):
1084
"""See Branch.sign_revision."""
1085
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1086
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1089
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1090
"""See Branch.store_revision_signature."""
1091
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1095
class ScratchBranch(BzrBranch):
1096
"""Special test class: a branch that cleans up after itself.
1098
>>> b = ScratchBranch()
1102
>>> b._transport.__del__()
1110
def __init__(self, transport_server, transport_readonly_server, formats):
1111
self._transport_server = transport_server
1112
self._transport_readonly_server = transport_readonly_server
1113
self._formats = formats
1107
def __init__(self, files=[], dirs=[], transport=None):
1108
"""Make a test branch.
1110
This creates a temporary directory and runs init-tree in it.
1112
If any files are listed, they are created in the working copy.
1114
if transport is None:
1115
transport = bzrlib.transport.local.ScratchTransport()
1116
super(ScratchBranch, self).__init__(transport, init=True)
1118
super(ScratchBranch, self).__init__(transport)
1121
self._transport.mkdir(d)
1124
self._transport.put(f, 'content of %s' % f)
1129
>>> orig = ScratchBranch(files=["file1", "file2"])
1130
>>> clone = orig.clone()
1131
>>> if os.name != 'nt':
1132
... os.path.samefile(orig.base, clone.base)
1134
... orig.base == clone.base
1137
>>> os.path.isfile(os.path.join(clone.base, "file1"))
1140
from shutil import copytree
1141
from tempfile import mkdtemp
1144
copytree(self.base, base, symlinks=True)
1145
return ScratchBranch(
1146
transport=bzrlib.transport.local.ScratchTransport(base))
1115
def adapt(self, test):
1116
result = TestSuite()
1117
for branch_format, bzrdir_format in self._formats:
1118
new_test = deepcopy(test)
1119
new_test.transport_server = self._transport_server
1120
new_test.transport_readonly_server = self._transport_readonly_server
1121
new_test.bzrdir_format = bzrdir_format
1122
new_test.branch_format = branch_format
1123
def make_new_test_id():
1124
new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1125
return lambda: new_id
1126
new_test.id = make_new_test_id()
1127
result.addTest(new_test)
1131
1149
######################################################################
1135
@deprecated_function(zero_eight)
1136
def ScratchBranch(*args, **kwargs):
1137
"""See bzrlib.bzrdir.ScratchDir."""
1138
d = ScratchDir(*args, **kwargs)
1139
return d.open_branch()
1142
@deprecated_function(zero_eight)
1143
def is_control_file(*args, **kwargs):
1144
"""See bzrlib.workingtree.is_control_file."""
1145
return bzrlib.workingtree.is_control_file(*args, **kwargs)
1153
def is_control_file(filename):
1154
## FIXME: better check
1155
filename = os.path.normpath(filename)
1156
while filename != '':
1157
head, tail = os.path.split(filename)
1158
## mutter('check %r for control file' % ((head, tail), ))
1159
if tail == bzrlib.BZRDIR:
1161
if filename == head: