1
# Copyright (C) 2005 Canonical Ltd
1
# Copyright (C) 2005, 2006 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
22
25
from warnings import warn
23
from cStringIO import StringIO
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,
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
32
32
import bzrlib.errors as errors
33
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
34
NoSuchRevision, HistoryMissing, NotBranchError,
35
DivergedBranches, LockError, UnlistableStore,
35
DivergedBranches, LockError,
36
UninitializableFormat,
36
38
UnlistableBranch, NoSuchFile, NotVersionedError,
40
import bzrlib.inventory as inventory
41
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,
38
49
from bzrlib.textui import show_status
39
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
42
from bzrlib.delta import compare_trees
50
from bzrlib.trace import mutter, note
43
51
from bzrlib.tree import EmptyTree, RevisionTree
44
from bzrlib.inventory import Inventory
52
from bzrlib.repository import Repository
53
from bzrlib.revision import (
54
get_intervening_revisions,
45
59
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
60
from bzrlib.symbol_versioning import *
49
61
import bzrlib.transactions as transactions
50
62
from bzrlib.transport import Transport, get_transport
63
from bzrlib.tree import EmptyTree, RevisionTree
53
from config import TreeConfig
56
68
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
57
69
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
58
70
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
59
## TODO: Maybe include checks for common corruption of newlines, etc?
73
# TODO: Maybe include checks for common corruption of newlines, etc?
62
75
# TODO: Some operations like log might retrieve the same revisions
63
76
# repeatedly to calculate deltas. We could perhaps have a weakref
64
77
# 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)
78
# cached in memory between lock and unlock operations. .. nb thats
79
# what the transaction identity map provides
94
82
######################################################################
194
177
raise NotImplementedError('abspath is abstract')
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')
179
def bind(self, other):
180
"""Bind the local branch the other branch.
182
:param other: The branch to bind to
185
raise errors.UpgradeRequired(self.base)
188
def fetch(self, from_branch, last_revision=None, pb=None):
189
"""Copy revisions from from_branch into this branch.
191
:param from_branch: Where to copy from.
192
:param last_revision: What revision to stop at (None for at the end
194
:param pb: An optional progress bar to use.
196
Returns the copied revision count and the failed revisions in a tuple:
199
if self.base == from_branch.base:
202
nested_pb = bzrlib.ui.ui_factory.nested_progress_bar()
207
from_branch.lock_read()
209
if last_revision is None:
210
pb.update('get source history')
211
from_history = from_branch.revision_history()
213
last_revision = from_history[-1]
215
# no history in the source branch
216
last_revision = NULL_REVISION
217
return self.repository.fetch(from_branch.repository,
218
revision_id=last_revision,
221
if nested_pb is not None:
225
def get_bound_location(self):
226
"""Return the URL of the branch we are bound to.
228
Older format branches cannot bind, please be sure to use a metadir
233
def get_master_branch(self):
234
"""Return the branch we are bound to.
236
:return: Either a Branch, or None
232
240
def get_root_id(self):
233
241
"""Return the id of this branches root"""
234
242
raise NotImplementedError('get_root_id is abstract')
236
def set_root_id(self, file_id):
237
raise NotImplementedError('set_root_id is abstract')
239
244
def print_file(self, file, revision_id):
240
245
"""Print `file` to stdout."""
241
246
raise NotImplementedError('print_file is abstract')
246
251
def set_revision_history(self, rev_history):
247
252
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
254
def revision_history(self):
313
255
"""Return sequence of revision hashes on to this branch."""
314
256
raise NotImplementedError('revision_history is abstract')
481
433
if revno < 1 or revno > self.revno():
482
434
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')
437
def clone(self, *args, **kwargs):
438
"""Clone this branch into to_bzrdir preserving all semantic values.
440
revision_id: if not None, the revision history in the new branch will
441
be truncated to end with revision_id.
443
# for API compatability, until 0.8 releases we provide the old api:
444
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
445
# after 0.8 releases, the *args and **kwargs should be changed:
446
# def clone(self, to_bzrdir, revision_id=None):
447
if (kwargs.get('to_location', None) or
448
kwargs.get('revision', None) or
449
kwargs.get('basis_branch', None) or
450
(len(args) and isinstance(args[0], basestring))):
451
# backwards compatability api:
452
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
453
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
456
basis_branch = args[2]
458
basis_branch = kwargs.get('basis_branch', None)
460
basis = basis_branch.bzrdir
465
revision_id = args[1]
467
revision_id = kwargs.get('revision', None)
472
# no default to raise if not provided.
473
url = kwargs.get('to_location')
474
return self.bzrdir.clone(url,
475
revision_id=revision_id,
476
basis=basis).open_branch()
478
# generate args by hand
480
revision_id = args[1]
482
revision_id = kwargs.get('revision_id', None)
486
# no default to raise if not provided.
487
to_bzrdir = kwargs.get('to_bzrdir')
488
result = self._format.initialize(to_bzrdir)
489
self.copy_content_into(result, revision_id=revision_id)
493
def sprout(self, to_bzrdir, revision_id=None):
494
"""Create a new line of development from the branch, into to_bzrdir.
496
revision_id: if not None, the revision history in the new branch will
497
be truncated to end with revision_id.
499
result = self._format.initialize(to_bzrdir)
500
self.copy_content_into(result, revision_id=revision_id)
501
result.set_parent(self.bzrdir.root_transport.base)
505
def copy_content_into(self, destination, revision_id=None):
506
"""Copy the content of self into destination.
508
revision_id: if not None, the revision history in the new branch will
509
be truncated to end with revision_id.
511
new_history = self.revision_history()
512
if revision_id is not None:
514
new_history = new_history[:new_history.index(revision_id) + 1]
516
rev = self.repository.get_revision(revision_id)
517
new_history = rev.get_history(self.repository)[1:]
518
destination.set_revision_history(new_history)
519
parent = self.get_parent()
521
destination.set_parent(parent)
524
class BranchFormat(object):
525
"""An encapsulation of the initialization and open routines for a format.
527
Formats provide three things:
528
* An initialization routine,
532
Formats are placed in an dict by their format string for reference
533
during branch opening. Its not required that these be instances, they
534
can be classes themselves with class methods - it simply depends on
535
whether state is needed for a given format or not.
537
Once a format is deprecated, just deprecate the initialize and open
538
methods on the format class. Do not deprecate the object, as the
539
object will be created every time regardless.
542
_default_format = None
543
"""The default format used for new branches."""
546
"""The known formats."""
549
def find_format(klass, a_bzrdir):
550
"""Return the format for the branch object in a_bzrdir."""
552
transport = a_bzrdir.get_branch_transport(None)
553
format_string = transport.get("format").read()
554
return klass._formats[format_string]
556
raise NotBranchError(path=transport.base)
558
raise errors.UnknownFormatError(format_string)
561
def get_default_format(klass):
562
"""Return the current default format."""
563
return klass._default_format
565
def get_format_string(self):
566
"""Return the ASCII format string that identifies this format."""
567
raise NotImplementedError(self.get_format_string)
569
def initialize(self, a_bzrdir):
570
"""Create a branch of this format in a_bzrdir."""
571
raise NotImplementedError(self.initialized)
573
def is_supported(self):
574
"""Is this format supported?
576
Supported formats can be initialized and opened.
577
Unsupported formats may not support initialization or committing or
578
some other features depending on the reason for not being supported.
582
def open(self, a_bzrdir, _found=False):
583
"""Return the branch object for a_bzrdir
585
_found is a private parameter, do not use it. It is used to indicate
586
if format probing has already be done.
588
raise NotImplementedError(self.open)
591
def register_format(klass, format):
592
klass._formats[format.get_format_string()] = format
595
def set_default_format(klass, format):
596
klass._default_format = format
599
def unregister_format(klass, format):
600
assert klass._formats[format.get_format_string()] is format
601
del klass._formats[format.get_format_string()]
604
return self.get_format_string().rstrip()
607
class BzrBranchFormat4(BranchFormat):
608
"""Bzr branch format 4.
611
- a revision-history file.
612
- a branch-lock lock file [ to be shared with the bzrdir ]
615
def initialize(self, a_bzrdir):
616
"""Create a branch of this format in a_bzrdir."""
617
mutter('creating branch in %s', a_bzrdir.transport.base)
618
branch_transport = a_bzrdir.get_branch_transport(self)
619
utf8_files = [('revision-history', ''),
622
control_files = LockableFiles(branch_transport, 'branch-lock',
624
control_files.create_lock()
625
control_files.lock_write()
627
for file, content in utf8_files:
628
control_files.put_utf8(file, content)
630
control_files.unlock()
631
return self.open(a_bzrdir, _found=True)
634
super(BzrBranchFormat4, self).__init__()
635
self._matchingbzrdir = bzrdir.BzrDirFormat6()
637
def open(self, a_bzrdir, _found=False):
638
"""Return the branch object for a_bzrdir
640
_found is a private parameter, do not use it. It is used to indicate
641
if format probing has already be done.
644
# we are being called directly and must probe.
645
raise NotImplementedError
646
return BzrBranch(_format=self,
647
_control_files=a_bzrdir._control_files,
649
_repository=a_bzrdir.open_repository())
652
return "Bazaar-NG branch format 4"
655
class BzrBranchFormat5(BranchFormat):
656
"""Bzr branch format 5.
659
- a revision-history file.
661
- a lock dir guarding the branch itself
662
- all of this stored in a branch/ subdirectory
663
- works with shared repositories.
665
This format is new in bzr 0.8.
668
def get_format_string(self):
669
"""See BranchFormat.get_format_string()."""
670
return "Bazaar-NG branch format 5\n"
672
def initialize(self, a_bzrdir):
673
"""Create a branch of this format in a_bzrdir."""
674
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
675
branch_transport = a_bzrdir.get_branch_transport(self)
676
utf8_files = [('revision-history', ''),
679
control_files = LockableFiles(branch_transport, 'lock', LockDir)
680
control_files.create_lock()
681
control_files.lock_write()
682
control_files.put_utf8('format', self.get_format_string())
684
for file, content in utf8_files:
685
control_files.put_utf8(file, content)
687
control_files.unlock()
688
return self.open(a_bzrdir, _found=True, )
691
super(BzrBranchFormat5, self).__init__()
692
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
694
def open(self, a_bzrdir, _found=False):
695
"""Return the branch object for a_bzrdir
697
_found is a private parameter, do not use it. It is used to indicate
698
if format probing has already be done.
701
format = BranchFormat.find_format(a_bzrdir)
702
assert format.__class__ == self.__class__
703
transport = a_bzrdir.get_branch_transport(None)
704
control_files = LockableFiles(transport, 'lock', LockDir)
705
return BzrBranch5(_format=self,
706
_control_files=control_files,
708
_repository=a_bzrdir.find_repository())
711
return "Bazaar-NG Metadir branch format 5"
714
class BranchReferenceFormat(BranchFormat):
715
"""Bzr branch reference format.
717
Branch references are used in implementing checkouts, they
718
act as an alias to the real branch which is at some other url.
725
def get_format_string(self):
726
"""See BranchFormat.get_format_string()."""
727
return "Bazaar-NG Branch Reference Format 1\n"
729
def initialize(self, a_bzrdir, target_branch=None):
730
"""Create a branch of this format in a_bzrdir."""
731
if target_branch is None:
732
# this format does not implement branch itself, thus the implicit
733
# creation contract must see it as uninitializable
734
raise errors.UninitializableFormat(self)
735
mutter('creating branch reference in %s', a_bzrdir.transport.base)
736
branch_transport = a_bzrdir.get_branch_transport(self)
737
# FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
738
branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
739
branch_transport.put('format', StringIO(self.get_format_string()))
740
return self.open(a_bzrdir, _found=True)
743
super(BranchReferenceFormat, self).__init__()
744
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
746
def _make_reference_clone_function(format, a_branch):
747
"""Create a clone() routine for a branch dynamically."""
748
def clone(to_bzrdir, revision_id=None):
749
"""See Branch.clone()."""
750
return format.initialize(to_bzrdir, a_branch)
751
# cannot obey revision_id limits when cloning a reference ...
752
# FIXME RBC 20060210 either nuke revision_id for clone, or
753
# emit some sort of warning/error to the caller ?!
756
def open(self, a_bzrdir, _found=False):
757
"""Return the branch that the branch reference in a_bzrdir points at.
759
_found is a private parameter, do not use it. It is used to indicate
760
if format probing has already be done.
763
format = BranchFormat.find_format(a_bzrdir)
764
assert format.__class__ == self.__class__
765
transport = a_bzrdir.get_branch_transport(None)
766
real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
767
result = real_bzrdir.open_branch()
768
# this changes the behaviour of result.clone to create a new reference
769
# rather than a copy of the content of the branch.
770
# I did not use a proxy object because that needs much more extensive
771
# testing, and we are only changing one behaviour at the moment.
772
# If we decide to alter more behaviours - i.e. the implicit nickname
773
# then this should be refactored to introduce a tested proxy branch
774
# and a subclass of that for use in overriding clone() and ....
776
result.clone = self._make_reference_clone_function(result)
780
# formats which have no format string are not discoverable
781
# and not independently creatable, so are not registered.
782
__default_format = BzrBranchFormat5()
783
BranchFormat.register_format(__default_format)
784
BranchFormat.register_format(BranchReferenceFormat())
785
BranchFormat.set_default_format(__default_format)
786
_legacy_formats = [BzrBranchFormat4(),
490
789
class BzrBranch(Branch):
491
790
"""A branch stored in the actual filesystem.
493
792
Note that it's "local" in the context of the filesystem; it doesn't
494
793
really matter if it's on an nfs/smb/afs/coda/... share, as long as
495
794
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
# We actually expect this class to be somewhat short-lived; part of its
508
# purpose is to try to isolate what bits of the branch logic are tied to
509
# filesystem access, so that in a later step, we can extricate them to
510
# a separarte ("storage") class.
514
_inventory_weave = None
516
# Map some sort of prefix into a namespace
517
# stuff like "revno:10", "revid:", etc.
518
# This should match a prefix with a function which accepts
519
REVISION_NAMESPACES = {}
521
def push_stores(self, branch_to):
522
"""See Branch.push_stores."""
523
if (self._branch_format != branch_to._branch_format
524
or self._branch_format != 4):
525
from bzrlib.fetch import greedy_fetch
526
mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
527
self, self._branch_format, branch_to, branch_to._branch_format)
528
greedy_fetch(to_branch=branch_to, from_branch=self,
529
revision=self.last_revision())
532
store_pairs = ((self.text_store, branch_to.text_store),
533
(self.inventory_store, branch_to.inventory_store),
534
(self.revision_store, branch_to.revision_store))
536
for from_store, to_store in store_pairs:
537
copy_all(from_store, to_store)
538
except UnlistableStore:
539
raise UnlistableBranch(from_store)
541
def __init__(self, transport, init=False,
542
relax_version_check=False):
797
def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
798
relax_version_check=DEPRECATED_PARAMETER, _format=None,
799
_control_files=None, a_bzrdir=None, _repository=None):
543
800
"""Create new branch object at a particular location.
545
802
transport -- A Transport object, defining how to access files.
552
809
version is not applied. This is intended only for
553
810
upgrade/recovery type use; it's not guaranteed that
554
811
all operations will work on old format branches.
556
In the test suite, creation of new trees is tested using the
557
`ScratchBranch` class.
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
814
self.bzrdir = bzrdir.BzrDir.open(transport.base)
816
self.bzrdir = a_bzrdir
817
self._transport = self.bzrdir.transport.clone('..')
818
self._base = self._transport.base
819
self._format = _format
820
if _control_files is None:
821
raise BzrBadParameterMissing('_control_files')
822
self.control_files = _control_files
823
if deprecated_passed(init):
824
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
825
"deprecated as of bzr 0.8. Please use Branch.create().",
829
# this is slower than before deprecation, oh well never mind.
831
self._initialize(transport.base)
832
self._check_format(_format)
833
if deprecated_passed(relax_version_check):
834
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
835
"relax_version_check parameter is deprecated as of bzr 0.8. "
836
"Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
840
if (not relax_version_check
841
and not self._format.is_supported()):
842
raise errors.UnsupportedFormatError(
843
'sorry, branch format %r not supported' % fmt,
844
['use a different bzr version',
845
'or remove the .bzr directory'
846
' and "bzr init" again'])
847
if deprecated_passed(transport):
848
warn("BzrBranch.__init__(transport=XXX...): The transport "
849
"parameter is deprecated as of bzr 0.8. "
850
"Please use Branch.open, or bzrdir.open_branch().",
853
self.repository = _repository
603
855
def __str__(self):
604
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
856
return '%s(%r)' % (self.__class__.__name__, self.base)
606
858
__repr__ = __str__
608
860
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)
615
861
# TODO: It might be best to do this somewhere else,
616
862
# but it is nice for a Branch object to automatically
617
863
# cache it's information.
618
864
# Alternatively, we could have the Transport objects cache requests
619
865
# See the earlier discussion about how major objects (like Branch)
620
866
# should never expect their __del__ function to run.
867
# XXX: cache_root seems to be unused, 2006-01-13 mbp
621
868
if hasattr(self, 'cache_root') and self.cache_root is not None:
623
870
shutil.rmtree(self.cache_root)
626
873
self.cache_root = None
628
875
def _get_base(self):
630
return self._transport.base
633
878
base = property(_get_base, doc="The URL for the root of this branch.")
635
880
def _finish_transaction(self):
636
881
"""Exit the current 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
882
return self.control_files._finish_transaction()
644
884
def get_transaction(self):
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):
885
"""Return the current active transaction.
887
If no transaction is active, this returns a passthrough object
888
for which all data is immediately flushed and no caching happens.
890
# this is an explicit function so that we can do tricky stuff
891
# when the storage in rev_storage is elsewhere.
892
# we probably need to hook the two 'lock a location' and
893
# 'have a transaction' together more delicately, so that
894
# we can have two locks (branch and storage) and one transaction
895
# ... and finishing the transaction unlocks both, but unlocking
896
# does not. - RBC 20051121
897
return self.control_files.get_transaction()
899
def _set_transaction(self, transaction):
652
900
"""Set a new active 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
901
return self.control_files._set_transaction(transaction)
702
903
def abspath(self, name):
703
904
"""See Branch.abspath."""
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.
905
return self.control_files._transport.abspath(name)
907
def _check_format(self, format):
908
"""Identify the branch format if needed.
910
The format is stored as a reference to the format object in
911
self._format for code that needs to check it later.
913
The format parameter is either None or the branch format class
914
used to open this branch.
916
FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
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'])
919
format = BzrBranchFormat.find_format(self.bzrdir)
920
self._format = format
921
mutter("got branch format %s", self._format)
821
924
def get_root_id(self):
822
925
"""See Branch.get_root_id."""
823
inv = self.get_inventory(self.last_revision())
824
return inv.root.file_id
926
tree = self.repository.revision_tree(self.last_revision())
927
return tree.inventory.root.file_id
929
def lock_write(self):
930
# TODO: test for failed two phase locks. This is known broken.
931
self.control_files.lock_write()
932
self.repository.lock_write()
935
# TODO: test for failed two phase locks. This is known broken.
936
self.control_files.lock_read()
937
self.repository.lock_read()
940
# TODO: test for failed two phase locks. This is known broken.
941
self.repository.unlock()
942
self.control_files.unlock()
944
def peek_lock_mode(self):
945
if self.control_files._lock_count == 0:
948
return self.control_files._lock_mode
827
951
def print_file(self, file, revision_id):
828
952
"""See Branch.print_file."""
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)
953
return self.repository.print_file(file, revision_id)
845
955
@needs_write_lock
846
956
def append_revision(self, *revision_ids):
854
964
@needs_write_lock
855
965
def set_revision_history(self, rev_history):
856
966
"""See Branch.set_revision_history."""
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)
967
self.control_files.put_utf8(
968
'revision-history', '\n'.join(rev_history))
969
transaction = self.get_transaction()
970
history = transaction.map.find_revision_history()
971
if history is not None:
972
# update the revision history in the identity map.
973
history[:] = list(rev_history)
974
# this call is disabled because revision_history is
975
# not really an object yet, and the transaction is for objects.
976
# transaction.register_dirty(history)
978
transaction.map.add_revision_history(rev_history)
979
# this call is disabled because revision_history is
980
# not really an object yet, and the transaction is for objects.
981
# transaction.register_clean(history)
983
def get_revision_delta(self, revno):
984
"""Return the delta for one revision.
986
The delta is relative to its mainline predecessor, or the
987
empty tree for revision 1.
989
assert isinstance(revno, int)
990
rh = self.revision_history()
991
if not (1 <= revno <= len(rh)):
992
raise InvalidRevisionNumber(revno)
994
# revno is 1-based; list is 0-based
996
new_tree = self.repository.revision_tree(rh[revno-1])
998
old_tree = EmptyTree()
1000
old_tree = self.repository.revision_tree(rh[revno-2])
1001
return compare_trees(old_tree, new_tree)
952
1003
@needs_read_lock
953
1004
def revision_history(self):
1068
1104
def set_parent(self, url):
1069
1105
"""See Branch.set_parent."""
1070
1106
# TODO: Maybe delete old location files?
1071
from bzrlib.atomicfile import AtomicFile
1072
f = AtomicFile(self.controlfilename('parent'))
1107
# URLs should never be unicode, even on the local fs,
1108
# FIXUP this and get_parent in a future branch format bump:
1109
# read and rewrite the file, and have the new format code read
1110
# using .get not .get_utf8. RBC 20060125
1111
self.control_files.put_utf8('parent', url + '\n')
1079
1113
def tree_config(self):
1080
1114
return TreeConfig(self)
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__()
1117
class BzrBranch5(BzrBranch):
1118
"""A format 5 branch. This supports new features over plan branches.
1120
It has support for a master_branch which is the data for bound branches.
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)
1128
super(BzrBranch5, self).__init__(_format=_format,
1129
_control_files=_control_files,
1131
_repository=_repository)
1134
def pull(self, source, overwrite=False, stop_revision=None):
1135
"""Updates branch.pull to be bound branch aware."""
1136
bound_location = self.get_bound_location()
1137
if source.base != bound_location:
1138
# not pulling from master, so we need to update master.
1139
master_branch = self.get_master_branch()
1141
master_branch.pull(source)
1142
source = master_branch
1143
return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
1145
def get_bound_location(self):
1147
return self.control_files.get_utf8('bound').read()[:-1]
1148
except errors.NoSuchFile:
1152
def get_master_branch(self):
1153
"""Return the branch we are bound to.
1155
:return: Either a Branch, or None
1157
This could memoise the branch, but if thats done
1158
it must be revalidated on each new lock.
1159
So for now we just dont memoise it.
1160
# RBC 20060304 review this decision.
1162
bound_loc = self.get_bound_location()
1166
return Branch.open(bound_loc)
1167
except (errors.NotBranchError, errors.ConnectionError), e:
1168
raise errors.BoundBranchConnectionFailure(
1172
def set_bound_location(self, location):
1173
"""Set the target where this branch is bound to.
1175
:param location: URL to the target branch
1178
self.control_files.put_utf8('bound', location+'\n')
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))
1181
self.control_files._transport.delete('bound')
1187
def bind(self, other):
1188
"""Bind the local branch the other branch.
1190
:param other: The branch to bind to
1193
# TODO: jam 20051230 Consider checking if the target is bound
1194
# It is debatable whether you should be able to bind to
1195
# a branch which is itself bound.
1196
# Committing is obviously forbidden,
1197
# but binding itself may not be.
1198
# Since we *have* to check at commit time, we don't
1199
# *need* to check here
1202
# we are now equal to or a suffix of other.
1204
# Since we have 'pulled' from the remote location,
1205
# now we should try to pull in the opposite direction
1206
# in case the local tree has more revisions than the
1208
# There may be a different check you could do here
1209
# rather than actually trying to install revisions remotely.
1210
# TODO: capture an exception which indicates the remote branch
1212
# If it is up-to-date, this probably should not be a failure
1214
# lock other for write so the revision-history syncing cannot race
1218
# if this does not error, other now has the same last rev we do
1219
# it can only error if the pull from other was concurrent with
1220
# a commit to other from someone else.
1222
# until we ditch revision-history, we need to sync them up:
1223
self.set_revision_history(other.revision_history())
1224
# now other and self are up to date with each other and have the
1225
# same revision-history.
1229
self.set_bound_location(other.base)
1233
"""If bound, unbind"""
1234
return self.set_bound_location(None)
1238
"""Synchronise this branch with the master branch if any.
1240
:return: None or the last_revision that was pivoted out during the
1243
master = self.get_master_branch()
1244
if master is not None:
1245
old_tip = self.last_revision()
1246
self.pull(master, overwrite=True)
1247
if old_tip in self.repository.get_ancestry(self.last_revision()):
1253
class BranchTestProviderAdapter(object):
1254
"""A tool to generate a suite testing multiple branch formats at once.
1256
This is done by copying the test once for each transport and injecting
1257
the transport_server, transport_readonly_server, and branch_format
1258
classes into each copy. Each copy is also given a new id() to make it
1262
def __init__(self, transport_server, transport_readonly_server, formats):
1263
self._transport_server = transport_server
1264
self._transport_readonly_server = transport_readonly_server
1265
self._formats = formats
1267
def adapt(self, test):
1268
result = TestSuite()
1269
for branch_format, bzrdir_format in self._formats:
1270
new_test = deepcopy(test)
1271
new_test.transport_server = self._transport_server
1272
new_test.transport_readonly_server = self._transport_readonly_server
1273
new_test.bzrdir_format = bzrdir_format
1274
new_test.branch_format = branch_format
1275
def make_new_test_id():
1276
new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1277
return lambda: new_id
1278
new_test.id = make_new_test_id()
1279
result.addTest(new_test)
1148
1283
######################################################################
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:
1160
if filename == head:
1287
@deprecated_function(zero_eight)
1288
def ScratchBranch(*args, **kwargs):
1289
"""See bzrlib.bzrdir.ScratchDir."""
1290
d = ScratchDir(*args, **kwargs)
1291
return d.open_branch()
1294
@deprecated_function(zero_eight)
1295
def is_control_file(*args, **kwargs):
1296
"""See bzrlib.workingtree.is_control_file."""
1297
return bzrlib.workingtree.is_control_file(*args, **kwargs)