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
24
from unittest import TestSuite
25
from warnings import warn
27
import xml.sax.saxutils
29
raise ImportError("We were unable to import 'xml.sax.saxutils',"
30
" most likely you have an xml.pyc or xml.pyo file"
31
" lying around in your bzrlib directory."
36
import bzrlib.bzrdir as bzrdir
37
from bzrlib.config import TreeConfig
38
from bzrlib.decorators import needs_read_lock, needs_write_lock
39
from bzrlib.delta import compare_trees
40
import bzrlib.errors as errors
41
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
42
NoSuchRevision, HistoryMissing, NotBranchError,
43
DivergedBranches, LockError,
44
UninitializableFormat,
46
UnlistableBranch, NoSuchFile, NotVersionedError,
48
import bzrlib.inventory as inventory
49
from bzrlib.inventory import Inventory
50
from bzrlib.lockable_files import LockableFiles
51
from bzrlib.osutils import (isdir, quotefn,
52
rename, splitpath, sha_file,
53
file_kind, abspath, normpath, pathjoin,
56
from bzrlib.textui import show_status
57
from bzrlib.trace import mutter, note
58
from bzrlib.tree import EmptyTree, RevisionTree
59
from bzrlib.repository import Repository
60
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions)
61
from bzrlib.store import copy_all
62
from bzrlib.symbol_versioning import *
63
import bzrlib.transactions as transactions
64
from bzrlib.transport import Transport, get_transport
65
from bzrlib.tree import EmptyTree, RevisionTree
70
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
71
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
72
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
75
# TODO: Maybe include checks for common corruption of newlines, etc?
77
# TODO: Some operations like log might retrieve the same revisions
78
# repeatedly to calculate deltas. We could perhaps have a weakref
79
# cache in memory to make this faster. In general anything can be
80
# cached in memory between lock and unlock operations. .. nb thats
81
# what the transaction identity map provides
84
######################################################################
88
"""Branch holding a history of revisions.
91
Base directory/url of the branch.
93
# this is really an instance variable - FIXME move it there
99
"""Construct the current default format branch in a_bzrdir.
101
This creates the current default BzrDir format, and if that
102
supports multiple Branch formats, then the default Branch format
105
print "not usable until we have repositories"
106
raise NotImplementedError("not usable right now")
107
return bzrdir.BzrDir.create(base)
109
def __init__(self, *ignored, **ignored_too):
110
raise NotImplementedError('The Branch class is abstract')
113
@deprecated_method(zero_eight)
114
def open_downlevel(base):
115
"""Open a branch which may be of an old format."""
116
return Branch.open(base, _unsupported=True)
119
def open(base, _unsupported=False):
120
"""Open the repository rooted at base.
122
For instance, if the repository is at URL/.bzr/repository,
123
Repository.open(URL) -> a Repository instance.
125
control = bzrdir.BzrDir.open(base, _unsupported)
126
return control.open_branch(_unsupported)
129
def open_containing(url):
130
"""Open an existing branch which contains url.
132
This probes for a branch at url, and searches upwards from there.
134
Basically we keep looking up until we find the control directory or
135
run into the root. If there isn't one, raises NotBranchError.
136
If there is one and it is either an unrecognised format or an unsupported
137
format, UnknownFormatError or UnsupportedFormatError are raised.
138
If there is one, it is returned, along with the unused portion of url.
140
control, relpath = bzrdir.BzrDir.open_containing(url)
141
return control.open_branch(), relpath
144
@deprecated_function(zero_eight)
145
def initialize(base):
146
"""Create a new working tree and branch, rooted at 'base' (url)
148
NOTE: This will soon be deprecated in favour of creation
151
return bzrdir.BzrDir.create_standalone_workingtree(base).branch
153
def setup_caching(self, cache_root):
154
"""Subclasses that care about caching should override this, and set
155
up cached stores located under cache_root.
157
# seems to be unused, 2006-01-13 mbp
158
warn('%s is deprecated' % self.setup_caching)
159
self.cache_root = cache_root
162
cfg = self.tree_config()
163
return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
165
def _set_nick(self, nick):
166
cfg = self.tree_config()
167
cfg.set_option(nick, "nickname")
168
assert cfg.get_option("nickname") == nick
170
nick = property(_get_nick, _set_nick)
172
def lock_write(self):
173
raise NotImplementedError('lock_write is abstract')
176
raise NotImplementedError('lock_read is abstract')
179
raise NotImplementedError('unlock is abstract')
181
def peek_lock_mode(self):
182
"""Return lock mode for the Branch: 'r', 'w' or None"""
183
raise NotImplementedError(self.peek_lock_mode)
185
def abspath(self, name):
186
"""Return absolute filename for something in the branch
188
XXX: Robert Collins 20051017 what is this used for? why is it a branch
189
method and not a tree method.
191
raise NotImplementedError('abspath is abstract')
193
def get_root_id(self):
194
"""Return the id of this branches root"""
195
raise NotImplementedError('get_root_id is abstract')
197
def print_file(self, file, revision_id):
198
"""Print `file` to stdout."""
199
raise NotImplementedError('print_file is abstract')
201
def append_revision(self, *revision_ids):
202
raise NotImplementedError('append_revision is abstract')
204
def set_revision_history(self, rev_history):
205
raise NotImplementedError('set_revision_history is abstract')
207
def revision_history(self):
208
"""Return sequence of revision hashes on to this branch."""
209
raise NotImplementedError('revision_history is abstract')
212
"""Return current revision number for this branch.
214
That is equivalent to the number of revisions committed to
217
return len(self.revision_history())
219
def last_revision(self):
220
"""Return last patch hash, or None if no history."""
221
ph = self.revision_history()
227
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
228
"""Return a list of new revisions that would perfectly fit.
230
If self and other have not diverged, return a list of the revisions
231
present in other, but missing from self.
233
>>> from bzrlib.workingtree import WorkingTree
234
>>> bzrlib.trace.silent = True
235
>>> d1 = bzrdir.ScratchDir()
236
>>> br1 = d1.open_branch()
237
>>> wt1 = WorkingTree(br1.base, br1)
238
>>> d2 = bzrdir.ScratchDir()
239
>>> br2 = d2.open_branch()
240
>>> wt2 = WorkingTree(br2.base, br2)
241
>>> br1.missing_revisions(br2)
243
>>> wt2.commit("lala!", rev_id="REVISION-ID-1")
244
>>> br1.missing_revisions(br2)
246
>>> br2.missing_revisions(br1)
248
>>> wt1.commit("lala!", rev_id="REVISION-ID-1")
249
>>> br1.missing_revisions(br2)
251
>>> wt2.commit("lala!", rev_id="REVISION-ID-2A")
252
>>> br1.missing_revisions(br2)
254
>>> wt1.commit("lala!", rev_id="REVISION-ID-2B")
255
>>> br1.missing_revisions(br2)
256
Traceback (most recent call last):
257
DivergedBranches: These branches have diverged. Try merge.
259
self_history = self.revision_history()
260
self_len = len(self_history)
261
other_history = other.revision_history()
262
other_len = len(other_history)
263
common_index = min(self_len, other_len) -1
264
if common_index >= 0 and \
265
self_history[common_index] != other_history[common_index]:
266
raise DivergedBranches(self, other)
268
if stop_revision is None:
269
stop_revision = other_len
271
assert isinstance(stop_revision, int)
272
if stop_revision > other_len:
273
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
274
return other_history[self_len:stop_revision]
276
def update_revisions(self, other, stop_revision=None):
277
"""Pull in new perfect-fit revisions."""
278
raise NotImplementedError('update_revisions is abstract')
280
def pullable_revisions(self, other, stop_revision):
281
raise NotImplementedError('pullable_revisions is abstract')
283
def revision_id_to_revno(self, revision_id):
284
"""Given a revision id, return its revno"""
285
if revision_id is None:
287
history = self.revision_history()
289
return history.index(revision_id) + 1
291
raise bzrlib.errors.NoSuchRevision(self, revision_id)
293
def get_rev_id(self, revno, history=None):
294
"""Find the revision id of the specified revno."""
298
history = self.revision_history()
299
elif revno <= 0 or revno > len(history):
300
raise bzrlib.errors.NoSuchRevision(self, revno)
301
return history[revno - 1]
303
def pull(self, source, overwrite=False):
304
raise NotImplementedError('pull is abstract')
306
def basis_tree(self):
307
"""Return `Tree` object for last revision.
309
If there are no revisions yet, return an `EmptyTree`.
311
return self.repository.revision_tree(self.last_revision())
313
def rename_one(self, from_rel, to_rel):
316
This can change the directory or the filename or both.
318
raise NotImplementedError('rename_one is abstract')
320
def move(self, from_paths, to_name):
323
to_name must exist as a versioned directory.
325
If to_name exists and is a directory, the files are moved into
326
it, keeping their old names. If it is a directory,
328
Note that to_name is only the last component of the new name;
329
this doesn't change the directory.
331
This returns a list of (from_path, to_path) pairs for each
334
raise NotImplementedError('move is abstract')
336
def get_parent(self):
337
"""Return the parent location of the branch.
339
This is the default location for push/pull/missing. The usual
340
pattern is that the user can override it by specifying a
343
raise NotImplementedError('get_parent is abstract')
345
def get_push_location(self):
346
"""Return the None or the location to push this branch to."""
347
raise NotImplementedError('get_push_location is abstract')
349
def set_push_location(self, location):
350
"""Set a new push location for this branch."""
351
raise NotImplementedError('set_push_location is abstract')
353
def set_parent(self, url):
354
raise NotImplementedError('set_parent is abstract')
356
def check_revno(self, revno):
358
Check whether a revno corresponds to any revision.
359
Zero (the NULL revision) is considered valid.
362
self.check_real_revno(revno)
364
def check_real_revno(self, revno):
366
Check whether a revno corresponds to a real revision.
367
Zero (the NULL revision) is considered invalid
369
if revno < 1 or revno > self.revno():
370
raise InvalidRevisionNumber(revno)
373
def clone(self, *args, **kwargs):
374
"""Clone this branch into to_bzrdir preserving all semantic values.
376
revision_id: if not None, the revision history in the new branch will
377
be truncated to end with revision_id.
379
# for API compatability, until 0.8 releases we provide the old api:
380
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
381
# after 0.8 releases, the *args and **kwargs should be changed:
382
# def clone(self, to_bzrdir, revision_id=None):
383
if (kwargs.get('to_location', None) or
384
kwargs.get('revision', None) or
385
kwargs.get('basis_branch', None) or
386
(len(args) and isinstance(args[0], basestring))):
387
# backwards compatability api:
388
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
389
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
392
basis_branch = args[2]
394
basis_branch = kwargs.get('basis_branch', None)
396
basis = basis_branch.bzrdir
401
revision_id = args[1]
403
revision_id = kwargs.get('revision', None)
408
# no default to raise if not provided.
409
url = kwargs.get('to_location')
410
return self.bzrdir.clone(url,
411
revision_id=revision_id,
412
basis=basis).open_branch()
414
# generate args by hand
416
revision_id = args[1]
418
revision_id = kwargs.get('revision_id', None)
422
# no default to raise if not provided.
423
to_bzrdir = kwargs.get('to_bzrdir')
424
result = self._format.initialize(to_bzrdir)
425
self.copy_content_into(result, revision_id=revision_id)
429
def sprout(self, to_bzrdir, revision_id=None):
430
"""Create a new line of development from the branch, into to_bzrdir.
432
revision_id: if not None, the revision history in the new branch will
433
be truncated to end with revision_id.
435
result = self._format.initialize(to_bzrdir)
436
self.copy_content_into(result, revision_id=revision_id)
437
result.set_parent(self.bzrdir.root_transport.base)
441
def copy_content_into(self, destination, revision_id=None):
442
"""Copy the content of self into destination.
444
revision_id: if not None, the revision history in the new branch will
445
be truncated to end with revision_id.
447
new_history = self.revision_history()
448
if revision_id is not None:
450
new_history = new_history[:new_history.index(revision_id) + 1]
452
rev = self.repository.get_revision(revision_id)
453
new_history = rev.get_history(self.repository)[1:]
454
destination.set_revision_history(new_history)
455
parent = self.get_parent()
457
destination.set_parent(parent)
460
class BranchFormat(object):
461
"""An encapsulation of the initialization and open routines for a format.
463
Formats provide three things:
464
* An initialization routine,
468
Formats are placed in an dict by their format string for reference
469
during branch opening. Its not required that these be instances, they
470
can be classes themselves with class methods - it simply depends on
471
whether state is needed for a given format or not.
473
Once a format is deprecated, just deprecate the initialize and open
474
methods on the format class. Do not deprecate the object, as the
475
object will be created every time regardless.
478
_default_format = None
479
"""The default format used for new branches."""
482
"""The known formats."""
485
def find_format(klass, a_bzrdir):
486
"""Return the format for the branch object in a_bzrdir."""
488
transport = a_bzrdir.get_branch_transport(None)
489
format_string = transport.get("format").read()
490
return klass._formats[format_string]
492
raise NotBranchError(path=transport.base)
494
raise errors.UnknownFormatError(format_string)
496
def find_repository(self, a_bzrdir):
497
"""Find the repository that should be used for a_bzrdir.
499
This does not require a branch as we use it to find the repo for
500
new branches as well as to hook existing branches up to their
503
next_transport = a_bzrdir.root_transport
506
found_bzrdir = bzrdir.BzrDir.open_containing_transport(
508
except errors.NotBranchError:
509
raise errors.NoRepositoryPresent(a_bzrdir)
511
repository = found_bzrdir.open_repository()
512
except errors.NoRepositoryPresent:
513
next_transport = found_bzrdir.root_transport.clone('..')
515
if ((found_bzrdir.root_transport.base ==
516
a_bzrdir.root_transport.base) or repository.is_shared()):
519
raise errors.NoRepositoryPresent(a_bzrdir)
520
raise errors.NoRepositoryPresent(a_bzrdir)
523
def get_default_format(klass):
524
"""Return the current default format."""
525
return klass._default_format
527
def get_format_string(self):
528
"""Return the ASCII format string that identifies this format."""
529
raise NotImplementedError(self.get_format_string)
531
def _find_modes(self, t):
532
"""Determine the appropriate modes for files and directories.
534
FIXME: When this merges into, or from storage,
535
this code becomes delgatable to a LockableFiles instance.
537
For now its cribbed and returns (dir_mode, file_mode)
541
except errors.TransportNotPossible:
545
dir_mode = st.st_mode & 07777
546
# Remove the sticky and execute bits for files
547
file_mode = dir_mode & ~07111
548
if not BzrBranch._set_dir_mode:
550
if not BzrBranch._set_file_mode:
552
return dir_mode, file_mode
554
def initialize(self, a_bzrdir):
555
"""Create a branch of this format in a_bzrdir."""
556
raise NotImplementedError(self.initialized)
558
def is_supported(self):
559
"""Is this format supported?
561
Supported formats can be initialized and opened.
562
Unsupported formats may not support initialization or committing or
563
some other features depending on the reason for not being supported.
567
def open(self, a_bzrdir, _found=False):
568
"""Return the branch object for a_bzrdir
570
_found is a private parameter, do not use it. It is used to indicate
571
if format probing has already be done.
573
raise NotImplementedError(self.open)
576
def register_format(klass, format):
577
klass._formats[format.get_format_string()] = format
580
def set_default_format(klass, format):
581
klass._default_format = format
584
def unregister_format(klass, format):
585
assert klass._formats[format.get_format_string()] is format
586
del klass._formats[format.get_format_string()]
589
class BzrBranchFormat4(BranchFormat):
590
"""Bzr branch format 4.
593
- a revision-history file.
594
- a branch-lock lock file [ to be shared with the bzrdir ]
597
def initialize(self, a_bzrdir):
598
"""Create a branch of this format in a_bzrdir."""
599
mutter('creating branch in %s', a_bzrdir.transport.base)
600
branch_transport = a_bzrdir.get_branch_transport(self)
601
utf8_files = [('revision-history', ''),
604
control_files = LockableFiles(branch_transport, 'branch-lock')
605
control_files.lock_write()
607
for file, content in utf8_files:
608
control_files.put_utf8(file, content)
610
control_files.unlock()
611
return self.open(a_bzrdir, _found=True)
614
super(BzrBranchFormat4, self).__init__()
615
self._matchingbzrdir = bzrdir.BzrDirFormat6()
617
def open(self, a_bzrdir, _found=False):
618
"""Return the branch object for a_bzrdir
620
_found is a private parameter, do not use it. It is used to indicate
621
if format probing has already be done.
624
# we are being called directly and must probe.
625
raise NotImplementedError
626
transport = a_bzrdir.get_branch_transport(self)
627
control_files = LockableFiles(transport, 'branch-lock')
628
return BzrBranch(_format=self,
629
_control_files=control_files,
631
_repository=a_bzrdir.open_repository())
634
class BzrBranchFormat5(BranchFormat):
635
"""Bzr branch format 5.
638
- a revision-history file.
641
- works with shared repositories.
644
def get_format_string(self):
645
"""See BranchFormat.get_format_string()."""
646
return "Bazaar-NG branch format 5\n"
648
def initialize(self, a_bzrdir):
649
"""Create a branch of this format in a_bzrdir."""
650
mutter('creating branch in %s', a_bzrdir.transport.base)
651
branch_transport = a_bzrdir.get_branch_transport(self)
653
utf8_files = [('revision-history', ''),
657
branch_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
658
control_files = LockableFiles(branch_transport, 'lock')
659
control_files.lock_write()
660
control_files.put_utf8('format', self.get_format_string())
662
for file, content in utf8_files:
663
control_files.put_utf8(file, content)
665
control_files.unlock()
666
return self.open(a_bzrdir, _found=True, )
669
super(BzrBranchFormat5, self).__init__()
670
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
672
def open(self, a_bzrdir, _found=False):
673
"""Return the branch object for a_bzrdir
675
_found is a private parameter, do not use it. It is used to indicate
676
if format probing has already be done.
679
format = BranchFormat.find_format(a_bzrdir)
680
assert format.__class__ == self.__class__
681
transport = a_bzrdir.get_branch_transport(None)
682
control_files = LockableFiles(transport, 'lock')
683
return BzrBranch(_format=self,
684
_control_files=control_files,
686
_repository=self.find_repository(a_bzrdir))
689
class BranchReferenceFormat(BranchFormat):
690
"""Bzr branch reference format.
692
Branch references are used in implementing checkouts, they
693
act as an alias to the real branch which is at some other url.
700
def get_format_string(self):
701
"""See BranchFormat.get_format_string()."""
702
return "Bazaar-NG Branch Reference Format 1\n"
704
def initialize(self, a_bzrdir, target_branch=None):
705
"""Create a branch of this format in a_bzrdir."""
706
if target_branch is None:
707
# this format does not implement branch itself, thus the implicit
708
# creation contract must see it as uninitializable
709
raise errors.UninitializableFormat(self)
710
mutter('creating branch reference in %s', a_bzrdir.transport.base)
711
branch_transport = a_bzrdir.get_branch_transport(self)
712
# FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
713
branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
714
branch_transport.put('format', StringIO(self.get_format_string()))
715
return self.open(a_bzrdir, _found=True)
718
super(BranchReferenceFormat, self).__init__()
719
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
721
def _make_reference_clone_function(format, a_branch):
722
"""Create a clone() routine for a branch dynamically."""
723
def clone(to_bzrdir, revision_id=None):
724
"""See Branch.clone()."""
725
return format.initialize(to_bzrdir, a_branch)
726
# cannot obey revision_id limits when cloning a reference ...
727
# FIXME RBC 20060210 either nuke revision_id for clone, or
728
# emit some sort of warning/error to the caller ?!
731
def open(self, a_bzrdir, _found=False):
732
"""Return the branch that the branch reference in a_bzrdir points at.
734
_found is a private parameter, do not use it. It is used to indicate
735
if format probing has already be done.
738
format = BranchFormat.find_format(a_bzrdir)
739
assert format.__class__ == self.__class__
740
transport = a_bzrdir.get_branch_transport(None)
741
real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
742
result = real_bzrdir.open_branch()
743
# this changes the behaviour of result.clone to create a new reference
744
# rather than a copy of the content of the branch.
745
# I did not use a proxy object because that needs much more extensive
746
# testing, and we are only changing one behaviour at the moment.
747
# If we decide to alter more behaviours - i.e. the implicit nickname
748
# then this should be refactored to introduce a tested proxy branch
749
# and a subclass of that for use in overriding clone() and ....
751
result.clone = self._make_reference_clone_function(result)
755
# formats which have no format string are not discoverable
756
# and not independently creatable, so are not registered.
757
__default_format = BzrBranchFormat5()
758
BranchFormat.register_format(__default_format)
759
BranchFormat.register_format(BranchReferenceFormat())
760
BranchFormat.set_default_format(__default_format)
761
_legacy_formats = [BzrBranchFormat4(),
764
class BzrBranch(Branch):
765
"""A branch stored in the actual filesystem.
767
Note that it's "local" in the context of the filesystem; it doesn't
768
really matter if it's on an nfs/smb/afs/coda/... share, as long as
769
it's writable, and can be accessed via the normal filesystem API.
771
# We actually expect this class to be somewhat short-lived; part of its
772
# purpose is to try to isolate what bits of the branch logic are tied to
773
# filesystem access, so that in a later step, we can extricate them to
774
# a separarte ("storage") class.
775
_inventory_weave = None
777
# Map some sort of prefix into a namespace
778
# stuff like "revno:10", "revid:", etc.
779
# This should match a prefix with a function which accepts
780
REVISION_NAMESPACES = {}
782
def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
783
relax_version_check=DEPRECATED_PARAMETER, _format=None,
784
_control_files=None, a_bzrdir=None, _repository=None):
785
"""Create new branch object at a particular location.
787
transport -- A Transport object, defining how to access files.
789
init -- If True, create new control files in a previously
790
unversioned directory. If False, the branch must already
793
relax_version_check -- If true, the usual check for the branch
794
version is not applied. This is intended only for
795
upgrade/recovery type use; it's not guaranteed that
796
all operations will work on old format branches.
799
self.bzrdir = bzrdir.BzrDir.open(transport.base)
801
self.bzrdir = a_bzrdir
802
self._transport = self.bzrdir.transport.clone('..')
803
self._base = self._transport.base
804
self._format = _format
805
if _control_files is None:
806
raise BzrBadParameterMissing('_control_files')
807
self.control_files = _control_files
808
if deprecated_passed(init):
809
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
810
"deprecated as of bzr 0.8. Please use Branch.create().",
814
# this is slower than before deprecation, oh well never mind.
816
self._initialize(transport.base)
817
self._check_format(_format)
818
if deprecated_passed(relax_version_check):
819
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
820
"relax_version_check parameter is deprecated as of bzr 0.8. "
821
"Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
825
if (not relax_version_check
826
and not self._format.is_supported()):
827
raise errors.UnsupportedFormatError(
828
'sorry, branch format %r not supported' % fmt,
829
['use a different bzr version',
830
'or remove the .bzr directory'
831
' and "bzr init" again'])
832
if deprecated_passed(transport):
833
warn("BzrBranch.__init__(transport=XXX...): The transport "
834
"parameter is deprecated as of bzr 0.8. "
835
"Please use Branch.open, or bzrdir.open_branch().",
838
self.repository = _repository
841
return '%s(%r)' % (self.__class__.__name__, self.base)
846
# TODO: It might be best to do this somewhere else,
847
# but it is nice for a Branch object to automatically
848
# cache it's information.
849
# Alternatively, we could have the Transport objects cache requests
850
# See the earlier discussion about how major objects (like Branch)
851
# should never expect their __del__ function to run.
852
# XXX: cache_root seems to be unused, 2006-01-13 mbp
853
if hasattr(self, 'cache_root') and self.cache_root is not None:
855
shutil.rmtree(self.cache_root)
858
self.cache_root = None
863
base = property(_get_base, doc="The URL for the root of this branch.")
865
def _finish_transaction(self):
866
"""Exit the current transaction."""
867
return self.control_files._finish_transaction()
869
def get_transaction(self):
870
"""Return the current active transaction.
872
If no transaction is active, this returns a passthrough object
873
for which all data is immediately flushed and no caching happens.
875
# this is an explicit function so that we can do tricky stuff
876
# when the storage in rev_storage is elsewhere.
877
# we probably need to hook the two 'lock a location' and
878
# 'have a transaction' together more delicately, so that
879
# we can have two locks (branch and storage) and one transaction
880
# ... and finishing the transaction unlocks both, but unlocking
881
# does not. - RBC 20051121
882
return self.control_files.get_transaction()
884
def _set_transaction(self, transaction):
885
"""Set a new active transaction."""
886
return self.control_files._set_transaction(transaction)
888
def abspath(self, name):
889
"""See Branch.abspath."""
890
return self.control_files._transport.abspath(name)
892
def _check_format(self, format):
893
"""Identify the branch format if needed.
895
The format is stored as a reference to the format object in
896
self._format for code that needs to check it later.
898
The format parameter is either None or the branch format class
899
used to open this branch.
901
FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
904
format = BzrBranchFormat.find_format(self.bzrdir)
905
self._format = format
906
mutter("got branch format %s", self._format)
909
def get_root_id(self):
910
"""See Branch.get_root_id."""
911
tree = self.repository.revision_tree(self.last_revision())
912
return tree.inventory.root.file_id
914
def lock_write(self):
915
# TODO: test for failed two phase locks. This is known broken.
916
self.control_files.lock_write()
917
self.repository.lock_write()
920
# TODO: test for failed two phase locks. This is known broken.
921
self.control_files.lock_read()
922
self.repository.lock_read()
925
# TODO: test for failed two phase locks. This is known broken.
926
self.repository.unlock()
927
self.control_files.unlock()
929
def peek_lock_mode(self):
930
if self.control_files._lock_count == 0:
933
return self.control_files._lock_mode
936
def print_file(self, file, revision_id):
937
"""See Branch.print_file."""
938
return self.repository.print_file(file, revision_id)
941
def append_revision(self, *revision_ids):
942
"""See Branch.append_revision."""
943
for revision_id in revision_ids:
944
mutter("add {%s} to revision-history" % revision_id)
945
rev_history = self.revision_history()
946
rev_history.extend(revision_ids)
947
self.set_revision_history(rev_history)
950
def set_revision_history(self, rev_history):
951
"""See Branch.set_revision_history."""
952
self.control_files.put_utf8(
953
'revision-history', '\n'.join(rev_history))
955
def get_revision_delta(self, revno):
956
"""Return the delta for one revision.
958
The delta is relative to its mainline predecessor, or the
959
empty tree for revision 1.
961
assert isinstance(revno, int)
962
rh = self.revision_history()
963
if not (1 <= revno <= len(rh)):
964
raise InvalidRevisionNumber(revno)
966
# revno is 1-based; list is 0-based
968
new_tree = self.repository.revision_tree(rh[revno-1])
970
old_tree = EmptyTree()
972
old_tree = self.repository.revision_tree(rh[revno-2])
973
return compare_trees(old_tree, new_tree)
976
def revision_history(self):
977
"""See Branch.revision_history."""
978
# FIXME are transactions bound to control files ? RBC 20051121
979
transaction = self.get_transaction()
980
history = transaction.map.find_revision_history()
981
if history is not None:
982
mutter("cache hit for revision-history in %s", self)
984
history = [l.rstrip('\r\n') for l in
985
self.control_files.get_utf8('revision-history').readlines()]
986
transaction.map.add_revision_history(history)
987
# this call is disabled because revision_history is
988
# not really an object yet, and the transaction is for objects.
989
# transaction.register_clean(history, precious=True)
992
def update_revisions(self, other, stop_revision=None):
993
"""See Branch.update_revisions."""
994
from bzrlib.fetch import greedy_fetch
995
if stop_revision is None:
996
stop_revision = other.last_revision()
997
### Should this be checking is_ancestor instead of revision_history?
998
if (stop_revision is not None and
999
stop_revision in self.revision_history()):
1001
greedy_fetch(to_branch=self, from_branch=other,
1002
revision=stop_revision)
1003
pullable_revs = self.pullable_revisions(other, stop_revision)
1004
if len(pullable_revs) > 0:
1005
self.append_revision(*pullable_revs)
1007
def pullable_revisions(self, other, stop_revision):
1008
"""See Branch.pullable_revisions."""
1009
other_revno = other.revision_id_to_revno(stop_revision)
1011
return self.missing_revisions(other, other_revno)
1012
except DivergedBranches, e:
1014
pullable_revs = get_intervening_revisions(self.last_revision(),
1017
assert self.last_revision() not in pullable_revs
1018
return pullable_revs
1019
except bzrlib.errors.NotAncestor:
1020
if is_ancestor(self.last_revision(), stop_revision, self):
1025
def basis_tree(self):
1026
"""See Branch.basis_tree."""
1027
return self.repository.revision_tree(self.last_revision())
1029
@deprecated_method(zero_eight)
1030
def working_tree(self):
1031
"""Create a Working tree object for this branch."""
1032
from bzrlib.workingtree import WorkingTree
1033
from bzrlib.transport.local import LocalTransport
1034
if (self.base.find('://') != -1 or
1035
not isinstance(self._transport, LocalTransport)):
1036
raise NoWorkingTree(self.base)
1037
return WorkingTree(self.base, branch=self)
1040
def pull(self, source, overwrite=False):
1041
"""See Branch.pull."""
1044
old_count = len(self.revision_history())
1046
self.update_revisions(source)
1047
except DivergedBranches:
1051
self.set_revision_history(source.revision_history())
1052
new_count = len(self.revision_history())
1053
return new_count - old_count
1057
def get_parent(self):
1058
"""See Branch.get_parent."""
1060
_locs = ['parent', 'pull', 'x-pull']
1063
return self.control_files.get_utf8(l).read().strip('\n')
1068
def get_push_location(self):
1069
"""See Branch.get_push_location."""
1070
config = bzrlib.config.BranchConfig(self)
1071
push_loc = config.get_user_option('push_location')
1074
def set_push_location(self, location):
1075
"""See Branch.set_push_location."""
1076
config = bzrlib.config.LocationConfig(self.base)
1077
config.set_user_option('push_location', location)
1080
def set_parent(self, url):
1081
"""See Branch.set_parent."""
1082
# TODO: Maybe delete old location files?
1083
# URLs should never be unicode, even on the local fs,
1084
# FIXUP this and get_parent in a future branch format bump:
1085
# read and rewrite the file, and have the new format code read
1086
# using .get not .get_utf8. RBC 20060125
1087
self.control_files.put_utf8('parent', url + '\n')
1089
def tree_config(self):
1090
return TreeConfig(self)
1092
def _get_truncated_history(self, revision_id):
1093
history = self.revision_history()
1094
if revision_id is None:
1097
idx = history.index(revision_id)
1099
raise InvalidRevisionId(revision_id=revision, branch=self)
1100
return history[:idx+1]
1103
def _clone_weave(self, to_location, revision=None, basis_branch=None):
1105
from bzrlib.workingtree import WorkingTree
1106
assert isinstance(to_location, basestring)
1107
if basis_branch is not None:
1108
note("basis_branch is not supported for fast weave copy yet.")
1110
history = self._get_truncated_history(revision)
1111
if not bzrlib.osutils.lexists(to_location):
1112
os.mkdir(to_location)
1113
bzrdir_to = self.bzrdir._format.initialize(to_location)
1114
self.repository.clone(bzrdir_to)
1115
branch_to = bzrdir_to.create_branch()
1116
mutter("copy branch from %s to %s", self, branch_to)
1118
# FIXME duplicate code with base .clone().
1119
# .. would template method be useful here? RBC 20051207
1120
branch_to.set_parent(self.base)
1121
branch_to.append_revision(*history)
1122
WorkingTree.create(branch_to, branch_to.base)
1127
class BranchTestProviderAdapter(object):
1128
"""A tool to generate a suite testing multiple branch formats at once.
1130
This is done by copying the test once for each transport and injecting
1131
the transport_server, transport_readonly_server, and branch_format
1132
classes into each copy. Each copy is also given a new id() to make it
1136
def __init__(self, transport_server, transport_readonly_server, formats):
1137
self._transport_server = transport_server
1138
self._transport_readonly_server = transport_readonly_server
1139
self._formats = formats
1141
def adapt(self, test):
1142
result = TestSuite()
1143
for branch_format, bzrdir_format in self._formats:
1144
new_test = deepcopy(test)
1145
new_test.transport_server = self._transport_server
1146
new_test.transport_readonly_server = self._transport_readonly_server
1147
new_test.bzrdir_format = bzrdir_format
1148
new_test.branch_format = branch_format
1149
def make_new_test_id():
1150
new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1151
return lambda: new_id
1152
new_test.id = make_new_test_id()
1153
result.addTest(new_test)
1157
######################################################################
1161
@deprecated_function(zero_eight)
1162
def ScratchBranch(*args, **kwargs):
1163
"""See bzrlib.bzrdir.ScratchDir."""
1164
d = ScratchDir(*args, **kwargs)
1165
return d.open_branch()
1168
@deprecated_function(zero_eight)
1169
def is_control_file(*args, **kwargs):
1170
"""See bzrlib.workingtree.is_control_file."""
1171
return bzrlib.workingtree.is_control_file(*args, **kwargs)