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
22
25
from warnings import warn
23
from cStringIO import StringIO
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."
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,
31
file_kind, abspath, normpath, pathjoin)
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
32
40
import bzrlib.errors as errors
33
41
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
42
NoSuchRevision, HistoryMissing, NotBranchError,
35
DivergedBranches, LockError, UnlistableStore,
43
DivergedBranches, LockError,
44
UninitializableFormat,
36
46
UnlistableBranch, NoSuchFile, NotVersionedError,
48
import bzrlib.inventory as inventory
49
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,
38
56
from bzrlib.textui import show_status
39
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
42
from bzrlib.delta import compare_trees
57
from bzrlib.trace import mutter, note
43
58
from bzrlib.tree import EmptyTree, RevisionTree
44
from bzrlib.inventory import Inventory
59
from bzrlib.repository import Repository
60
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions)
45
61
from bzrlib.store import copy_all
46
from bzrlib.store.text import TextStore
47
from bzrlib.store.weave import WeaveStore
48
from bzrlib.testament import Testament
62
from bzrlib.symbol_versioning import *
49
63
import bzrlib.transactions as transactions
50
64
from bzrlib.transport import Transport, get_transport
65
from bzrlib.tree import EmptyTree, RevisionTree
53
from config import TreeConfig
56
70
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
57
71
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
58
72
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
59
## TODO: Maybe include checks for common corruption of newlines, etc?
75
# TODO: Maybe include checks for common corruption of newlines, etc?
62
77
# TODO: Some operations like log might retrieve the same revisions
63
78
# repeatedly to calculate deltas. We could perhaps have a weakref
64
79
# cache in memory to make this faster. In general anything can be
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)
80
# cached in memory between lock and unlock operations. .. nb thats
81
# what the transaction identity map provides
94
84
######################################################################
246
204
def set_revision_history(self, rev_history):
247
205
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')
312
207
def revision_history(self):
313
208
"""Return sequence of revision hashes on to this branch."""
314
209
raise NotImplementedError('revision_history is abstract')
481
369
if revno < 1 or revno > self.revno():
482
370
raise InvalidRevisionNumber(revno)
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')
373
def clone(self, *args, **kwargs):
374
"""Clone this branch into to_bzrdir preserving all semantic values.
376
revision_id: if not None, the revision history in the new branch will
377
be truncated to end with revision_id.
379
# for API compatability, until 0.8 releases we provide the old api:
380
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
381
# after 0.8 releases, the *args and **kwargs should be changed:
382
# def clone(self, to_bzrdir, revision_id=None):
383
if (kwargs.get('to_location', None) or
384
kwargs.get('revision', None) or
385
kwargs.get('basis_branch', None) or
386
(len(args) and isinstance(args[0], basestring))):
387
# backwards compatability api:
388
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
389
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
392
basis_branch = args[2]
394
basis_branch = kwargs.get('basis_branch', None)
396
basis = basis_branch.bzrdir
401
revision_id = args[1]
403
revision_id = kwargs.get('revision', None)
408
# no default to raise if not provided.
409
url = kwargs.get('to_location')
410
return self.bzrdir.clone(url,
411
revision_id=revision_id,
412
basis=basis).open_branch()
414
# generate args by hand
416
revision_id = args[1]
418
revision_id = kwargs.get('revision_id', None)
422
# no default to raise if not provided.
423
to_bzrdir = kwargs.get('to_bzrdir')
424
result = self._format.initialize(to_bzrdir)
425
self.copy_content_into(result, revision_id=revision_id)
429
def sprout(self, to_bzrdir, revision_id=None):
430
"""Create a new line of development from the branch, into to_bzrdir.
432
revision_id: if not None, the revision history in the new branch will
433
be truncated to end with revision_id.
435
result = self._format.initialize(to_bzrdir)
436
self.copy_content_into(result, revision_id=revision_id)
437
result.set_parent(self.bzrdir.root_transport.base)
441
def copy_content_into(self, destination, revision_id=None):
442
"""Copy the content of self into destination.
444
revision_id: if not None, the revision history in the new branch will
445
be truncated to end with revision_id.
447
new_history = self.revision_history()
448
if revision_id is not None:
450
new_history = new_history[:new_history.index(revision_id) + 1]
452
rev = self.repository.get_revision(revision_id)
453
new_history = rev.get_history(self.repository)[1:]
454
destination.set_revision_history(new_history)
455
parent = self.get_parent()
457
destination.set_parent(parent)
460
class BranchFormat(object):
461
"""An encapsulation of the initialization and open routines for a format.
463
Formats provide three things:
464
* An initialization routine,
468
Formats are placed in an dict by their format string for reference
469
during branch opening. Its not required that these be instances, they
470
can be classes themselves with class methods - it simply depends on
471
whether state is needed for a given format or not.
473
Once a format is deprecated, just deprecate the initialize and open
474
methods on the format class. Do not deprecate the object, as the
475
object will be created every time regardless.
478
_default_format = None
479
"""The default format used for new branches."""
482
"""The known formats."""
485
def find_format(klass, a_bzrdir):
486
"""Return the format for the branch object in a_bzrdir."""
488
transport = a_bzrdir.get_branch_transport(None)
489
format_string = transport.get("format").read()
490
return klass._formats[format_string]
492
raise NotBranchError(path=transport.base)
494
raise errors.UnknownFormatError(format_string)
497
def get_default_format(klass):
498
"""Return the current default format."""
499
return klass._default_format
501
def get_format_string(self):
502
"""Return the ASCII format string that identifies this format."""
503
raise NotImplementedError(self.get_format_string)
505
def initialize(self, a_bzrdir):
506
"""Create a branch of this format in a_bzrdir."""
507
raise NotImplementedError(self.initialized)
509
def is_supported(self):
510
"""Is this format supported?
512
Supported formats can be initialized and opened.
513
Unsupported formats may not support initialization or committing or
514
some other features depending on the reason for not being supported.
518
def open(self, a_bzrdir, _found=False):
519
"""Return the branch object for a_bzrdir
521
_found is a private parameter, do not use it. It is used to indicate
522
if format probing has already be done.
524
raise NotImplementedError(self.open)
527
def register_format(klass, format):
528
klass._formats[format.get_format_string()] = format
531
def set_default_format(klass, format):
532
klass._default_format = format
535
def unregister_format(klass, format):
536
assert klass._formats[format.get_format_string()] is format
537
del klass._formats[format.get_format_string()]
540
class BzrBranchFormat4(BranchFormat):
541
"""Bzr branch format 4.
544
- a revision-history file.
545
- a branch-lock lock file [ to be shared with the bzrdir ]
548
def initialize(self, a_bzrdir):
549
"""Create a branch of this format in a_bzrdir."""
550
mutter('creating branch in %s', a_bzrdir.transport.base)
551
branch_transport = a_bzrdir.get_branch_transport(self)
552
utf8_files = [('revision-history', ''),
555
control_files = LockableFiles(branch_transport, 'branch-lock')
556
control_files.lock_write()
558
for file, content in utf8_files:
559
control_files.put_utf8(file, content)
561
control_files.unlock()
562
return self.open(a_bzrdir, _found=True)
565
super(BzrBranchFormat4, self).__init__()
566
self._matchingbzrdir = bzrdir.BzrDirFormat6()
568
def open(self, a_bzrdir, _found=False):
569
"""Return the branch object for a_bzrdir
571
_found is a private parameter, do not use it. It is used to indicate
572
if format probing has already be done.
575
# we are being called directly and must probe.
576
raise NotImplementedError
577
return BzrBranch(_format=self,
578
_control_files=a_bzrdir._control_files,
580
_repository=a_bzrdir.open_repository())
583
class BzrBranchFormat5(BranchFormat):
584
"""Bzr branch format 5.
587
- a revision-history file.
590
- works with shared repositories.
593
def get_format_string(self):
594
"""See BranchFormat.get_format_string()."""
595
return "Bazaar-NG branch format 5\n"
597
def initialize(self, a_bzrdir):
598
"""Create a branch of this format in a_bzrdir."""
599
mutter('creating branch in %s', a_bzrdir.transport.base)
600
branch_transport = a_bzrdir.get_branch_transport(self)
602
utf8_files = [('revision-history', ''),
606
branch_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
607
control_files = LockableFiles(branch_transport, 'lock')
608
control_files.lock_write()
609
control_files.put_utf8('format', self.get_format_string())
611
for file, content in utf8_files:
612
control_files.put_utf8(file, content)
614
control_files.unlock()
615
return self.open(a_bzrdir, _found=True, )
618
super(BzrBranchFormat5, self).__init__()
619
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
621
def open(self, a_bzrdir, _found=False):
622
"""Return the branch object for a_bzrdir
624
_found is a private parameter, do not use it. It is used to indicate
625
if format probing has already be done.
628
format = BranchFormat.find_format(a_bzrdir)
629
assert format.__class__ == self.__class__
630
transport = a_bzrdir.get_branch_transport(None)
631
control_files = LockableFiles(transport, 'lock')
632
return BzrBranch(_format=self,
633
_control_files=control_files,
635
_repository=a_bzrdir.find_repository())
638
class BranchReferenceFormat(BranchFormat):
639
"""Bzr branch reference format.
641
Branch references are used in implementing checkouts, they
642
act as an alias to the real branch which is at some other url.
649
def get_format_string(self):
650
"""See BranchFormat.get_format_string()."""
651
return "Bazaar-NG Branch Reference Format 1\n"
653
def initialize(self, a_bzrdir, target_branch=None):
654
"""Create a branch of this format in a_bzrdir."""
655
if target_branch is None:
656
# this format does not implement branch itself, thus the implicit
657
# creation contract must see it as uninitializable
658
raise errors.UninitializableFormat(self)
659
mutter('creating branch reference in %s', a_bzrdir.transport.base)
660
branch_transport = a_bzrdir.get_branch_transport(self)
661
# FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
662
branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
663
branch_transport.put('format', StringIO(self.get_format_string()))
664
return self.open(a_bzrdir, _found=True)
667
super(BranchReferenceFormat, self).__init__()
668
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
670
def _make_reference_clone_function(format, a_branch):
671
"""Create a clone() routine for a branch dynamically."""
672
def clone(to_bzrdir, revision_id=None):
673
"""See Branch.clone()."""
674
return format.initialize(to_bzrdir, a_branch)
675
# cannot obey revision_id limits when cloning a reference ...
676
# FIXME RBC 20060210 either nuke revision_id for clone, or
677
# emit some sort of warning/error to the caller ?!
680
def open(self, a_bzrdir, _found=False):
681
"""Return the branch that the branch reference in a_bzrdir points at.
683
_found is a private parameter, do not use it. It is used to indicate
684
if format probing has already be done.
687
format = BranchFormat.find_format(a_bzrdir)
688
assert format.__class__ == self.__class__
689
transport = a_bzrdir.get_branch_transport(None)
690
real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
691
result = real_bzrdir.open_branch()
692
# this changes the behaviour of result.clone to create a new reference
693
# rather than a copy of the content of the branch.
694
# I did not use a proxy object because that needs much more extensive
695
# testing, and we are only changing one behaviour at the moment.
696
# If we decide to alter more behaviours - i.e. the implicit nickname
697
# then this should be refactored to introduce a tested proxy branch
698
# and a subclass of that for use in overriding clone() and ....
700
result.clone = self._make_reference_clone_function(result)
704
# formats which have no format string are not discoverable
705
# and not independently creatable, so are not registered.
706
__default_format = BzrBranchFormat5()
707
BranchFormat.register_format(__default_format)
708
BranchFormat.register_format(BranchReferenceFormat())
709
BranchFormat.set_default_format(__default_format)
710
_legacy_formats = [BzrBranchFormat4(),
490
713
class BzrBranch(Branch):
491
714
"""A branch stored in the actual filesystem.
493
716
Note that it's "local" in the context of the filesystem; it doesn't
494
717
really matter if it's on an nfs/smb/afs/coda/... share, as long as
495
718
it's writable, and can be accessed via the normal filesystem API.
501
If _lock_mode is true, a positive count of the number of times the
505
Lock object from bzrlib.lock.
507
720
# We actually expect this class to be somewhat short-lived; part of its
508
721
# purpose is to try to isolate what bits of the branch logic are tied to
509
722
# filesystem access, so that in a later step, we can extricate them to
510
723
# a separarte ("storage") class.
514
724
_inventory_weave = None
515
# If set to False (by a plugin, etc) BzrBranch will not set the
516
# mode on created files or directories
517
_set_file_mode = True
520
726
# Map some sort of prefix into a namespace
521
727
# stuff like "revno:10", "revid:", etc.
522
728
# This should match a prefix with a function which accepts
523
729
REVISION_NAMESPACES = {}
525
def push_stores(self, branch_to):
526
"""See Branch.push_stores."""
527
if (self._branch_format != branch_to._branch_format
528
or self._branch_format != 4):
529
from bzrlib.fetch import greedy_fetch
530
mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
531
self, self._branch_format, branch_to, branch_to._branch_format)
532
greedy_fetch(to_branch=branch_to, from_branch=self,
533
revision=self.last_revision())
536
store_pairs = ((self.text_store, branch_to.text_store),
537
(self.inventory_store, branch_to.inventory_store),
538
(self.revision_store, branch_to.revision_store))
540
for from_store, to_store in store_pairs:
541
copy_all(from_store, to_store)
542
except UnlistableStore:
543
raise UnlistableBranch(from_store)
545
def __init__(self, transport, init=False,
546
relax_version_check=False):
731
def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
732
relax_version_check=DEPRECATED_PARAMETER, _format=None,
733
_control_files=None, a_bzrdir=None, _repository=None):
547
734
"""Create new branch object at a particular location.
549
736
transport -- A Transport object, defining how to access files.
556
743
version is not applied. This is intended only for
557
744
upgrade/recovery type use; it's not guaranteed that
558
745
all operations will work on old format branches.
560
In the test suite, creation of new trees is tested using the
561
`ScratchBranch` class.
563
assert isinstance(transport, Transport), \
564
"%r is not a Transport" % transport
565
self._transport = transport
568
self._check_format(relax_version_check)
571
def get_store(name, compressed=True, prefixed=False):
572
relpath = self._rel_controlfilename(unicode(name))
573
store = TextStore(self._transport.clone(relpath),
574
dir_mode=self._dir_mode,
575
file_mode=self._file_mode,
577
compressed=compressed)
580
def get_weave(name, prefixed=False):
581
relpath = self._rel_controlfilename(unicode(name))
582
ws = WeaveStore(self._transport.clone(relpath),
584
dir_mode=self._dir_mode,
585
file_mode=self._file_mode)
586
if self._transport.should_cache():
587
ws.enable_cache = True
590
if self._branch_format == 4:
591
self.inventory_store = get_store('inventory-store')
592
self.text_store = get_store('text-store')
593
self.revision_store = get_store('revision-store')
594
elif self._branch_format == 5:
595
self.control_weaves = get_weave(u'')
596
self.weave_store = get_weave(u'weaves')
597
self.revision_store = get_store(u'revision-store', compressed=False)
598
elif self._branch_format == 6:
599
self.control_weaves = get_weave(u'')
600
self.weave_store = get_weave(u'weaves', prefixed=True)
601
self.revision_store = get_store(u'revision-store', compressed=False,
603
self.revision_store.register_suffix('sig')
604
self._transaction = None
748
self.bzrdir = bzrdir.BzrDir.open(transport.base)
750
self.bzrdir = a_bzrdir
751
self._transport = self.bzrdir.transport.clone('..')
752
self._base = self._transport.base
753
self._format = _format
754
if _control_files is None:
755
raise BzrBadParameterMissing('_control_files')
756
self.control_files = _control_files
757
if deprecated_passed(init):
758
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
759
"deprecated as of bzr 0.8. Please use Branch.create().",
763
# this is slower than before deprecation, oh well never mind.
765
self._initialize(transport.base)
766
self._check_format(_format)
767
if deprecated_passed(relax_version_check):
768
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
769
"relax_version_check parameter is deprecated as of bzr 0.8. "
770
"Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
774
if (not relax_version_check
775
and not self._format.is_supported()):
776
raise errors.UnsupportedFormatError(
777
'sorry, branch format %r not supported' % fmt,
778
['use a different bzr version',
779
'or remove the .bzr directory'
780
' and "bzr init" again'])
781
if deprecated_passed(transport):
782
warn("BzrBranch.__init__(transport=XXX...): The transport "
783
"parameter is deprecated as of bzr 0.8. "
784
"Please use Branch.open, or bzrdir.open_branch().",
787
self.repository = _repository
606
789
def __str__(self):
607
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
790
return '%s(%r)' % (self.__class__.__name__, self.base)
609
792
__repr__ = __str__
611
794
def __del__(self):
612
if self._lock_mode or self._lock:
613
# XXX: This should show something every time, and be suitable for
614
# headless operation and embedding
615
warn("branch %r was not explicitly unlocked" % self)
618
795
# TODO: It might be best to do this somewhere else,
619
796
# but it is nice for a Branch object to automatically
620
797
# cache it's information.
621
798
# Alternatively, we could have the Transport objects cache requests
622
799
# See the earlier discussion about how major objects (like Branch)
623
800
# should never expect their __del__ function to run.
801
# XXX: cache_root seems to be unused, 2006-01-13 mbp
624
802
if hasattr(self, 'cache_root') and self.cache_root is not None:
626
804
shutil.rmtree(self.cache_root)
629
807
self.cache_root = None
631
809
def _get_base(self):
633
return self._transport.base
636
812
base = property(_get_base, doc="The URL for the root of this branch.")
638
814
def _finish_transaction(self):
639
815
"""Exit the current transaction."""
640
if self._transaction is None:
641
raise errors.LockError('Branch %s is not in a transaction' %
643
transaction = self._transaction
644
self._transaction = None
816
return self.control_files._finish_transaction()
647
818
def get_transaction(self):
648
"""See Branch.get_transaction."""
649
if self._transaction is None:
650
return transactions.PassThroughTransaction()
652
return self._transaction
654
def _set_transaction(self, new_transaction):
819
"""Return the current active transaction.
821
If no transaction is active, this returns a passthrough object
822
for which all data is immediately flushed and no caching happens.
824
# this is an explicit function so that we can do tricky stuff
825
# when the storage in rev_storage is elsewhere.
826
# we probably need to hook the two 'lock a location' and
827
# 'have a transaction' together more delicately, so that
828
# we can have two locks (branch and storage) and one transaction
829
# ... and finishing the transaction unlocks both, but unlocking
830
# does not. - RBC 20051121
831
return self.control_files.get_transaction()
833
def _set_transaction(self, transaction):
655
834
"""Set a new active transaction."""
656
if self._transaction is not None:
657
raise errors.LockError('Branch %s is in a transaction already.' %
659
self._transaction = new_transaction
661
def lock_write(self):
662
#mutter("lock write: %s (%s)", self, self._lock_count)
663
# TODO: Upgrade locking to support using a Transport,
664
# and potentially a remote locking protocol
666
if self._lock_mode != 'w':
667
raise LockError("can't upgrade to a write lock from %r" %
669
self._lock_count += 1
671
self._lock = self._transport.lock_write(
672
self._rel_controlfilename('branch-lock'))
673
self._lock_mode = 'w'
675
self._set_transaction(transactions.PassThroughTransaction())
678
#mutter("lock read: %s (%s)", self, self._lock_count)
680
assert self._lock_mode in ('r', 'w'), \
681
"invalid lock mode %r" % self._lock_mode
682
self._lock_count += 1
684
self._lock = self._transport.lock_read(
685
self._rel_controlfilename('branch-lock'))
686
self._lock_mode = 'r'
688
self._set_transaction(transactions.ReadOnlyTransaction())
689
# 5K may be excessive, but hey, its a knob.
690
self.get_transaction().set_cache_size(5000)
693
#mutter("unlock: %s (%s)", self, self._lock_count)
694
if not self._lock_mode:
695
raise LockError('branch %r is not locked' % (self))
697
if self._lock_count > 1:
698
self._lock_count -= 1
700
self._finish_transaction()
703
self._lock_mode = self._lock_count = None
835
return self.control_files._set_transaction(transaction)
705
837
def abspath(self, name):
706
838
"""See Branch.abspath."""
707
return self._transport.abspath(name)
709
def _rel_controlfilename(self, file_or_path):
710
if not isinstance(file_or_path, basestring):
711
file_or_path = u'/'.join(file_or_path)
712
if file_or_path == '':
714
return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
716
def controlfilename(self, file_or_path):
717
"""See Branch.controlfilename."""
718
return self._transport.abspath(self._rel_controlfilename(file_or_path))
720
def controlfile(self, file_or_path, mode='r'):
721
"""See Branch.controlfile."""
724
relpath = self._rel_controlfilename(file_or_path)
725
#TODO: codecs.open() buffers linewise, so it was overloaded with
726
# a much larger buffer, do we need to do the same for getreader/getwriter?
728
return self._transport.get(relpath)
730
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
732
# XXX: Do we really want errors='replace'? Perhaps it should be
733
# an error, or at least reported, if there's incorrectly-encoded
734
# data inside a file.
735
# <https://launchpad.net/products/bzr/+bug/3823>
736
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
738
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
740
raise BzrError("invalid controlfile mode %r" % mode)
742
def put_controlfile(self, path, f, encode=True):
743
"""See Branch.put_controlfile."""
744
self.put_controlfiles([(path, f)], encode=encode)
746
def put_controlfiles(self, files, encode=True):
747
"""See Branch.put_controlfiles."""
750
for path, f in files:
752
if isinstance(f, basestring):
753
f = f.encode('utf-8', 'replace')
755
f = codecs.getwriter('utf-8')(f, errors='replace')
756
path = self._rel_controlfilename(path)
757
ctrl_files.append((path, f))
758
self._transport.put_multi(ctrl_files, mode=self._file_mode)
760
def _find_modes(self, path=None):
761
"""Determine the appropriate modes for files and directories."""
764
path = self._rel_controlfilename('')
765
st = self._transport.stat(path)
766
except errors.TransportNotPossible:
767
self._dir_mode = 0755
768
self._file_mode = 0644
770
self._dir_mode = st.st_mode & 07777
771
# Remove the sticky and execute bits for files
772
self._file_mode = self._dir_mode & ~07111
773
if not self._set_dir_mode:
774
self._dir_mode = None
775
if not self._set_file_mode:
776
self._file_mode = None
778
def _make_control(self):
779
from bzrlib.inventory import Inventory
780
from bzrlib.weavefile import write_weave_v5
781
from bzrlib.weave import Weave
783
# Create an empty inventory
785
# if we want per-tree root ids then this is the place to set
786
# them; they're not needed for now and so ommitted for
788
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
789
empty_inv = sio.getvalue()
791
bzrlib.weavefile.write_weave_v5(Weave(), sio)
792
empty_weave = sio.getvalue()
794
cfn = self._rel_controlfilename
795
# Since we don't have a .bzr directory, inherit the
796
# mode from the root directory
797
self._find_modes(u'.')
799
dirs = ['', 'revision-store', 'weaves']
801
"This is a Bazaar-NG control directory.\n"
802
"Do not change any files in this directory.\n"),
803
('branch-format', BZR_BRANCH_FORMAT_6),
804
('revision-history', ''),
807
('pending-merges', ''),
808
('inventory', empty_inv),
809
('inventory.weave', empty_weave),
810
('ancestry.weave', empty_weave)
812
self._transport.mkdir_multi([cfn(d) for d in dirs], mode=self._dir_mode)
813
self.put_controlfiles(files)
814
mutter('created control directory in ' + self._transport.base)
816
def _check_format(self, relax_version_check):
817
"""Check this branch format is supported.
819
The format level is stored, as an integer, in
820
self._branch_format for code that needs to check it later.
822
In the future, we might need different in-memory Branch
823
classes to support downlevel branches. But not yet.
839
return self.control_files._transport.abspath(name)
841
def _check_format(self, format):
842
"""Identify the branch format if needed.
844
The format is stored as a reference to the format object in
845
self._format for code that needs to check it later.
847
The format parameter is either None or the branch format class
848
used to open this branch.
850
FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
826
fmt = self.controlfile('branch-format', 'r').read()
828
raise NotBranchError(path=self.base)
829
mutter("got branch format %r", fmt)
830
if fmt == BZR_BRANCH_FORMAT_6:
831
self._branch_format = 6
832
elif fmt == BZR_BRANCH_FORMAT_5:
833
self._branch_format = 5
834
elif fmt == BZR_BRANCH_FORMAT_4:
835
self._branch_format = 4
837
if (not relax_version_check
838
and self._branch_format not in (5, 6)):
839
raise errors.UnsupportedFormatError(
840
'sorry, branch format %r not supported' % fmt,
841
['use a different bzr version',
842
'or remove the .bzr directory'
843
' and "bzr init" again'])
853
format = BzrBranchFormat.find_format(self.bzrdir)
854
self._format = format
855
mutter("got branch format %s", self._format)
846
858
def get_root_id(self):
847
859
"""See Branch.get_root_id."""
848
inv = self.get_inventory(self.last_revision())
849
return inv.root.file_id
860
tree = self.repository.revision_tree(self.last_revision())
861
return tree.inventory.root.file_id
863
def lock_write(self):
864
# TODO: test for failed two phase locks. This is known broken.
865
self.control_files.lock_write()
866
self.repository.lock_write()
869
# TODO: test for failed two phase locks. This is known broken.
870
self.control_files.lock_read()
871
self.repository.lock_read()
874
# TODO: test for failed two phase locks. This is known broken.
875
self.repository.unlock()
876
self.control_files.unlock()
878
def peek_lock_mode(self):
879
if self.control_files._lock_count == 0:
882
return self.control_files._lock_mode
852
885
def print_file(self, file, revision_id):
853
886
"""See Branch.print_file."""
854
tree = self.revision_tree(revision_id)
855
# use inventory as it was in that revision
856
file_id = tree.inventory.path2id(file)
859
revno = self.revision_id_to_revno(revision_id)
860
except errors.NoSuchRevision:
861
# TODO: This should not be BzrError,
862
# but NoSuchFile doesn't fit either
863
raise BzrError('%r is not present in revision %s'
864
% (file, revision_id))
866
raise BzrError('%r is not present in revision %s'
868
tree.print_file(file_id)
887
return self.repository.print_file(file, revision_id)
870
889
@needs_write_lock
871
890
def append_revision(self, *revision_ids):
879
898
@needs_write_lock
880
899
def set_revision_history(self, rev_history):
881
900
"""See Branch.set_revision_history."""
882
old_revision = self.last_revision()
883
new_revision = rev_history[-1]
884
self.put_controlfile('revision-history', '\n'.join(rev_history))
886
self.working_tree().set_last_revision(new_revision, old_revision)
887
except NoWorkingTree:
888
mutter('Unable to set_last_revision without a working tree.')
890
def has_revision(self, revision_id):
891
"""See Branch.has_revision."""
892
return (revision_id is None
893
or self.revision_store.has_id(revision_id))
896
def _get_revision_xml_file(self, revision_id):
897
if not revision_id or not isinstance(revision_id, basestring):
898
raise InvalidRevisionId(revision_id=revision_id, branch=self)
900
return self.revision_store.get(revision_id)
901
except (IndexError, KeyError):
902
raise bzrlib.errors.NoSuchRevision(self, revision_id)
904
def get_revision_xml(self, revision_id):
905
"""See Branch.get_revision_xml."""
906
return self._get_revision_xml_file(revision_id).read()
908
def get_revision(self, revision_id):
909
"""See Branch.get_revision."""
910
xml_file = self._get_revision_xml_file(revision_id)
913
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
914
except SyntaxError, e:
915
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
919
assert r.revision_id == revision_id
922
def get_revision_sha1(self, revision_id):
923
"""See Branch.get_revision_sha1."""
924
# In the future, revision entries will be signed. At that
925
# point, it is probably best *not* to include the signature
926
# in the revision hash. Because that lets you re-sign
927
# the revision, (add signatures/remove signatures) and still
928
# have all hash pointers stay consistent.
929
# But for now, just hash the contents.
930
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
932
def get_ancestry(self, revision_id):
933
"""See Branch.get_ancestry."""
934
if revision_id is None:
936
w = self._get_inventory_weave()
937
return [None] + map(w.idx_to_name,
938
w.inclusions([w.lookup(revision_id)]))
940
def _get_inventory_weave(self):
941
return self.control_weaves.get_weave('inventory',
942
self.get_transaction())
944
def get_inventory(self, revision_id):
945
"""See Branch.get_inventory."""
946
xml = self.get_inventory_xml(revision_id)
947
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
949
def get_inventory_xml(self, revision_id):
950
"""See Branch.get_inventory_xml."""
952
assert isinstance(revision_id, basestring), type(revision_id)
953
iw = self._get_inventory_weave()
954
return iw.get_text(iw.lookup(revision_id))
956
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
958
def get_inventory_sha1(self, revision_id):
959
"""See Branch.get_inventory_sha1."""
960
return self.get_revision(revision_id).inventory_sha1
962
def get_revision_inventory(self, revision_id):
963
"""See Branch.get_revision_inventory."""
964
# TODO: Unify this with get_inventory()
965
# bzr 0.0.6 and later imposes the constraint that the inventory_id
966
# must be the same as its revision, so this is trivial.
967
if revision_id == None:
968
# This does not make sense: if there is no revision,
969
# then it is the current tree inventory surely ?!
970
# and thus get_root_id() is something that looks at the last
971
# commit on the branch, and the get_root_id is an inventory check.
972
raise NotImplementedError
973
# return Inventory(self.get_root_id())
901
self.control_files.put_utf8(
902
'revision-history', '\n'.join(rev_history))
904
def get_revision_delta(self, revno):
905
"""Return the delta for one revision.
907
The delta is relative to its mainline predecessor, or the
908
empty tree for revision 1.
910
assert isinstance(revno, int)
911
rh = self.revision_history()
912
if not (1 <= revno <= len(rh)):
913
raise InvalidRevisionNumber(revno)
915
# revno is 1-based; list is 0-based
917
new_tree = self.repository.revision_tree(rh[revno-1])
919
old_tree = EmptyTree()
975
return self.get_inventory(revision_id)
921
old_tree = self.repository.revision_tree(rh[revno-2])
922
return compare_trees(old_tree, new_tree)
978
925
def revision_history(self):
979
926
"""See Branch.revision_history."""
927
# FIXME are transactions bound to control files ? RBC 20051121
980
928
transaction = self.get_transaction()
981
929
history = transaction.map.find_revision_history()
982
930
if history is not None:
983
931
mutter("cache hit for revision-history in %s", self)
984
932
return list(history)
985
933
history = [l.rstrip('\r\n') for l in
986
self.controlfile('revision-history', 'r').readlines()]
934
self.control_files.get_utf8('revision-history').readlines()]
987
935
transaction.map.add_revision_history(history)
988
936
# this call is disabled because revision_history is
989
937
# not really an object yet, and the transaction is for objects.
1093
1030
def set_parent(self, url):
1094
1031
"""See Branch.set_parent."""
1095
1032
# TODO: Maybe delete old location files?
1096
from bzrlib.atomicfile import AtomicFile
1097
f = AtomicFile(self.controlfilename('parent'))
1033
# URLs should never be unicode, even on the local fs,
1034
# FIXUP this and get_parent in a future branch format bump:
1035
# read and rewrite the file, and have the new format code read
1036
# using .get not .get_utf8. RBC 20060125
1037
self.control_files.put_utf8('parent', url + '\n')
1104
1039
def tree_config(self):
1105
1040
return TreeConfig(self)
1107
def sign_revision(self, revision_id, gpg_strategy):
1108
"""See Branch.sign_revision."""
1109
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1110
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1113
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1114
"""See Branch.store_revision_signature."""
1115
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1119
class ScratchBranch(BzrBranch):
1120
"""Special test class: a branch that cleans up after itself.
1122
>>> b = ScratchBranch()
1126
>>> b._transport.__del__()
1042
def _get_truncated_history(self, revision_id):
1043
history = self.revision_history()
1044
if revision_id is None:
1047
idx = history.index(revision_id)
1049
raise InvalidRevisionId(revision_id=revision, branch=self)
1050
return history[:idx+1]
1053
def _clone_weave(self, to_location, revision=None, basis_branch=None):
1055
from bzrlib.workingtree import WorkingTree
1056
assert isinstance(to_location, basestring)
1057
if basis_branch is not None:
1058
note("basis_branch is not supported for fast weave copy yet.")
1060
history = self._get_truncated_history(revision)
1061
if not bzrlib.osutils.lexists(to_location):
1062
os.mkdir(to_location)
1063
bzrdir_to = self.bzrdir._format.initialize(to_location)
1064
self.repository.clone(bzrdir_to)
1065
branch_to = bzrdir_to.create_branch()
1066
mutter("copy branch from %s to %s", self, branch_to)
1068
# FIXME duplicate code with base .clone().
1069
# .. would template method be useful here? RBC 20051207
1070
branch_to.set_parent(self.base)
1071
branch_to.append_revision(*history)
1072
WorkingTree.create(branch_to, branch_to.base)
1077
class BranchTestProviderAdapter(object):
1078
"""A tool to generate a suite testing multiple branch formats at once.
1080
This is done by copying the test once for each transport and injecting
1081
the transport_server, transport_readonly_server, and branch_format
1082
classes into each copy. Each copy is also given a new id() to make it
1131
def __init__(self, files=[], dirs=[], transport=None):
1132
"""Make a test branch.
1134
This creates a temporary directory and runs init-tree in it.
1136
If any files are listed, they are created in the working copy.
1138
if transport is None:
1139
transport = bzrlib.transport.local.ScratchTransport()
1140
super(ScratchBranch, self).__init__(transport, init=True)
1142
super(ScratchBranch, self).__init__(transport)
1145
self._transport.mkdir(d)
1148
self._transport.put(f, 'content of %s' % f)
1153
>>> orig = ScratchBranch(files=["file1", "file2"])
1154
>>> clone = orig.clone()
1155
>>> if os.name != 'nt':
1156
... os.path.samefile(orig.base, clone.base)
1158
... orig.base == clone.base
1161
>>> os.path.isfile(pathjoin(clone.base, "file1"))
1164
from shutil import copytree
1165
from bzrlib.osutils import mkdtemp
1168
copytree(self.base, base, symlinks=True)
1169
return ScratchBranch(
1170
transport=bzrlib.transport.local.ScratchTransport(base))
1086
def __init__(self, transport_server, transport_readonly_server, formats):
1087
self._transport_server = transport_server
1088
self._transport_readonly_server = transport_readonly_server
1089
self._formats = formats
1091
def adapt(self, test):
1092
result = TestSuite()
1093
for branch_format, bzrdir_format in self._formats:
1094
new_test = deepcopy(test)
1095
new_test.transport_server = self._transport_server
1096
new_test.transport_readonly_server = self._transport_readonly_server
1097
new_test.bzrdir_format = bzrdir_format
1098
new_test.branch_format = branch_format
1099
def make_new_test_id():
1100
new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1101
return lambda: new_id
1102
new_test.id = make_new_test_id()
1103
result.addTest(new_test)
1173
1107
######################################################################
1177
def is_control_file(filename):
1178
## FIXME: better check
1179
filename = normpath(filename)
1180
while filename != '':
1181
head, tail = os.path.split(filename)
1182
## mutter('check %r for control file' % ((head, tail), ))
1183
if tail == bzrlib.BZRDIR:
1185
if filename == head:
1111
@deprecated_function(zero_eight)
1112
def ScratchBranch(*args, **kwargs):
1113
"""See bzrlib.bzrdir.ScratchDir."""
1114
d = ScratchDir(*args, **kwargs)
1115
return d.open_branch()
1118
@deprecated_function(zero_eight)
1119
def is_control_file(*args, **kwargs):
1120
"""See bzrlib.workingtree.is_control_file."""
1121
return bzrlib.workingtree.is_control_file(*args, **kwargs)