1
# Copyright (C) 2005, 2006 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
from copy import deepcopy
19
from cStringIO import StringIO
20
from unittest import TestSuite
21
from warnings import warn
24
from bzrlib import bzrdir, errors, lockdir, osutils, revision, \
28
from bzrlib.config import TreeConfig
29
from bzrlib.decorators import needs_read_lock, needs_write_lock
30
import bzrlib.errors as errors
31
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches,
32
HistoryMissing, InvalidRevisionId,
33
InvalidRevisionNumber, LockError, NoSuchFile,
34
NoSuchRevision, NoWorkingTree, NotVersionedError,
35
NotBranchError, UninitializableFormat,
36
UnlistableStore, UnlistableBranch,
38
from bzrlib.lockable_files import LockableFiles, TransportLock
39
from bzrlib.symbol_versioning import (deprecated_function,
43
zero_eight, zero_nine,
45
from bzrlib.trace import mutter, note
48
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
49
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
50
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
53
# TODO: Maybe include checks for common corruption of newlines, etc?
55
# TODO: Some operations like log might retrieve the same revisions
56
# repeatedly to calculate deltas. We could perhaps have a weakref
57
# cache in memory to make this faster. In general anything can be
58
# cached in memory between lock and unlock operations. .. nb thats
59
# what the transaction identity map provides
62
######################################################################
66
"""Branch holding a history of revisions.
69
Base directory/url of the branch.
71
# this is really an instance variable - FIXME move it there
75
def __init__(self, *ignored, **ignored_too):
76
raise NotImplementedError('The Branch class is abstract')
79
"""Break a lock if one is present from another instance.
81
Uses the ui factory to ask for confirmation if the lock may be from
84
This will probe the repository for its lock as well.
86
self.control_files.break_lock()
87
self.repository.break_lock()
88
master = self.get_master_branch()
89
if master is not None:
93
@deprecated_method(zero_eight)
94
def open_downlevel(base):
95
"""Open a branch which may be of an old format."""
96
return Branch.open(base, _unsupported=True)
99
def open(base, _unsupported=False):
100
"""Open the branch rooted at base.
102
For instance, if the branch is at URL/.bzr/branch,
103
Branch.open(URL) -> a Branch instance.
105
control = bzrdir.BzrDir.open(base, _unsupported)
106
return control.open_branch(_unsupported)
109
def open_containing(url):
110
"""Open an existing branch which contains url.
112
This probes for a branch at url, and searches upwards from there.
114
Basically we keep looking up until we find the control directory or
115
run into the root. If there isn't one, raises NotBranchError.
116
If there is one and it is either an unrecognised format or an unsupported
117
format, UnknownFormatError or UnsupportedFormatError are raised.
118
If there is one, it is returned, along with the unused portion of url.
120
control, relpath = bzrdir.BzrDir.open_containing(url)
121
return control.open_branch(), relpath
124
@deprecated_function(zero_eight)
125
def initialize(base):
126
"""Create a new working tree and branch, rooted at 'base' (url)
128
NOTE: This will soon be deprecated in favour of creation
131
return bzrdir.BzrDir.create_standalone_workingtree(base).branch
133
def setup_caching(self, cache_root):
134
"""Subclasses that care about caching should override this, and set
135
up cached stores located under cache_root.
137
# seems to be unused, 2006-01-13 mbp
138
warn('%s is deprecated' % self.setup_caching)
139
self.cache_root = cache_root
141
def get_config(self):
142
return bzrlib.config.BranchConfig(self)
145
return self.get_config().get_nickname()
147
def _set_nick(self, nick):
148
self.get_config().set_user_option('nickname', nick)
150
nick = property(_get_nick, _set_nick)
153
raise NotImplementedError('is_locked is abstract')
155
def lock_write(self):
156
raise NotImplementedError('lock_write is abstract')
159
raise NotImplementedError('lock_read is abstract')
162
raise NotImplementedError('unlock is abstract')
164
def peek_lock_mode(self):
165
"""Return lock mode for the Branch: 'r', 'w' or None"""
166
raise NotImplementedError(self.peek_lock_mode)
168
def get_physical_lock_status(self):
169
raise NotImplementedError('get_physical_lock_status is abstract')
171
def abspath(self, name):
172
"""Return absolute filename for something in the branch
174
XXX: Robert Collins 20051017 what is this used for? why is it a branch
175
method and not a tree method.
177
raise NotImplementedError('abspath 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 = 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 = 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_commit_builder(self, parents, config=None, timestamp=None,
234
timezone=None, committer=None, revprops=None,
236
"""Obtain a CommitBuilder for this branch.
238
:param parents: Revision ids of the parents of the new revision.
239
:param config: Optional configuration to use.
240
:param timestamp: Optional timestamp recorded for commit.
241
:param timezone: Optional timezone for timestamp.
242
:param committer: Optional committer to set for commit.
243
:param revprops: Optional dictionary of revision properties.
244
:param revision_id: Optional revision id.
248
config = self.get_config()
250
return self.repository.get_commit_builder(self, parents, config,
251
timestamp, timezone, committer, revprops, revision_id)
253
def get_master_branch(self):
254
"""Return the branch we are bound to.
256
:return: Either a Branch, or None
260
def get_revision_delta(self, revno):
261
"""Return the delta for one revision.
263
The delta is relative to its mainline predecessor, or the
264
empty tree for revision 1.
266
assert isinstance(revno, int)
267
rh = self.revision_history()
268
if not (1 <= revno <= len(rh)):
269
raise InvalidRevisionNumber(revno)
270
return self.repository.get_revision_delta(rh[revno-1])
272
def get_root_id(self):
273
"""Return the id of this branches root"""
274
raise NotImplementedError('get_root_id is abstract')
276
def print_file(self, file, revision_id):
277
"""Print `file` to stdout."""
278
raise NotImplementedError('print_file is abstract')
280
def append_revision(self, *revision_ids):
281
raise NotImplementedError('append_revision is abstract')
283
def set_revision_history(self, rev_history):
284
raise NotImplementedError('set_revision_history is abstract')
286
def revision_history(self):
287
"""Return sequence of revision hashes on to this branch."""
288
raise NotImplementedError('revision_history is abstract')
291
"""Return current revision number for this branch.
293
That is equivalent to the number of revisions committed to
296
return len(self.revision_history())
299
"""Older format branches cannot bind or unbind."""
300
raise errors.UpgradeRequired(self.base)
302
def last_revision(self):
303
"""Return last patch hash, or None if no history."""
304
ph = self.revision_history()
310
def missing_revisions(self, other, stop_revision=None):
311
"""Return a list of new revisions that would perfectly fit.
313
If self and other have not diverged, return a list of the revisions
314
present in other, but missing from self.
316
self_history = self.revision_history()
317
self_len = len(self_history)
318
other_history = other.revision_history()
319
other_len = len(other_history)
320
common_index = min(self_len, other_len) -1
321
if common_index >= 0 and \
322
self_history[common_index] != other_history[common_index]:
323
raise DivergedBranches(self, other)
325
if stop_revision is None:
326
stop_revision = other_len
328
assert isinstance(stop_revision, int)
329
if stop_revision > other_len:
330
raise errors.NoSuchRevision(self, stop_revision)
331
return other_history[self_len:stop_revision]
333
def update_revisions(self, other, stop_revision=None):
334
"""Pull in new perfect-fit revisions.
336
:param other: Another Branch to pull from
337
:param stop_revision: Updated until the given revision
340
raise NotImplementedError('update_revisions is abstract')
342
def revision_id_to_revno(self, revision_id):
343
"""Given a revision id, return its revno"""
344
if revision_id is None:
346
history = self.revision_history()
348
return history.index(revision_id) + 1
350
raise bzrlib.errors.NoSuchRevision(self, revision_id)
352
def get_rev_id(self, revno, history=None):
353
"""Find the revision id of the specified revno."""
357
history = self.revision_history()
358
elif revno <= 0 or revno > len(history):
359
raise bzrlib.errors.NoSuchRevision(self, revno)
360
return history[revno - 1]
362
def pull(self, source, overwrite=False, stop_revision=None):
363
raise NotImplementedError('pull is abstract')
365
def basis_tree(self):
366
"""Return `Tree` object for last revision."""
367
return self.repository.revision_tree(self.last_revision())
369
def rename_one(self, from_rel, to_rel):
372
This can change the directory or the filename or both.
374
raise NotImplementedError('rename_one is abstract')
376
def move(self, from_paths, to_name):
379
to_name must exist as a versioned directory.
381
If to_name exists and is a directory, the files are moved into
382
it, keeping their old names. If it is a directory,
384
Note that to_name is only the last component of the new name;
385
this doesn't change the directory.
387
This returns a list of (from_path, to_path) pairs for each
390
raise NotImplementedError('move is abstract')
392
def get_parent(self):
393
"""Return the parent location of the branch.
395
This is the default location for push/pull/missing. The usual
396
pattern is that the user can override it by specifying a
399
raise NotImplementedError('get_parent is abstract')
401
def get_submit_branch(self):
402
"""Return the submit location of the branch.
404
This is the default location for bundle. The usual
405
pattern is that the user can override it by specifying a
408
return self.get_config().get_user_option('submit_branch')
410
def set_submit_branch(self, location):
411
"""Return the submit location of the branch.
413
This is the default location for bundle. The usual
414
pattern is that the user can override it by specifying a
417
self.get_config().set_user_option('submit_branch', location)
419
def get_push_location(self):
420
"""Return the None or the location to push this branch to."""
421
raise NotImplementedError('get_push_location is abstract')
423
def set_push_location(self, location):
424
"""Set a new push location for this branch."""
425
raise NotImplementedError('set_push_location is abstract')
427
def set_parent(self, url):
428
raise NotImplementedError('set_parent is abstract')
432
"""Synchronise this branch with the master branch if any.
434
:return: None or the last_revision pivoted out during the update.
438
def check_revno(self, revno):
440
Check whether a revno corresponds to any revision.
441
Zero (the NULL revision) is considered valid.
444
self.check_real_revno(revno)
446
def check_real_revno(self, revno):
448
Check whether a revno corresponds to a real revision.
449
Zero (the NULL revision) is considered invalid
451
if revno < 1 or revno > self.revno():
452
raise InvalidRevisionNumber(revno)
455
def clone(self, *args, **kwargs):
456
"""Clone this branch into to_bzrdir preserving all semantic values.
458
revision_id: if not None, the revision history in the new branch will
459
be truncated to end with revision_id.
461
# for API compatibility, until 0.8 releases we provide the old api:
462
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
463
# after 0.8 releases, the *args and **kwargs should be changed:
464
# def clone(self, to_bzrdir, revision_id=None):
465
if (kwargs.get('to_location', None) or
466
kwargs.get('revision', None) or
467
kwargs.get('basis_branch', None) or
468
(len(args) and isinstance(args[0], basestring))):
469
# backwards compatibility api:
470
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
471
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
474
basis_branch = args[2]
476
basis_branch = kwargs.get('basis_branch', None)
478
basis = basis_branch.bzrdir
483
revision_id = args[1]
485
revision_id = kwargs.get('revision', None)
490
# no default to raise if not provided.
491
url = kwargs.get('to_location')
492
return self.bzrdir.clone(url,
493
revision_id=revision_id,
494
basis=basis).open_branch()
496
# generate args by hand
498
revision_id = args[1]
500
revision_id = kwargs.get('revision_id', None)
504
# no default to raise if not provided.
505
to_bzrdir = kwargs.get('to_bzrdir')
506
result = self._format.initialize(to_bzrdir)
507
self.copy_content_into(result, revision_id=revision_id)
511
def sprout(self, to_bzrdir, revision_id=None):
512
"""Create a new line of development from the branch, into to_bzrdir.
514
revision_id: if not None, the revision history in the new branch will
515
be truncated to end with revision_id.
517
result = self._format.initialize(to_bzrdir)
518
self.copy_content_into(result, revision_id=revision_id)
519
result.set_parent(self.bzrdir.root_transport.base)
523
def copy_content_into(self, destination, revision_id=None):
524
"""Copy the content of self into destination.
526
revision_id: if not None, the revision history in the new branch will
527
be truncated to end with revision_id.
529
new_history = self.revision_history()
530
if revision_id is not None:
532
new_history = new_history[:new_history.index(revision_id) + 1]
534
rev = self.repository.get_revision(revision_id)
535
new_history = rev.get_history(self.repository)[1:]
536
destination.set_revision_history(new_history)
538
parent = self.get_parent()
539
except errors.InaccessibleParent, e:
540
mutter('parent was not accessible to copy: %s', e)
543
destination.set_parent(parent)
547
"""Check consistency of the branch.
549
In particular this checks that revisions given in the revision-history
550
do actually match up in the revision graph, and that they're all
551
present in the repository.
553
Callers will typically also want to check the repository.
555
:return: A BranchCheckResult.
557
mainline_parent_id = None
558
for revision_id in self.revision_history():
560
revision = self.repository.get_revision(revision_id)
561
except errors.NoSuchRevision, e:
562
raise errors.BzrCheckError("mainline revision {%s} not in repository"
564
# In general the first entry on the revision history has no parents.
565
# But it's not illegal for it to have parents listed; this can happen
566
# in imports from Arch when the parents weren't reachable.
567
if mainline_parent_id is not None:
568
if mainline_parent_id not in revision.parent_ids:
569
raise errors.BzrCheckError("previous revision {%s} not listed among "
571
% (mainline_parent_id, revision_id))
572
mainline_parent_id = revision_id
573
return BranchCheckResult(self)
576
class BranchFormat(object):
577
"""An encapsulation of the initialization and open routines for a format.
579
Formats provide three things:
580
* An initialization routine,
584
Formats are placed in an dict by their format string for reference
585
during branch opening. Its not required that these be instances, they
586
can be classes themselves with class methods - it simply depends on
587
whether state is needed for a given format or not.
589
Once a format is deprecated, just deprecate the initialize and open
590
methods on the format class. Do not deprecate the object, as the
591
object will be created every time regardless.
594
_default_format = None
595
"""The default format used for new branches."""
598
"""The known formats."""
601
def find_format(klass, a_bzrdir):
602
"""Return the format for the branch object in a_bzrdir."""
604
transport = a_bzrdir.get_branch_transport(None)
605
format_string = transport.get("format").read()
606
return klass._formats[format_string]
608
raise NotBranchError(path=transport.base)
610
raise errors.UnknownFormatError(format=format_string)
613
def get_default_format(klass):
614
"""Return the current default format."""
615
return klass._default_format
617
def get_format_string(self):
618
"""Return the ASCII format string that identifies this format."""
619
raise NotImplementedError(self.get_format_string)
621
def get_format_description(self):
622
"""Return the short format description for this format."""
623
raise NotImplementedError(self.get_format_string)
625
def initialize(self, a_bzrdir):
626
"""Create a branch of this format in a_bzrdir."""
627
raise NotImplementedError(self.initialize)
629
def is_supported(self):
630
"""Is this format supported?
632
Supported formats can be initialized and opened.
633
Unsupported formats may not support initialization or committing or
634
some other features depending on the reason for not being supported.
638
def open(self, a_bzrdir, _found=False):
639
"""Return the branch object for a_bzrdir
641
_found is a private parameter, do not use it. It is used to indicate
642
if format probing has already be done.
644
raise NotImplementedError(self.open)
647
def register_format(klass, format):
648
klass._formats[format.get_format_string()] = format
651
def set_default_format(klass, format):
652
klass._default_format = format
655
def unregister_format(klass, format):
656
assert klass._formats[format.get_format_string()] is format
657
del klass._formats[format.get_format_string()]
660
return self.get_format_string().rstrip()
663
class BzrBranchFormat4(BranchFormat):
664
"""Bzr branch format 4.
667
- a revision-history file.
668
- a branch-lock lock file [ to be shared with the bzrdir ]
671
def get_format_description(self):
672
"""See BranchFormat.get_format_description()."""
673
return "Branch format 4"
675
def initialize(self, a_bzrdir):
676
"""Create a branch of this format in a_bzrdir."""
677
mutter('creating branch in %s', a_bzrdir.transport.base)
678
branch_transport = a_bzrdir.get_branch_transport(self)
679
utf8_files = [('revision-history', ''),
682
control_files = LockableFiles(branch_transport, 'branch-lock',
684
control_files.create_lock()
685
control_files.lock_write()
687
for file, content in utf8_files:
688
control_files.put_utf8(file, content)
690
control_files.unlock()
691
return self.open(a_bzrdir, _found=True)
694
super(BzrBranchFormat4, self).__init__()
695
self._matchingbzrdir = bzrdir.BzrDirFormat6()
697
def open(self, a_bzrdir, _found=False):
698
"""Return the branch object for a_bzrdir
700
_found is a private parameter, do not use it. It is used to indicate
701
if format probing has already be done.
704
# we are being called directly and must probe.
705
raise NotImplementedError
706
return BzrBranch(_format=self,
707
_control_files=a_bzrdir._control_files,
709
_repository=a_bzrdir.open_repository())
712
return "Bazaar-NG branch format 4"
715
class BzrBranchFormat5(BranchFormat):
716
"""Bzr branch format 5.
719
- a revision-history file.
721
- a lock dir guarding the branch itself
722
- all of this stored in a branch/ subdirectory
723
- works with shared repositories.
725
This format is new in bzr 0.8.
728
def get_format_string(self):
729
"""See BranchFormat.get_format_string()."""
730
return "Bazaar-NG branch format 5\n"
732
def get_format_description(self):
733
"""See BranchFormat.get_format_description()."""
734
return "Branch format 5"
736
def initialize(self, a_bzrdir):
737
"""Create a branch of this format in a_bzrdir."""
738
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
739
branch_transport = a_bzrdir.get_branch_transport(self)
740
utf8_files = [('revision-history', ''),
743
control_files = LockableFiles(branch_transport, 'lock', lockdir.LockDir)
744
control_files.create_lock()
745
control_files.lock_write()
746
control_files.put_utf8('format', self.get_format_string())
748
for file, content in utf8_files:
749
control_files.put_utf8(file, content)
751
control_files.unlock()
752
return self.open(a_bzrdir, _found=True, )
755
super(BzrBranchFormat5, self).__init__()
756
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
758
def open(self, a_bzrdir, _found=False):
759
"""Return the branch object for a_bzrdir
761
_found is a private parameter, do not use it. It is used to indicate
762
if format probing has already be done.
765
format = BranchFormat.find_format(a_bzrdir)
766
assert format.__class__ == self.__class__
767
transport = a_bzrdir.get_branch_transport(None)
768
control_files = LockableFiles(transport, 'lock', lockdir.LockDir)
769
return BzrBranch5(_format=self,
770
_control_files=control_files,
772
_repository=a_bzrdir.find_repository())
775
return "Bazaar-NG Metadir branch format 5"
778
class BranchReferenceFormat(BranchFormat):
779
"""Bzr branch reference format.
781
Branch references are used in implementing checkouts, they
782
act as an alias to the real branch which is at some other url.
789
def get_format_string(self):
790
"""See BranchFormat.get_format_string()."""
791
return "Bazaar-NG Branch Reference Format 1\n"
793
def get_format_description(self):
794
"""See BranchFormat.get_format_description()."""
795
return "Checkout reference format 1"
797
def initialize(self, a_bzrdir, target_branch=None):
798
"""Create a branch of this format in a_bzrdir."""
799
if target_branch is None:
800
# this format does not implement branch itself, thus the implicit
801
# creation contract must see it as uninitializable
802
raise errors.UninitializableFormat(self)
803
mutter('creating branch reference in %s', a_bzrdir.transport.base)
804
branch_transport = a_bzrdir.get_branch_transport(self)
805
# FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
806
branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
807
branch_transport.put('format', StringIO(self.get_format_string()))
808
return self.open(a_bzrdir, _found=True)
811
super(BranchReferenceFormat, self).__init__()
812
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
814
def _make_reference_clone_function(format, a_branch):
815
"""Create a clone() routine for a branch dynamically."""
816
def clone(to_bzrdir, revision_id=None):
817
"""See Branch.clone()."""
818
return format.initialize(to_bzrdir, a_branch)
819
# cannot obey revision_id limits when cloning a reference ...
820
# FIXME RBC 20060210 either nuke revision_id for clone, or
821
# emit some sort of warning/error to the caller ?!
824
def open(self, a_bzrdir, _found=False):
825
"""Return the branch that the branch reference in a_bzrdir points at.
827
_found is a private parameter, do not use it. It is used to indicate
828
if format probing has already be done.
831
format = BranchFormat.find_format(a_bzrdir)
832
assert format.__class__ == self.__class__
833
transport = a_bzrdir.get_branch_transport(None)
834
real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
835
result = real_bzrdir.open_branch()
836
# this changes the behaviour of result.clone to create a new reference
837
# rather than a copy of the content of the branch.
838
# I did not use a proxy object because that needs much more extensive
839
# testing, and we are only changing one behaviour at the moment.
840
# If we decide to alter more behaviours - i.e. the implicit nickname
841
# then this should be refactored to introduce a tested proxy branch
842
# and a subclass of that for use in overriding clone() and ....
844
result.clone = self._make_reference_clone_function(result)
848
# formats which have no format string are not discoverable
849
# and not independently creatable, so are not registered.
850
__default_format = BzrBranchFormat5()
851
BranchFormat.register_format(__default_format)
852
BranchFormat.register_format(BranchReferenceFormat())
853
BranchFormat.set_default_format(__default_format)
854
_legacy_formats = [BzrBranchFormat4(),
857
class BzrBranch(Branch):
858
"""A branch stored in the actual filesystem.
860
Note that it's "local" in the context of the filesystem; it doesn't
861
really matter if it's on an nfs/smb/afs/coda/... share, as long as
862
it's writable, and can be accessed via the normal filesystem API.
865
def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
866
relax_version_check=DEPRECATED_PARAMETER, _format=None,
867
_control_files=None, a_bzrdir=None, _repository=None):
868
"""Create new branch object at a particular location.
870
transport -- A Transport object, defining how to access files.
872
init -- If True, create new control files in a previously
873
unversioned directory. If False, the branch must already
876
relax_version_check -- If true, the usual check for the branch
877
version is not applied. This is intended only for
878
upgrade/recovery type use; it's not guaranteed that
879
all operations will work on old format branches.
882
self.bzrdir = bzrdir.BzrDir.open(transport.base)
884
self.bzrdir = a_bzrdir
885
self._transport = self.bzrdir.transport.clone('..')
886
self._base = self._transport.base
887
self._format = _format
888
if _control_files is None:
889
raise ValueError('BzrBranch _control_files is None')
890
self.control_files = _control_files
891
if deprecated_passed(init):
892
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
893
"deprecated as of bzr 0.8. Please use Branch.create().",
897
# this is slower than before deprecation, oh well never mind.
899
self._initialize(transport.base)
900
self._check_format(_format)
901
if deprecated_passed(relax_version_check):
902
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
903
"relax_version_check parameter is deprecated as of bzr 0.8. "
904
"Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
908
if (not relax_version_check
909
and not self._format.is_supported()):
910
raise errors.UnsupportedFormatError(format=fmt)
911
if deprecated_passed(transport):
912
warn("BzrBranch.__init__(transport=XXX...): The transport "
913
"parameter is deprecated as of bzr 0.8. "
914
"Please use Branch.open, or bzrdir.open_branch().",
917
self.repository = _repository
920
return '%s(%r)' % (self.__class__.__name__, self.base)
925
# TODO: It might be best to do this somewhere else,
926
# but it is nice for a Branch object to automatically
927
# cache it's information.
928
# Alternatively, we could have the Transport objects cache requests
929
# See the earlier discussion about how major objects (like Branch)
930
# should never expect their __del__ function to run.
931
# XXX: cache_root seems to be unused, 2006-01-13 mbp
932
if hasattr(self, 'cache_root') and self.cache_root is not None:
934
osutils.rmtree(self.cache_root)
937
self.cache_root = None
942
base = property(_get_base, doc="The URL for the root of this branch.")
944
def _finish_transaction(self):
945
"""Exit the current transaction."""
946
return self.control_files._finish_transaction()
948
def get_transaction(self):
949
"""Return the current active transaction.
951
If no transaction is active, this returns a passthrough object
952
for which all data is immediately flushed and no caching happens.
954
# this is an explicit function so that we can do tricky stuff
955
# when the storage in rev_storage is elsewhere.
956
# we probably need to hook the two 'lock a location' and
957
# 'have a transaction' together more delicately, so that
958
# we can have two locks (branch and storage) and one transaction
959
# ... and finishing the transaction unlocks both, but unlocking
960
# does not. - RBC 20051121
961
return self.control_files.get_transaction()
963
def _set_transaction(self, transaction):
964
"""Set a new active transaction."""
965
return self.control_files._set_transaction(transaction)
967
def abspath(self, name):
968
"""See Branch.abspath."""
969
return self.control_files._transport.abspath(name)
971
def _check_format(self, format):
972
"""Identify the branch format if needed.
974
The format is stored as a reference to the format object in
975
self._format for code that needs to check it later.
977
The format parameter is either None or the branch format class
978
used to open this branch.
980
FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
983
format = BranchFormat.find_format(self.bzrdir)
984
self._format = format
985
mutter("got branch format %s", self._format)
988
def get_root_id(self):
989
"""See Branch.get_root_id."""
990
tree = self.repository.revision_tree(self.last_revision())
991
return tree.inventory.root.file_id
994
return self.control_files.is_locked()
996
def lock_write(self):
997
self.repository.lock_write()
999
self.control_files.lock_write()
1001
self.repository.unlock()
1004
def lock_read(self):
1005
self.repository.lock_read()
1007
self.control_files.lock_read()
1009
self.repository.unlock()
1013
# TODO: test for failed two phase locks. This is known broken.
1015
self.control_files.unlock()
1017
self.repository.unlock()
1019
def peek_lock_mode(self):
1020
if self.control_files._lock_count == 0:
1023
return self.control_files._lock_mode
1025
def get_physical_lock_status(self):
1026
return self.control_files.get_physical_lock_status()
1029
def print_file(self, file, revision_id):
1030
"""See Branch.print_file."""
1031
return self.repository.print_file(file, revision_id)
1034
def append_revision(self, *revision_ids):
1035
"""See Branch.append_revision."""
1036
for revision_id in revision_ids:
1037
mutter("add {%s} to revision-history" % revision_id)
1038
rev_history = self.revision_history()
1039
rev_history.extend(revision_ids)
1040
self.set_revision_history(rev_history)
1043
def set_revision_history(self, rev_history):
1044
"""See Branch.set_revision_history."""
1045
self.control_files.put_utf8(
1046
'revision-history', '\n'.join(rev_history))
1047
transaction = self.get_transaction()
1048
history = transaction.map.find_revision_history()
1049
if history is not None:
1050
# update the revision history in the identity map.
1051
history[:] = list(rev_history)
1052
# this call is disabled because revision_history is
1053
# not really an object yet, and the transaction is for objects.
1054
# transaction.register_dirty(history)
1056
transaction.map.add_revision_history(rev_history)
1057
# this call is disabled because revision_history is
1058
# not really an object yet, and the transaction is for objects.
1059
# transaction.register_clean(history)
1062
def revision_history(self):
1063
"""See Branch.revision_history."""
1064
transaction = self.get_transaction()
1065
history = transaction.map.find_revision_history()
1066
if history is not None:
1067
mutter("cache hit for revision-history in %s", self)
1068
return list(history)
1069
history = [l.rstrip('\r\n') for l in
1070
self.control_files.get_utf8('revision-history').readlines()]
1071
transaction.map.add_revision_history(history)
1072
# this call is disabled because revision_history is
1073
# not really an object yet, and the transaction is for objects.
1074
# transaction.register_clean(history, precious=True)
1075
return list(history)
1078
def generate_revision_history(self, revision_id, last_rev=None,
1080
"""Create a new revision history that will finish with revision_id.
1082
:param revision_id: the new tip to use.
1083
:param last_rev: The previous last_revision. If not None, then this
1084
must be a ancestory of revision_id, or DivergedBranches is raised.
1085
:param other_branch: The other branch that DivergedBranches should
1086
raise with respect to.
1088
# stop_revision must be a descendant of last_revision
1089
stop_graph = self.repository.get_revision_graph(revision_id)
1090
if last_rev is not None and last_rev not in stop_graph:
1091
# our previous tip is not merged into stop_revision
1092
raise errors.DivergedBranches(self, other_branch)
1093
# make a new revision history from the graph
1094
current_rev_id = revision_id
1096
while current_rev_id not in (None, revision.NULL_REVISION):
1097
new_history.append(current_rev_id)
1098
current_rev_id_parents = stop_graph[current_rev_id]
1100
current_rev_id = current_rev_id_parents[0]
1102
current_rev_id = None
1103
new_history.reverse()
1104
self.set_revision_history(new_history)
1107
def update_revisions(self, other, stop_revision=None):
1108
"""See Branch.update_revisions."""
1111
if stop_revision is None:
1112
stop_revision = other.last_revision()
1113
if stop_revision is None:
1114
# if there are no commits, we're done.
1116
# whats the current last revision, before we fetch [and change it
1118
last_rev = self.last_revision()
1119
# we fetch here regardless of whether we need to so that we pickup
1121
self.fetch(other, stop_revision)
1122
my_ancestry = self.repository.get_ancestry(last_rev)
1123
if stop_revision in my_ancestry:
1124
# last_revision is a descendant of stop_revision
1126
self.generate_revision_history(stop_revision, last_rev=last_rev,
1131
def basis_tree(self):
1132
"""See Branch.basis_tree."""
1133
return self.repository.revision_tree(self.last_revision())
1135
@deprecated_method(zero_eight)
1136
def working_tree(self):
1137
"""Create a Working tree object for this branch."""
1139
from bzrlib.transport.local import LocalTransport
1140
if (self.base.find('://') != -1 or
1141
not isinstance(self._transport, LocalTransport)):
1142
raise NoWorkingTree(self.base)
1143
return self.bzrdir.open_workingtree()
1146
def pull(self, source, overwrite=False, stop_revision=None):
1147
"""See Branch.pull."""
1150
old_count = len(self.revision_history())
1152
self.update_revisions(source,stop_revision)
1153
except DivergedBranches:
1157
self.set_revision_history(source.revision_history())
1158
new_count = len(self.revision_history())
1159
return new_count - old_count
1163
def get_parent(self):
1164
"""See Branch.get_parent."""
1166
_locs = ['parent', 'pull', 'x-pull']
1167
assert self.base[-1] == '/'
1170
parent = self.control_files.get(l).read().strip('\n')
1173
# This is an old-format absolute path to a local branch
1174
# turn it into a url
1175
if parent.startswith('/'):
1176
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1178
return urlutils.join(self.base[:-1], parent)
1179
except errors.InvalidURLJoin, e:
1180
raise errors.InaccessibleParent(parent, self.base)
1183
def get_push_location(self):
1184
"""See Branch.get_push_location."""
1185
push_loc = self.get_config().get_user_option('push_location')
1188
def set_push_location(self, location):
1189
"""See Branch.set_push_location."""
1190
self.get_config().set_user_option('push_location', location,
1194
def set_parent(self, url):
1195
"""See Branch.set_parent."""
1196
# TODO: Maybe delete old location files?
1197
# URLs should never be unicode, even on the local fs,
1198
# FIXUP this and get_parent in a future branch format bump:
1199
# read and rewrite the file, and have the new format code read
1200
# using .get not .get_utf8. RBC 20060125
1202
self.control_files._transport.delete('parent')
1204
if isinstance(url, unicode):
1206
url = url.encode('ascii')
1207
except UnicodeEncodeError:
1208
raise bzrlib.errors.InvalidURL(url,
1209
"Urls must be 7-bit ascii, "
1210
"use bzrlib.urlutils.escape")
1212
url = urlutils.relative_url(self.base, url)
1213
self.control_files.put('parent', url + '\n')
1215
@deprecated_function(zero_nine)
1216
def tree_config(self):
1217
"""DEPRECATED; call get_config instead.
1218
TreeConfig has become part of BranchConfig."""
1219
return TreeConfig(self)
1222
class BzrBranch5(BzrBranch):
1223
"""A format 5 branch. This supports new features over plan branches.
1225
It has support for a master_branch which is the data for bound branches.
1233
super(BzrBranch5, self).__init__(_format=_format,
1234
_control_files=_control_files,
1236
_repository=_repository)
1239
def pull(self, source, overwrite=False, stop_revision=None):
1240
"""Updates branch.pull to be bound branch aware."""
1241
bound_location = self.get_bound_location()
1242
if source.base != bound_location:
1243
# not pulling from master, so we need to update master.
1244
master_branch = self.get_master_branch()
1246
master_branch.pull(source)
1247
source = master_branch
1248
return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
1250
def get_bound_location(self):
1252
return self.control_files.get_utf8('bound').read()[:-1]
1253
except errors.NoSuchFile:
1257
def get_master_branch(self):
1258
"""Return the branch we are bound to.
1260
:return: Either a Branch, or None
1262
This could memoise the branch, but if thats done
1263
it must be revalidated on each new lock.
1264
So for now we just don't memoise it.
1265
# RBC 20060304 review this decision.
1267
bound_loc = self.get_bound_location()
1271
return Branch.open(bound_loc)
1272
except (errors.NotBranchError, errors.ConnectionError), e:
1273
raise errors.BoundBranchConnectionFailure(
1277
def set_bound_location(self, location):
1278
"""Set the target where this branch is bound to.
1280
:param location: URL to the target branch
1283
self.control_files.put_utf8('bound', location+'\n')
1286
self.control_files._transport.delete('bound')
1292
def bind(self, other):
1293
"""Bind the local branch the other branch.
1295
:param other: The branch to bind to
1298
# TODO: jam 20051230 Consider checking if the target is bound
1299
# It is debatable whether you should be able to bind to
1300
# a branch which is itself bound.
1301
# Committing is obviously forbidden,
1302
# but binding itself may not be.
1303
# Since we *have* to check at commit time, we don't
1304
# *need* to check here
1307
# we are now equal to or a suffix of other.
1309
# Since we have 'pulled' from the remote location,
1310
# now we should try to pull in the opposite direction
1311
# in case the local tree has more revisions than the
1313
# There may be a different check you could do here
1314
# rather than actually trying to install revisions remotely.
1315
# TODO: capture an exception which indicates the remote branch
1317
# If it is up-to-date, this probably should not be a failure
1319
# lock other for write so the revision-history syncing cannot race
1323
# if this does not error, other now has the same last rev we do
1324
# it can only error if the pull from other was concurrent with
1325
# a commit to other from someone else.
1327
# until we ditch revision-history, we need to sync them up:
1328
self.set_revision_history(other.revision_history())
1329
# now other and self are up to date with each other and have the
1330
# same revision-history.
1334
self.set_bound_location(other.base)
1338
"""If bound, unbind"""
1339
return self.set_bound_location(None)
1343
"""Synchronise this branch with the master branch if any.
1345
:return: None or the last_revision that was pivoted out during the
1348
master = self.get_master_branch()
1349
if master is not None:
1350
old_tip = self.last_revision()
1351
self.pull(master, overwrite=True)
1352
if old_tip in self.repository.get_ancestry(self.last_revision()):
1358
class BranchTestProviderAdapter(object):
1359
"""A tool to generate a suite testing multiple branch formats at once.
1361
This is done by copying the test once for each transport and injecting
1362
the transport_server, transport_readonly_server, and branch_format
1363
classes into each copy. Each copy is also given a new id() to make it
1367
def __init__(self, transport_server, transport_readonly_server, formats):
1368
self._transport_server = transport_server
1369
self._transport_readonly_server = transport_readonly_server
1370
self._formats = formats
1372
def adapt(self, test):
1373
result = TestSuite()
1374
for branch_format, bzrdir_format in self._formats:
1375
new_test = deepcopy(test)
1376
new_test.transport_server = self._transport_server
1377
new_test.transport_readonly_server = self._transport_readonly_server
1378
new_test.bzrdir_format = bzrdir_format
1379
new_test.branch_format = branch_format
1380
def make_new_test_id():
1381
new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1382
return lambda: new_id
1383
new_test.id = make_new_test_id()
1384
result.addTest(new_test)
1388
class BranchCheckResult(object):
1389
"""Results of checking branch consistency.
1394
def __init__(self, branch):
1395
self.branch = branch
1397
def report_results(self, verbose):
1398
"""Report the check results via trace.note.
1400
:param verbose: Requests more detailed display of what was checked,
1403
note('checked branch %s format %s',
1405
self.branch._format)
1408
######################################################################
1412
@deprecated_function(zero_eight)
1413
def is_control_file(*args, **kwargs):
1414
"""See bzrlib.workingtree.is_control_file."""
1415
return bzrlib.workingtree.is_control_file(*args, **kwargs)