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
from bzrlib.delta import compare_trees
31
from bzrlib.errors import (InvalidRevisionNumber,
36
from bzrlib.lockable_files import LockableFiles, TransportLock
37
from bzrlib.symbol_versioning import (deprecated_function,
43
from bzrlib.trace import mutter, note
46
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
47
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
48
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
51
# TODO: Maybe include checks for common corruption of newlines, etc?
53
# TODO: Some operations like log might retrieve the same revisions
54
# repeatedly to calculate deltas. We could perhaps have a weakref
55
# cache in memory to make this faster. In general anything can be
56
# cached in memory between lock and unlock operations. .. nb thats
57
# what the transaction identity map provides
60
######################################################################
64
"""Branch holding a history of revisions.
67
Base directory/url of the branch.
69
# this is really an instance variable - FIXME move it there
73
def __init__(self, *ignored, **ignored_too):
74
raise NotImplementedError('The Branch class is abstract')
77
"""Break a lock if one is present from another instance.
79
Uses the ui factory to ask for confirmation if the lock may be from
82
This will probe the repository for its lock as well.
84
self.control_files.break_lock()
85
self.repository.break_lock()
86
master = self.get_master_branch()
87
if master is not None:
91
@deprecated_method(zero_eight)
92
def open_downlevel(base):
93
"""Open a branch which may be of an old format."""
94
return Branch.open(base, _unsupported=True)
97
def open(base, _unsupported=False):
98
"""Open the repository rooted at base.
100
For instance, if the repository is at URL/.bzr/repository,
101
Repository.open(URL) -> a Repository instance.
103
control = bzrdir.BzrDir.open(base, _unsupported)
104
return control.open_branch(_unsupported)
107
def open_containing(url):
108
"""Open an existing branch which contains url.
110
This probes for a branch at url, and searches upwards from there.
112
Basically we keep looking up until we find the control directory or
113
run into the root. If there isn't one, raises NotBranchError.
114
If there is one and it is either an unrecognised format or an unsupported
115
format, UnknownFormatError or UnsupportedFormatError are raised.
116
If there is one, it is returned, along with the unused portion of url.
118
control, relpath = bzrdir.BzrDir.open_containing(url)
119
return control.open_branch(), relpath
122
@deprecated_function(zero_eight)
123
def initialize(base):
124
"""Create a new working tree and branch, rooted at 'base' (url)
126
NOTE: This will soon be deprecated in favour of creation
129
return bzrdir.BzrDir.create_standalone_workingtree(base).branch
131
def setup_caching(self, cache_root):
132
"""Subclasses that care about caching should override this, and set
133
up cached stores located under cache_root.
135
# seems to be unused, 2006-01-13 mbp
136
warn('%s is deprecated' % self.setup_caching)
137
self.cache_root = cache_root
140
cfg = self.tree_config()
141
return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
143
def _set_nick(self, nick):
144
cfg = self.tree_config()
145
cfg.set_option(nick, "nickname")
146
assert cfg.get_option("nickname") == nick
148
nick = property(_get_nick, _set_nick)
151
raise NotImplementedError('is_locked is abstract')
153
def lock_write(self):
154
raise NotImplementedError('lock_write is abstract')
157
raise NotImplementedError('lock_read is abstract')
160
raise NotImplementedError('unlock is abstract')
162
def peek_lock_mode(self):
163
"""Return lock mode for the Branch: 'r', 'w' or None"""
164
raise NotImplementedError(self.peek_lock_mode)
166
def get_physical_lock_status(self):
167
raise NotImplementedError('get_physical_lock_status is abstract')
169
def abspath(self, name):
170
"""Return absolute filename for something in the branch
172
XXX: Robert Collins 20051017 what is this used for? why is it a branch
173
method and not a tree method.
175
raise NotImplementedError('abspath is abstract')
177
def bind(self, other):
178
"""Bind the local branch the other branch.
180
:param other: The branch to bind to
183
raise errors.UpgradeRequired(self.base)
186
def fetch(self, from_branch, last_revision=None, pb=None):
187
"""Copy revisions from from_branch into this branch.
189
:param from_branch: Where to copy from.
190
:param last_revision: What revision to stop at (None for at the end
192
:param pb: An optional progress bar to use.
194
Returns the copied revision count and the failed revisions in a tuple:
197
if self.base == from_branch.base:
200
nested_pb = ui.ui_factory.nested_progress_bar()
205
from_branch.lock_read()
207
if last_revision is None:
208
pb.update('get source history')
209
from_history = from_branch.revision_history()
211
last_revision = from_history[-1]
213
# no history in the source branch
214
last_revision = revision.NULL_REVISION
215
return self.repository.fetch(from_branch.repository,
216
revision_id=last_revision,
219
if nested_pb is not None:
223
def get_bound_location(self):
224
"""Return the URL of the branch we are bound to.
226
Older format branches cannot bind, please be sure to use a metadir
231
def get_commit_builder(self, parents, config=None, timestamp=None,
232
timezone=None, committer=None, revprops=None,
234
"""Obtain a CommitBuilder for this branch.
236
:param parents: Revision ids of the parents of the new revision.
237
:param config: Optional configuration to use.
238
:param timestamp: Optional timestamp recorded for commit.
239
:param timezone: Optional timezone for timestamp.
240
:param committer: Optional committer to set for commit.
241
:param revprops: Optional dictionary of revision properties.
242
:param revision_id: Optional revision id.
246
config = bzrlib.config.BranchConfig(self)
248
return self.repository.get_commit_builder(self, parents, config,
249
timestamp, timezone, committer, revprops, revision_id)
251
def get_master_branch(self):
252
"""Return the branch we are bound to.
254
:return: Either a Branch, or None
258
def get_root_id(self):
259
"""Return the id of this branches root"""
260
raise NotImplementedError('get_root_id is abstract')
262
def print_file(self, file, revision_id):
263
"""Print `file` to stdout."""
264
raise NotImplementedError('print_file is abstract')
266
def append_revision(self, *revision_ids):
267
raise NotImplementedError('append_revision is abstract')
269
def set_revision_history(self, rev_history):
270
raise NotImplementedError('set_revision_history is abstract')
272
def revision_history(self):
273
"""Return sequence of revision hashes on to this branch."""
274
raise NotImplementedError('revision_history is abstract')
277
"""Return current revision number for this branch.
279
That is equivalent to the number of revisions committed to
282
return len(self.revision_history())
285
"""Older format branches cannot bind or unbind."""
286
raise errors.UpgradeRequired(self.base)
288
def last_revision(self):
289
"""Return last patch hash, or None if no history."""
290
ph = self.revision_history()
296
def missing_revisions(self, other, stop_revision=None):
297
"""Return a list of new revisions that would perfectly fit.
299
If self and other have not diverged, return a list of the revisions
300
present in other, but missing from self.
302
>>> from bzrlib.workingtree import WorkingTree
303
>>> bzrlib.trace.silent = True
304
>>> d1 = bzrdir.ScratchDir()
305
>>> br1 = d1.open_branch()
306
>>> wt1 = d1.open_workingtree()
307
>>> d2 = bzrdir.ScratchDir()
308
>>> br2 = d2.open_branch()
309
>>> wt2 = d2.open_workingtree()
310
>>> br1.missing_revisions(br2)
312
>>> wt2.commit("lala!", rev_id="REVISION-ID-1")
313
>>> br1.missing_revisions(br2)
315
>>> br2.missing_revisions(br1)
317
>>> wt1.commit("lala!", rev_id="REVISION-ID-1")
318
>>> br1.missing_revisions(br2)
320
>>> wt2.commit("lala!", rev_id="REVISION-ID-2A")
321
>>> br1.missing_revisions(br2)
323
>>> wt1.commit("lala!", rev_id="REVISION-ID-2B")
324
>>> br1.missing_revisions(br2)
325
Traceback (most recent call last):
326
DivergedBranches: These branches have diverged. Try merge.
328
self_history = self.revision_history()
329
self_len = len(self_history)
330
other_history = other.revision_history()
331
other_len = len(other_history)
332
common_index = min(self_len, other_len) -1
333
if common_index >= 0 and \
334
self_history[common_index] != other_history[common_index]:
335
raise DivergedBranches(self, other)
337
if stop_revision is None:
338
stop_revision = other_len
340
assert isinstance(stop_revision, int)
341
if stop_revision > other_len:
342
raise errors.NoSuchRevision(self, stop_revision)
343
return other_history[self_len:stop_revision]
345
def update_revisions(self, other, stop_revision=None):
346
"""Pull in new perfect-fit revisions.
348
:param other: Another Branch to pull from
349
:param stop_revision: Updated until the given revision
352
raise NotImplementedError('update_revisions is abstract')
354
def revision_id_to_revno(self, revision_id):
355
"""Given a revision id, return its revno"""
356
if revision_id is None:
358
history = self.revision_history()
360
return history.index(revision_id) + 1
362
raise bzrlib.errors.NoSuchRevision(self, revision_id)
364
def get_rev_id(self, revno, history=None):
365
"""Find the revision id of the specified revno."""
369
history = self.revision_history()
370
elif revno <= 0 or revno > len(history):
371
raise bzrlib.errors.NoSuchRevision(self, revno)
372
return history[revno - 1]
374
def pull(self, source, overwrite=False, stop_revision=None):
375
raise NotImplementedError('pull is abstract')
377
def basis_tree(self):
378
"""Return `Tree` object for last revision.
380
If there are no revisions yet, return an `EmptyTree`.
382
return self.repository.revision_tree(self.last_revision())
384
def rename_one(self, from_rel, to_rel):
387
This can change the directory or the filename or both.
389
raise NotImplementedError('rename_one is abstract')
391
def move(self, from_paths, to_name):
394
to_name must exist as a versioned directory.
396
If to_name exists and is a directory, the files are moved into
397
it, keeping their old names. If it is a directory,
399
Note that to_name is only the last component of the new name;
400
this doesn't change the directory.
402
This returns a list of (from_path, to_path) pairs for each
405
raise NotImplementedError('move is abstract')
407
def get_parent(self):
408
"""Return the parent location of the branch.
410
This is the default location for push/pull/missing. The usual
411
pattern is that the user can override it by specifying a
414
raise NotImplementedError('get_parent is abstract')
416
def get_push_location(self):
417
"""Return the None or the location to push this branch to."""
418
raise NotImplementedError('get_push_location is abstract')
420
def set_push_location(self, location):
421
"""Set a new push location for this branch."""
422
raise NotImplementedError('set_push_location is abstract')
424
def set_parent(self, url):
425
raise NotImplementedError('set_parent is abstract')
429
"""Synchronise this branch with the master branch if any.
431
:return: None or the last_revision pivoted out during the update.
435
def check_revno(self, revno):
437
Check whether a revno corresponds to any revision.
438
Zero (the NULL revision) is considered valid.
441
self.check_real_revno(revno)
443
def check_real_revno(self, revno):
445
Check whether a revno corresponds to a real revision.
446
Zero (the NULL revision) is considered invalid
448
if revno < 1 or revno > self.revno():
449
raise InvalidRevisionNumber(revno)
452
def clone(self, *args, **kwargs):
453
"""Clone this branch into to_bzrdir preserving all semantic values.
455
revision_id: if not None, the revision history in the new branch will
456
be truncated to end with revision_id.
458
# for API compatibility, until 0.8 releases we provide the old api:
459
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
460
# after 0.8 releases, the *args and **kwargs should be changed:
461
# def clone(self, to_bzrdir, revision_id=None):
462
if (kwargs.get('to_location', None) or
463
kwargs.get('revision', None) or
464
kwargs.get('basis_branch', None) or
465
(len(args) and isinstance(args[0], basestring))):
466
# backwards compatibility api:
467
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
468
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
471
basis_branch = args[2]
473
basis_branch = kwargs.get('basis_branch', None)
475
basis = basis_branch.bzrdir
480
revision_id = args[1]
482
revision_id = kwargs.get('revision', None)
487
# no default to raise if not provided.
488
url = kwargs.get('to_location')
489
return self.bzrdir.clone(url,
490
revision_id=revision_id,
491
basis=basis).open_branch()
493
# generate args by hand
495
revision_id = args[1]
497
revision_id = kwargs.get('revision_id', None)
501
# no default to raise if not provided.
502
to_bzrdir = kwargs.get('to_bzrdir')
503
result = self._format.initialize(to_bzrdir)
504
self.copy_content_into(result, revision_id=revision_id)
508
def sprout(self, to_bzrdir, revision_id=None):
509
"""Create a new line of development from the branch, into to_bzrdir.
511
revision_id: if not None, the revision history in the new branch will
512
be truncated to end with revision_id.
514
result = self._format.initialize(to_bzrdir)
515
self.copy_content_into(result, revision_id=revision_id)
516
result.set_parent(self.bzrdir.root_transport.base)
520
def copy_content_into(self, destination, revision_id=None):
521
"""Copy the content of self into destination.
523
revision_id: if not None, the revision history in the new branch will
524
be truncated to end with revision_id.
526
new_history = self.revision_history()
527
if revision_id is not None:
529
new_history = new_history[:new_history.index(revision_id) + 1]
531
rev = self.repository.get_revision(revision_id)
532
new_history = rev.get_history(self.repository)[1:]
533
destination.set_revision_history(new_history)
534
parent = self.get_parent()
536
destination.set_parent(parent)
540
"""Check consistency of the branch.
542
In particular this checks that revisions given in the revision-history
543
do actually match up in the revision graph, and that they're all
544
present in the repository.
546
Callers will typically also want to check the repository.
548
:return: A BranchCheckResult.
550
mainline_parent_id = None
551
for revision_id in self.revision_history():
553
revision = self.repository.get_revision(revision_id)
554
except errors.NoSuchRevision, e:
555
raise errors.BzrCheckError("mainline revision {%s} not in repository"
557
# In general the first entry on the revision history has no parents.
558
# But it's not illegal for it to have parents listed; this can happen
559
# in imports from Arch when the parents weren't reachable.
560
if mainline_parent_id is not None:
561
if mainline_parent_id not in revision.parent_ids:
562
raise errors.BzrCheckError("previous revision {%s} not listed among "
564
% (mainline_parent_id, revision_id))
565
mainline_parent_id = revision_id
566
return BranchCheckResult(self)
569
class BranchFormat(object):
570
"""An encapsulation of the initialization and open routines for a format.
572
Formats provide three things:
573
* An initialization routine,
577
Formats are placed in an dict by their format string for reference
578
during branch opening. Its not required that these be instances, they
579
can be classes themselves with class methods - it simply depends on
580
whether state is needed for a given format or not.
582
Once a format is deprecated, just deprecate the initialize and open
583
methods on the format class. Do not deprecate the object, as the
584
object will be created every time regardless.
587
_default_format = None
588
"""The default format used for new branches."""
591
"""The known formats."""
594
def find_format(klass, a_bzrdir):
595
"""Return the format for the branch object in a_bzrdir."""
597
transport = a_bzrdir.get_branch_transport(None)
598
format_string = transport.get("format").read()
599
return klass._formats[format_string]
601
raise NotBranchError(path=transport.base)
603
raise errors.UnknownFormatError(format_string)
606
def get_default_format(klass):
607
"""Return the current default format."""
608
return klass._default_format
610
def get_format_string(self):
611
"""Return the ASCII format string that identifies this format."""
612
raise NotImplementedError(self.get_format_string)
614
def get_format_description(self):
615
"""Return the short format description for this format."""
616
raise NotImplementedError(self.get_format_string)
618
def initialize(self, a_bzrdir):
619
"""Create a branch of this format in a_bzrdir."""
620
raise NotImplementedError(self.initialize)
622
def is_supported(self):
623
"""Is this format supported?
625
Supported formats can be initialized and opened.
626
Unsupported formats may not support initialization or committing or
627
some other features depending on the reason for not being supported.
631
def open(self, a_bzrdir, _found=False):
632
"""Return the branch object for a_bzrdir
634
_found is a private parameter, do not use it. It is used to indicate
635
if format probing has already be done.
637
raise NotImplementedError(self.open)
640
def register_format(klass, format):
641
klass._formats[format.get_format_string()] = format
644
def set_default_format(klass, format):
645
klass._default_format = format
648
def unregister_format(klass, format):
649
assert klass._formats[format.get_format_string()] is format
650
del klass._formats[format.get_format_string()]
653
return self.get_format_string().rstrip()
656
class BzrBranchFormat4(BranchFormat):
657
"""Bzr branch format 4.
660
- a revision-history file.
661
- a branch-lock lock file [ to be shared with the bzrdir ]
664
def get_format_description(self):
665
"""See BranchFormat.get_format_description()."""
666
return "Branch format 4"
668
def initialize(self, a_bzrdir):
669
"""Create a branch of this format in a_bzrdir."""
670
mutter('creating branch in %s', a_bzrdir.transport.base)
671
branch_transport = a_bzrdir.get_branch_transport(self)
672
utf8_files = [('revision-history', ''),
675
control_files = LockableFiles(branch_transport, 'branch-lock',
677
control_files.create_lock()
678
control_files.lock_write()
680
for file, content in utf8_files:
681
control_files.put_utf8(file, content)
683
control_files.unlock()
684
return self.open(a_bzrdir, _found=True)
687
super(BzrBranchFormat4, self).__init__()
688
self._matchingbzrdir = bzrdir.BzrDirFormat6()
690
def open(self, a_bzrdir, _found=False):
691
"""Return the branch object for a_bzrdir
693
_found is a private parameter, do not use it. It is used to indicate
694
if format probing has already be done.
697
# we are being called directly and must probe.
698
raise NotImplementedError
699
return BzrBranch(_format=self,
700
_control_files=a_bzrdir._control_files,
702
_repository=a_bzrdir.open_repository())
705
return "Bazaar-NG branch format 4"
708
class BzrBranchFormat5(BranchFormat):
709
"""Bzr branch format 5.
712
- a revision-history file.
714
- a lock dir guarding the branch itself
715
- all of this stored in a branch/ subdirectory
716
- works with shared repositories.
718
This format is new in bzr 0.8.
721
def get_format_string(self):
722
"""See BranchFormat.get_format_string()."""
723
return "Bazaar-NG branch format 5\n"
725
def get_format_description(self):
726
"""See BranchFormat.get_format_description()."""
727
return "Branch format 5"
729
def initialize(self, a_bzrdir):
730
"""Create a branch of this format in a_bzrdir."""
731
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
732
branch_transport = a_bzrdir.get_branch_transport(self)
733
utf8_files = [('revision-history', ''),
736
control_files = LockableFiles(branch_transport, 'lock', lockdir.LockDir)
737
control_files.create_lock()
738
control_files.lock_write()
739
control_files.put_utf8('format', self.get_format_string())
741
for file, content in utf8_files:
742
control_files.put_utf8(file, content)
744
control_files.unlock()
745
return self.open(a_bzrdir, _found=True, )
748
super(BzrBranchFormat5, self).__init__()
749
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
751
def open(self, a_bzrdir, _found=False):
752
"""Return the branch object for a_bzrdir
754
_found is a private parameter, do not use it. It is used to indicate
755
if format probing has already be done.
758
format = BranchFormat.find_format(a_bzrdir)
759
assert format.__class__ == self.__class__
760
transport = a_bzrdir.get_branch_transport(None)
761
control_files = LockableFiles(transport, 'lock', lockdir.LockDir)
762
return BzrBranch5(_format=self,
763
_control_files=control_files,
765
_repository=a_bzrdir.find_repository())
768
return "Bazaar-NG Metadir branch format 5"
771
class BranchReferenceFormat(BranchFormat):
772
"""Bzr branch reference format.
774
Branch references are used in implementing checkouts, they
775
act as an alias to the real branch which is at some other url.
782
def get_format_string(self):
783
"""See BranchFormat.get_format_string()."""
784
return "Bazaar-NG Branch Reference Format 1\n"
786
def get_format_description(self):
787
"""See BranchFormat.get_format_description()."""
788
return "Checkout reference format 1"
790
def initialize(self, a_bzrdir, target_branch=None):
791
"""Create a branch of this format in a_bzrdir."""
792
if target_branch is None:
793
# this format does not implement branch itself, thus the implicit
794
# creation contract must see it as uninitializable
795
raise errors.UninitializableFormat(self)
796
mutter('creating branch reference in %s', a_bzrdir.transport.base)
797
branch_transport = a_bzrdir.get_branch_transport(self)
798
# FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
799
branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
800
branch_transport.put('format', StringIO(self.get_format_string()))
801
return self.open(a_bzrdir, _found=True)
804
super(BranchReferenceFormat, self).__init__()
805
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
807
def _make_reference_clone_function(format, a_branch):
808
"""Create a clone() routine for a branch dynamically."""
809
def clone(to_bzrdir, revision_id=None):
810
"""See Branch.clone()."""
811
return format.initialize(to_bzrdir, a_branch)
812
# cannot obey revision_id limits when cloning a reference ...
813
# FIXME RBC 20060210 either nuke revision_id for clone, or
814
# emit some sort of warning/error to the caller ?!
817
def open(self, a_bzrdir, _found=False):
818
"""Return the branch that the branch reference in a_bzrdir points at.
820
_found is a private parameter, do not use it. It is used to indicate
821
if format probing has already be done.
824
format = BranchFormat.find_format(a_bzrdir)
825
assert format.__class__ == self.__class__
826
transport = a_bzrdir.get_branch_transport(None)
827
real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
828
result = real_bzrdir.open_branch()
829
# this changes the behaviour of result.clone to create a new reference
830
# rather than a copy of the content of the branch.
831
# I did not use a proxy object because that needs much more extensive
832
# testing, and we are only changing one behaviour at the moment.
833
# If we decide to alter more behaviours - i.e. the implicit nickname
834
# then this should be refactored to introduce a tested proxy branch
835
# and a subclass of that for use in overriding clone() and ....
837
result.clone = self._make_reference_clone_function(result)
841
# formats which have no format string are not discoverable
842
# and not independently creatable, so are not registered.
843
__default_format = BzrBranchFormat5()
844
BranchFormat.register_format(__default_format)
845
BranchFormat.register_format(BranchReferenceFormat())
846
BranchFormat.set_default_format(__default_format)
847
_legacy_formats = [BzrBranchFormat4(),
850
class BzrBranch(Branch):
851
"""A branch stored in the actual filesystem.
853
Note that it's "local" in the context of the filesystem; it doesn't
854
really matter if it's on an nfs/smb/afs/coda/... share, as long as
855
it's writable, and can be accessed via the normal filesystem API.
858
def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
859
relax_version_check=DEPRECATED_PARAMETER, _format=None,
860
_control_files=None, a_bzrdir=None, _repository=None):
861
"""Create new branch object at a particular location.
863
transport -- A Transport object, defining how to access files.
865
init -- If True, create new control files in a previously
866
unversioned directory. If False, the branch must already
869
relax_version_check -- If true, the usual check for the branch
870
version is not applied. This is intended only for
871
upgrade/recovery type use; it's not guaranteed that
872
all operations will work on old format branches.
875
self.bzrdir = bzrdir.BzrDir.open(transport.base)
877
self.bzrdir = a_bzrdir
878
self._transport = self.bzrdir.transport.clone('..')
879
self._base = self._transport.base
880
self._format = _format
881
if _control_files is None:
882
raise ValueError('BzrBranch _control_files is None')
883
self.control_files = _control_files
884
if deprecated_passed(init):
885
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
886
"deprecated as of bzr 0.8. Please use Branch.create().",
890
# this is slower than before deprecation, oh well never mind.
892
self._initialize(transport.base)
893
self._check_format(_format)
894
if deprecated_passed(relax_version_check):
895
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
896
"relax_version_check parameter is deprecated as of bzr 0.8. "
897
"Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
901
if (not relax_version_check
902
and not self._format.is_supported()):
903
raise errors.UnsupportedFormatError(
904
'sorry, branch format %r not supported' % self._format,
905
['use a different bzr version',
906
'or remove the .bzr directory'
907
' and "bzr init" again'])
908
if deprecated_passed(transport):
909
warn("BzrBranch.__init__(transport=XXX...): The transport "
910
"parameter is deprecated as of bzr 0.8. "
911
"Please use Branch.open, or bzrdir.open_branch().",
914
self.repository = _repository
917
return '%s(%r)' % (self.__class__.__name__, self.base)
922
# TODO: It might be best to do this somewhere else,
923
# but it is nice for a Branch object to automatically
924
# cache it's information.
925
# Alternatively, we could have the Transport objects cache requests
926
# See the earlier discussion about how major objects (like Branch)
927
# should never expect their __del__ function to run.
928
# XXX: cache_root seems to be unused, 2006-01-13 mbp
929
if hasattr(self, 'cache_root') and self.cache_root is not None:
931
osutils.rmtree(self.cache_root)
934
self.cache_root = None
939
base = property(_get_base, doc="The URL for the root of this branch.")
941
def _finish_transaction(self):
942
"""Exit the current transaction."""
943
return self.control_files._finish_transaction()
945
def get_transaction(self):
946
"""Return the current active transaction.
948
If no transaction is active, this returns a passthrough object
949
for which all data is immediately flushed and no caching happens.
951
# this is an explicit function so that we can do tricky stuff
952
# when the storage in rev_storage is elsewhere.
953
# we probably need to hook the two 'lock a location' and
954
# 'have a transaction' together more delicately, so that
955
# we can have two locks (branch and storage) and one transaction
956
# ... and finishing the transaction unlocks both, but unlocking
957
# does not. - RBC 20051121
958
return self.control_files.get_transaction()
960
def _set_transaction(self, transaction):
961
"""Set a new active transaction."""
962
return self.control_files._set_transaction(transaction)
964
def abspath(self, name):
965
"""See Branch.abspath."""
966
return self.control_files._transport.abspath(name)
968
def _check_format(self, format):
969
"""Identify the branch format if needed.
971
The format is stored as a reference to the format object in
972
self._format for code that needs to check it later.
974
The format parameter is either None or the branch format class
975
used to open this branch.
977
FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
980
format = BranchFormat.find_format(self.bzrdir)
981
self._format = format
982
mutter("got branch format %s", self._format)
985
def get_root_id(self):
986
"""See Branch.get_root_id."""
987
tree = self.repository.revision_tree(self.last_revision())
988
return tree.inventory.root.file_id
991
return self.control_files.is_locked()
993
def lock_write(self):
994
# TODO: test for failed two phase locks. This is known broken.
995
self.control_files.lock_write()
996
self.repository.lock_write()
999
# TODO: test for failed two phase locks. This is known broken.
1000
self.control_files.lock_read()
1001
self.repository.lock_read()
1004
# TODO: test for failed two phase locks. This is known broken.
1006
self.repository.unlock()
1008
self.control_files.unlock()
1010
def peek_lock_mode(self):
1011
if self.control_files._lock_count == 0:
1014
return self.control_files._lock_mode
1016
def get_physical_lock_status(self):
1017
return self.control_files.get_physical_lock_status()
1020
def print_file(self, file, revision_id):
1021
"""See Branch.print_file."""
1022
return self.repository.print_file(file, revision_id)
1025
def append_revision(self, *revision_ids):
1026
"""See Branch.append_revision."""
1027
for revision_id in revision_ids:
1028
mutter("add {%s} to revision-history" % revision_id)
1029
rev_history = self.revision_history()
1030
rev_history.extend(revision_ids)
1031
self.set_revision_history(rev_history)
1034
def set_revision_history(self, rev_history):
1035
"""See Branch.set_revision_history."""
1036
self.control_files.put_utf8(
1037
'revision-history', '\n'.join(rev_history))
1038
transaction = self.get_transaction()
1039
history = transaction.map.find_revision_history()
1040
if history is not None:
1041
# update the revision history in the identity map.
1042
history[:] = list(rev_history)
1043
# this call is disabled because revision_history is
1044
# not really an object yet, and the transaction is for objects.
1045
# transaction.register_dirty(history)
1047
transaction.map.add_revision_history(rev_history)
1048
# this call is disabled because revision_history is
1049
# not really an object yet, and the transaction is for objects.
1050
# transaction.register_clean(history)
1052
def get_revision_delta(self, revno):
1053
"""Return the delta for one revision.
1055
The delta is relative to its mainline predecessor, or the
1056
empty tree for revision 1.
1058
assert isinstance(revno, int)
1059
rh = self.revision_history()
1060
if not (1 <= revno <= len(rh)):
1061
raise InvalidRevisionNumber(revno)
1063
# revno is 1-based; list is 0-based
1065
new_tree = self.repository.revision_tree(rh[revno-1])
1067
old_tree = tree.EmptyTree()
1069
old_tree = self.repository.revision_tree(rh[revno-2])
1070
return compare_trees(old_tree, new_tree)
1073
def revision_history(self):
1074
"""See Branch.revision_history."""
1075
transaction = self.get_transaction()
1076
history = transaction.map.find_revision_history()
1077
if history is not None:
1078
mutter("cache hit for revision-history in %s", self)
1079
return list(history)
1080
history = [l.rstrip('\r\n') for l in
1081
self.control_files.get_utf8('revision-history').readlines()]
1082
transaction.map.add_revision_history(history)
1083
# this call is disabled because revision_history is
1084
# not really an object yet, and the transaction is for objects.
1085
# transaction.register_clean(history, precious=True)
1086
return list(history)
1089
def update_revisions(self, other, stop_revision=None):
1090
"""See Branch.update_revisions."""
1093
if stop_revision is None:
1094
stop_revision = other.last_revision()
1095
if stop_revision is None:
1096
# if there are no commits, we're done.
1098
# whats the current last revision, before we fetch [and change it
1100
last_rev = self.last_revision()
1101
# we fetch here regardless of whether we need to so that we pickup
1103
self.fetch(other, stop_revision)
1104
my_ancestry = self.repository.get_ancestry(last_rev)
1105
if stop_revision in my_ancestry:
1106
# last_revision is a descendant of stop_revision
1108
# stop_revision must be a descendant of last_revision
1109
stop_graph = self.repository.get_revision_graph(stop_revision)
1110
if last_rev is not None and last_rev not in stop_graph:
1111
# our previous tip is not merged into stop_revision
1112
raise errors.DivergedBranches(self, other)
1113
# make a new revision history from the graph
1114
current_rev_id = stop_revision
1116
while current_rev_id not in (None, revision.NULL_REVISION):
1117
new_history.append(current_rev_id)
1118
current_rev_id_parents = stop_graph[current_rev_id]
1120
current_rev_id = current_rev_id_parents[0]
1122
current_rev_id = None
1123
new_history.reverse()
1124
self.set_revision_history(new_history)
1128
def basis_tree(self):
1129
"""See Branch.basis_tree."""
1130
return self.repository.revision_tree(self.last_revision())
1132
@deprecated_method(zero_eight)
1133
def working_tree(self):
1134
"""Create a Working tree object for this branch."""
1135
from bzrlib.transport.local import LocalTransport
1136
if (self.base.find('://') != -1 or
1137
not isinstance(self._transport, LocalTransport)):
1138
raise NoWorkingTree(self.base)
1139
return self.bzrdir.open_workingtree()
1142
def pull(self, source, overwrite=False, stop_revision=None):
1143
"""See Branch.pull."""
1146
old_count = len(self.revision_history())
1148
self.update_revisions(source,stop_revision)
1149
except DivergedBranches:
1153
self.set_revision_history(source.revision_history())
1154
new_count = len(self.revision_history())
1155
return new_count - old_count
1159
def get_parent(self):
1160
"""See Branch.get_parent."""
1161
_locs = ['parent', 'pull', 'x-pull']
1162
assert self.base[-1] == '/'
1165
return urlutils.join(self.base[:-1],
1166
self.control_files.get(l).read().strip('\n'))
1171
def get_push_location(self):
1172
"""See Branch.get_push_location."""
1173
config = bzrlib.config.BranchConfig(self)
1174
push_loc = config.get_user_option('push_location')
1177
def set_push_location(self, location):
1178
"""See Branch.set_push_location."""
1179
config = bzrlib.config.LocationConfig(self.base)
1180
config.set_user_option('push_location', location)
1183
def set_parent(self, url):
1184
"""See Branch.set_parent."""
1185
# TODO: Maybe delete old location files?
1186
# URLs should never be unicode, even on the local fs,
1187
# FIXUP this and get_parent in a future branch format bump:
1188
# read and rewrite the file, and have the new format code read
1189
# using .get not .get_utf8. RBC 20060125
1191
self.control_files._transport.delete('parent')
1193
if isinstance(url, unicode):
1195
url = url.encode('ascii')
1196
except UnicodeEncodeError:
1197
raise bzrlib.errors.InvalidURL(url,
1198
"Urls must be 7-bit ascii, "
1199
"use bzrlib.urlutils.escape")
1201
url = urlutils.relative_url(self.base, url)
1202
self.control_files.put('parent', url + '\n')
1204
def tree_config(self):
1205
return TreeConfig(self)
1208
class BzrBranch5(BzrBranch):
1209
"""A format 5 branch. This supports new features over plan branches.
1211
It has support for a master_branch which is the data for bound branches.
1219
super(BzrBranch5, self).__init__(_format=_format,
1220
_control_files=_control_files,
1222
_repository=_repository)
1225
def pull(self, source, overwrite=False, stop_revision=None):
1226
"""Updates branch.pull to be bound branch aware."""
1227
bound_location = self.get_bound_location()
1228
if source.base != bound_location:
1229
# not pulling from master, so we need to update master.
1230
master_branch = self.get_master_branch()
1232
master_branch.pull(source)
1233
source = master_branch
1234
return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
1236
def get_bound_location(self):
1238
return self.control_files.get_utf8('bound').read()[:-1]
1239
except errors.NoSuchFile:
1243
def get_master_branch(self):
1244
"""Return the branch we are bound to.
1246
:return: Either a Branch, or None
1248
This could memoise the branch, but if thats done
1249
it must be revalidated on each new lock.
1250
So for now we just don't memoise it.
1251
# RBC 20060304 review this decision.
1253
bound_loc = self.get_bound_location()
1257
return Branch.open(bound_loc)
1258
except (errors.NotBranchError, errors.ConnectionError), e:
1259
raise errors.BoundBranchConnectionFailure(
1263
def set_bound_location(self, location):
1264
"""Set the target where this branch is bound to.
1266
:param location: URL to the target branch
1269
self.control_files.put_utf8('bound', location+'\n')
1272
self.control_files._transport.delete('bound')
1278
def bind(self, other):
1279
"""Bind the local branch the other branch.
1281
:param other: The branch to bind to
1284
# TODO: jam 20051230 Consider checking if the target is bound
1285
# It is debatable whether you should be able to bind to
1286
# a branch which is itself bound.
1287
# Committing is obviously forbidden,
1288
# but binding itself may not be.
1289
# Since we *have* to check at commit time, we don't
1290
# *need* to check here
1293
# we are now equal to or a suffix of other.
1295
# Since we have 'pulled' from the remote location,
1296
# now we should try to pull in the opposite direction
1297
# in case the local tree has more revisions than the
1299
# There may be a different check you could do here
1300
# rather than actually trying to install revisions remotely.
1301
# TODO: capture an exception which indicates the remote branch
1303
# If it is up-to-date, this probably should not be a failure
1305
# lock other for write so the revision-history syncing cannot race
1309
# if this does not error, other now has the same last rev we do
1310
# it can only error if the pull from other was concurrent with
1311
# a commit to other from someone else.
1313
# until we ditch revision-history, we need to sync them up:
1314
self.set_revision_history(other.revision_history())
1315
# now other and self are up to date with each other and have the
1316
# same revision-history.
1320
self.set_bound_location(other.base)
1324
"""If bound, unbind"""
1325
return self.set_bound_location(None)
1329
"""Synchronise this branch with the master branch if any.
1331
:return: None or the last_revision that was pivoted out during the
1334
master = self.get_master_branch()
1335
if master is not None:
1336
old_tip = self.last_revision()
1337
self.pull(master, overwrite=True)
1338
if old_tip in self.repository.get_ancestry(self.last_revision()):
1344
class BranchTestProviderAdapter(object):
1345
"""A tool to generate a suite testing multiple branch formats at once.
1347
This is done by copying the test once for each transport and injecting
1348
the transport_server, transport_readonly_server, and branch_format
1349
classes into each copy. Each copy is also given a new id() to make it
1353
def __init__(self, transport_server, transport_readonly_server, formats):
1354
self._transport_server = transport_server
1355
self._transport_readonly_server = transport_readonly_server
1356
self._formats = formats
1358
def adapt(self, test):
1359
result = TestSuite()
1360
for branch_format, bzrdir_format in self._formats:
1361
new_test = deepcopy(test)
1362
new_test.transport_server = self._transport_server
1363
new_test.transport_readonly_server = self._transport_readonly_server
1364
new_test.bzrdir_format = bzrdir_format
1365
new_test.branch_format = branch_format
1366
def make_new_test_id():
1367
new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1368
return lambda: new_id
1369
new_test.id = make_new_test_id()
1370
result.addTest(new_test)
1374
class BranchCheckResult(object):
1375
"""Results of checking branch consistency.
1380
def __init__(self, branch):
1381
self.branch = branch
1383
def report_results(self, verbose):
1384
"""Report the check results via trace.note.
1386
:param verbose: Requests more detailed display of what was checked,
1389
note('checked branch %s format %s',
1391
self.branch._format)
1394
######################################################################
1398
@deprecated_function(zero_eight)
1399
def is_control_file(*args, **kwargs):
1400
"""See bzrlib.workingtree.is_control_file."""
1401
return bzrlib.workingtree.is_control_file(*args, **kwargs)