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 (
61
get_intervening_revisions,
66
from bzrlib.store import copy_all
67
from bzrlib.symbol_versioning import *
68
import bzrlib.transactions as transactions
69
from bzrlib.transport import Transport, get_transport
70
from bzrlib.tree import EmptyTree, RevisionTree
75
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
76
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
77
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
80
# TODO: Maybe include checks for common corruption of newlines, etc?
82
# TODO: Some operations like log might retrieve the same revisions
83
# repeatedly to calculate deltas. We could perhaps have a weakref
84
# cache in memory to make this faster. In general anything can be
85
# cached in memory between lock and unlock operations. .. nb thats
86
# what the transaction identity map provides
89
######################################################################
93
"""Branch holding a history of revisions.
96
Base directory/url of the branch.
98
# this is really an instance variable - FIXME move it there
104
"""Construct the current default format branch in a_bzrdir.
106
This creates the current default BzrDir format, and if that
107
supports multiple Branch formats, then the default Branch format
110
print "not usable until we have repositories"
111
raise NotImplementedError("not usable right now")
112
return bzrdir.BzrDir.create(base)
114
def __init__(self, *ignored, **ignored_too):
115
raise NotImplementedError('The Branch class is abstract')
118
@deprecated_method(zero_eight)
119
def open_downlevel(base):
120
"""Open a branch which may be of an old format."""
121
return Branch.open(base, _unsupported=True)
124
def open(base, _unsupported=False):
125
"""Open the repository rooted at base.
127
For instance, if the repository is at URL/.bzr/repository,
128
Repository.open(URL) -> a Repository instance.
130
control = bzrdir.BzrDir.open(base, _unsupported)
131
return control.open_branch(_unsupported)
134
def open_containing(url):
135
"""Open an existing branch which contains url.
137
This probes for a branch at url, and searches upwards from there.
139
Basically we keep looking up until we find the control directory or
140
run into the root. If there isn't one, raises NotBranchError.
141
If there is one and it is either an unrecognised format or an unsupported
142
format, UnknownFormatError or UnsupportedFormatError are raised.
143
If there is one, it is returned, along with the unused portion of url.
145
control, relpath = bzrdir.BzrDir.open_containing(url)
146
return control.open_branch(), relpath
149
@deprecated_function(zero_eight)
150
def initialize(base):
151
"""Create a new working tree and branch, rooted at 'base' (url)
153
NOTE: This will soon be deprecated in favour of creation
156
return bzrdir.BzrDir.create_standalone_workingtree(base).branch
158
def setup_caching(self, cache_root):
159
"""Subclasses that care about caching should override this, and set
160
up cached stores located under cache_root.
162
# seems to be unused, 2006-01-13 mbp
163
warn('%s is deprecated' % self.setup_caching)
164
self.cache_root = cache_root
167
cfg = self.tree_config()
168
return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
170
def _set_nick(self, nick):
171
cfg = self.tree_config()
172
cfg.set_option(nick, "nickname")
173
assert cfg.get_option("nickname") == nick
175
nick = property(_get_nick, _set_nick)
177
def lock_write(self):
178
raise NotImplementedError('lock_write is abstract')
181
raise NotImplementedError('lock_read is abstract')
184
raise NotImplementedError('unlock is abstract')
186
def peek_lock_mode(self):
187
"""Return lock mode for the Branch: 'r', 'w' or None"""
188
raise NotImplementedError(self.peek_lock_mode)
190
def abspath(self, name):
191
"""Return absolute filename for something in the branch
193
XXX: Robert Collins 20051017 what is this used for? why is it a branch
194
method and not a tree method.
196
raise NotImplementedError('abspath is abstract')
199
def fetch(self, from_branch, last_revision=None, pb=None):
200
"""Copy revisions from from_branch into this branch.
202
:param from_branch: Where to copy from.
203
:param last_revision: What revision to stop at (None for at the end
205
:param pb: An optional progress bar to use.
207
Returns the copied revision count and the failed revisions in a tuple:
210
if self.base == from_branch.base:
211
raise Exception("can't fetch from a branch to itself %s, %s" %
212
(self.base, to_branch.base))
214
pb = bzrlib.ui.ui_factory.progress_bar()
216
from_branch.lock_read()
218
if last_revision is None:
219
pb.update('get source history')
220
from_history = from_branch.revision_history()
222
last_revision = from_history[-1]
224
# no history in the source branch
225
last_revision = NULL_REVISION
226
return self.repository.fetch(from_branch.repository,
227
revision_id=last_revision,
232
def get_root_id(self):
233
"""Return the id of this branches root"""
234
raise NotImplementedError('get_root_id is abstract')
236
def print_file(self, file, revision_id):
237
"""Print `file` to stdout."""
238
raise NotImplementedError('print_file is abstract')
240
def append_revision(self, *revision_ids):
241
raise NotImplementedError('append_revision is abstract')
243
def set_revision_history(self, rev_history):
244
raise NotImplementedError('set_revision_history is abstract')
246
def revision_history(self):
247
"""Return sequence of revision hashes on to this branch."""
248
raise NotImplementedError('revision_history is abstract')
251
"""Return current revision number for this branch.
253
That is equivalent to the number of revisions committed to
256
return len(self.revision_history())
258
def last_revision(self):
259
"""Return last patch hash, or None if no history."""
260
ph = self.revision_history()
266
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
267
"""Return a list of new revisions that would perfectly fit.
269
If self and other have not diverged, return a list of the revisions
270
present in other, but missing from self.
272
>>> from bzrlib.workingtree import WorkingTree
273
>>> bzrlib.trace.silent = True
274
>>> d1 = bzrdir.ScratchDir()
275
>>> br1 = d1.open_branch()
276
>>> wt1 = d1.open_workingtree()
277
>>> d2 = bzrdir.ScratchDir()
278
>>> br2 = d2.open_branch()
279
>>> wt2 = d2.open_workingtree()
280
>>> br1.missing_revisions(br2)
282
>>> wt2.commit("lala!", rev_id="REVISION-ID-1")
283
>>> br1.missing_revisions(br2)
285
>>> br2.missing_revisions(br1)
287
>>> wt1.commit("lala!", rev_id="REVISION-ID-1")
288
>>> br1.missing_revisions(br2)
290
>>> wt2.commit("lala!", rev_id="REVISION-ID-2A")
291
>>> br1.missing_revisions(br2)
293
>>> wt1.commit("lala!", rev_id="REVISION-ID-2B")
294
>>> br1.missing_revisions(br2)
295
Traceback (most recent call last):
296
DivergedBranches: These branches have diverged. Try merge.
298
self_history = self.revision_history()
299
self_len = len(self_history)
300
other_history = other.revision_history()
301
other_len = len(other_history)
302
common_index = min(self_len, other_len) -1
303
if common_index >= 0 and \
304
self_history[common_index] != other_history[common_index]:
305
raise DivergedBranches(self, other)
307
if stop_revision is None:
308
stop_revision = other_len
310
assert isinstance(stop_revision, int)
311
if stop_revision > other_len:
312
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
313
return other_history[self_len:stop_revision]
315
def update_revisions(self, other, stop_revision=None):
316
"""Pull in new perfect-fit revisions."""
317
raise NotImplementedError('update_revisions is abstract')
319
def pullable_revisions(self, other, stop_revision):
320
raise NotImplementedError('pullable_revisions is abstract')
322
def revision_id_to_revno(self, revision_id):
323
"""Given a revision id, return its revno"""
324
if revision_id is None:
326
history = self.revision_history()
328
return history.index(revision_id) + 1
330
raise bzrlib.errors.NoSuchRevision(self, revision_id)
332
def get_rev_id(self, revno, history=None):
333
"""Find the revision id of the specified revno."""
337
history = self.revision_history()
338
elif revno <= 0 or revno > len(history):
339
raise bzrlib.errors.NoSuchRevision(self, revno)
340
return history[revno - 1]
342
def pull(self, source, overwrite=False, stop_revision=None):
343
raise NotImplementedError('pull is abstract')
345
def basis_tree(self):
346
"""Return `Tree` object for last revision.
348
If there are no revisions yet, return an `EmptyTree`.
350
return self.repository.revision_tree(self.last_revision())
352
def rename_one(self, from_rel, to_rel):
355
This can change the directory or the filename or both.
357
raise NotImplementedError('rename_one is abstract')
359
def move(self, from_paths, to_name):
362
to_name must exist as a versioned directory.
364
If to_name exists and is a directory, the files are moved into
365
it, keeping their old names. If it is a directory,
367
Note that to_name is only the last component of the new name;
368
this doesn't change the directory.
370
This returns a list of (from_path, to_path) pairs for each
373
raise NotImplementedError('move is abstract')
375
def get_parent(self):
376
"""Return the parent location of the branch.
378
This is the default location for push/pull/missing. The usual
379
pattern is that the user can override it by specifying a
382
raise NotImplementedError('get_parent is abstract')
384
def get_push_location(self):
385
"""Return the None or the location to push this branch to."""
386
raise NotImplementedError('get_push_location is abstract')
388
def set_push_location(self, location):
389
"""Set a new push location for this branch."""
390
raise NotImplementedError('set_push_location is abstract')
392
def set_parent(self, url):
393
raise NotImplementedError('set_parent is abstract')
395
def check_revno(self, revno):
397
Check whether a revno corresponds to any revision.
398
Zero (the NULL revision) is considered valid.
401
self.check_real_revno(revno)
403
def check_real_revno(self, revno):
405
Check whether a revno corresponds to a real revision.
406
Zero (the NULL revision) is considered invalid
408
if revno < 1 or revno > self.revno():
409
raise InvalidRevisionNumber(revno)
412
def clone(self, *args, **kwargs):
413
"""Clone this branch into to_bzrdir preserving all semantic values.
415
revision_id: if not None, the revision history in the new branch will
416
be truncated to end with revision_id.
418
# for API compatability, until 0.8 releases we provide the old api:
419
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
420
# after 0.8 releases, the *args and **kwargs should be changed:
421
# def clone(self, to_bzrdir, revision_id=None):
422
if (kwargs.get('to_location', None) or
423
kwargs.get('revision', None) or
424
kwargs.get('basis_branch', None) or
425
(len(args) and isinstance(args[0], basestring))):
426
# backwards compatability api:
427
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
428
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
431
basis_branch = args[2]
433
basis_branch = kwargs.get('basis_branch', None)
435
basis = basis_branch.bzrdir
440
revision_id = args[1]
442
revision_id = kwargs.get('revision', None)
447
# no default to raise if not provided.
448
url = kwargs.get('to_location')
449
return self.bzrdir.clone(url,
450
revision_id=revision_id,
451
basis=basis).open_branch()
453
# generate args by hand
455
revision_id = args[1]
457
revision_id = kwargs.get('revision_id', None)
461
# no default to raise if not provided.
462
to_bzrdir = kwargs.get('to_bzrdir')
463
result = self._format.initialize(to_bzrdir)
464
self.copy_content_into(result, revision_id=revision_id)
468
def sprout(self, to_bzrdir, revision_id=None):
469
"""Create a new line of development from the branch, into to_bzrdir.
471
revision_id: if not None, the revision history in the new branch will
472
be truncated to end with revision_id.
474
result = self._format.initialize(to_bzrdir)
475
self.copy_content_into(result, revision_id=revision_id)
476
result.set_parent(self.bzrdir.root_transport.base)
480
def copy_content_into(self, destination, revision_id=None):
481
"""Copy the content of self into destination.
483
revision_id: if not None, the revision history in the new branch will
484
be truncated to end with revision_id.
486
new_history = self.revision_history()
487
if revision_id is not None:
489
new_history = new_history[:new_history.index(revision_id) + 1]
491
rev = self.repository.get_revision(revision_id)
492
new_history = rev.get_history(self.repository)[1:]
493
destination.set_revision_history(new_history)
494
parent = self.get_parent()
496
destination.set_parent(parent)
499
class BranchFormat(object):
500
"""An encapsulation of the initialization and open routines for a format.
502
Formats provide three things:
503
* An initialization routine,
507
Formats are placed in an dict by their format string for reference
508
during branch opening. Its not required that these be instances, they
509
can be classes themselves with class methods - it simply depends on
510
whether state is needed for a given format or not.
512
Once a format is deprecated, just deprecate the initialize and open
513
methods on the format class. Do not deprecate the object, as the
514
object will be created every time regardless.
517
_default_format = None
518
"""The default format used for new branches."""
521
"""The known formats."""
524
def find_format(klass, a_bzrdir):
525
"""Return the format for the branch object in a_bzrdir."""
527
transport = a_bzrdir.get_branch_transport(None)
528
format_string = transport.get("format").read()
529
return klass._formats[format_string]
531
raise NotBranchError(path=transport.base)
533
raise errors.UnknownFormatError(format_string)
536
def get_default_format(klass):
537
"""Return the current default format."""
538
return klass._default_format
540
def get_format_string(self):
541
"""Return the ASCII format string that identifies this format."""
542
raise NotImplementedError(self.get_format_string)
544
def initialize(self, a_bzrdir):
545
"""Create a branch of this format in a_bzrdir."""
546
raise NotImplementedError(self.initialized)
548
def is_supported(self):
549
"""Is this format supported?
551
Supported formats can be initialized and opened.
552
Unsupported formats may not support initialization or committing or
553
some other features depending on the reason for not being supported.
557
def open(self, a_bzrdir, _found=False):
558
"""Return the branch object for a_bzrdir
560
_found is a private parameter, do not use it. It is used to indicate
561
if format probing has already be done.
563
raise NotImplementedError(self.open)
566
def register_format(klass, format):
567
klass._formats[format.get_format_string()] = format
570
def set_default_format(klass, format):
571
klass._default_format = format
574
def unregister_format(klass, format):
575
assert klass._formats[format.get_format_string()] is format
576
del klass._formats[format.get_format_string()]
579
class BzrBranchFormat4(BranchFormat):
580
"""Bzr branch format 4.
583
- a revision-history file.
584
- a branch-lock lock file [ to be shared with the bzrdir ]
587
def initialize(self, a_bzrdir):
588
"""Create a branch of this format in a_bzrdir."""
589
mutter('creating branch in %s', a_bzrdir.transport.base)
590
branch_transport = a_bzrdir.get_branch_transport(self)
591
utf8_files = [('revision-history', ''),
594
control_files = LockableFiles(branch_transport, 'branch-lock')
595
control_files.lock_write()
597
for file, content in utf8_files:
598
control_files.put_utf8(file, content)
600
control_files.unlock()
601
return self.open(a_bzrdir, _found=True)
604
super(BzrBranchFormat4, self).__init__()
605
self._matchingbzrdir = bzrdir.BzrDirFormat6()
607
def open(self, a_bzrdir, _found=False):
608
"""Return the branch object for a_bzrdir
610
_found is a private parameter, do not use it. It is used to indicate
611
if format probing has already be done.
614
# we are being called directly and must probe.
615
raise NotImplementedError
616
return BzrBranch(_format=self,
617
_control_files=a_bzrdir._control_files,
619
_repository=a_bzrdir.open_repository())
622
class BzrBranchFormat5(BranchFormat):
623
"""Bzr branch format 5.
626
- a revision-history file.
629
- works with shared repositories.
632
def get_format_string(self):
633
"""See BranchFormat.get_format_string()."""
634
return "Bazaar-NG branch format 5\n"
636
def initialize(self, a_bzrdir):
637
"""Create a branch of this format in a_bzrdir."""
638
mutter('creating branch in %s', a_bzrdir.transport.base)
639
branch_transport = a_bzrdir.get_branch_transport(self)
641
utf8_files = [('revision-history', ''),
645
branch_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
646
control_files = LockableFiles(branch_transport, 'lock')
647
control_files.lock_write()
648
control_files.put_utf8('format', self.get_format_string())
650
for file, content in utf8_files:
651
control_files.put_utf8(file, content)
653
control_files.unlock()
654
return self.open(a_bzrdir, _found=True, )
657
super(BzrBranchFormat5, self).__init__()
658
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
660
def open(self, a_bzrdir, _found=False):
661
"""Return the branch object for a_bzrdir
663
_found is a private parameter, do not use it. It is used to indicate
664
if format probing has already be done.
667
format = BranchFormat.find_format(a_bzrdir)
668
assert format.__class__ == self.__class__
669
transport = a_bzrdir.get_branch_transport(None)
670
control_files = LockableFiles(transport, 'lock')
671
return BzrBranch(_format=self,
672
_control_files=control_files,
674
_repository=a_bzrdir.find_repository())
677
class BranchReferenceFormat(BranchFormat):
678
"""Bzr branch reference format.
680
Branch references are used in implementing checkouts, they
681
act as an alias to the real branch which is at some other url.
688
def get_format_string(self):
689
"""See BranchFormat.get_format_string()."""
690
return "Bazaar-NG Branch Reference Format 1\n"
692
def initialize(self, a_bzrdir, target_branch=None):
693
"""Create a branch of this format in a_bzrdir."""
694
if target_branch is None:
695
# this format does not implement branch itself, thus the implicit
696
# creation contract must see it as uninitializable
697
raise errors.UninitializableFormat(self)
698
mutter('creating branch reference in %s', a_bzrdir.transport.base)
699
branch_transport = a_bzrdir.get_branch_transport(self)
700
# FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
701
branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
702
branch_transport.put('format', StringIO(self.get_format_string()))
703
return self.open(a_bzrdir, _found=True)
706
super(BranchReferenceFormat, self).__init__()
707
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
709
def _make_reference_clone_function(format, a_branch):
710
"""Create a clone() routine for a branch dynamically."""
711
def clone(to_bzrdir, revision_id=None):
712
"""See Branch.clone()."""
713
return format.initialize(to_bzrdir, a_branch)
714
# cannot obey revision_id limits when cloning a reference ...
715
# FIXME RBC 20060210 either nuke revision_id for clone, or
716
# emit some sort of warning/error to the caller ?!
719
def open(self, a_bzrdir, _found=False):
720
"""Return the branch that the branch reference in a_bzrdir points at.
722
_found is a private parameter, do not use it. It is used to indicate
723
if format probing has already be done.
726
format = BranchFormat.find_format(a_bzrdir)
727
assert format.__class__ == self.__class__
728
transport = a_bzrdir.get_branch_transport(None)
729
real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
730
result = real_bzrdir.open_branch()
731
# this changes the behaviour of result.clone to create a new reference
732
# rather than a copy of the content of the branch.
733
# I did not use a proxy object because that needs much more extensive
734
# testing, and we are only changing one behaviour at the moment.
735
# If we decide to alter more behaviours - i.e. the implicit nickname
736
# then this should be refactored to introduce a tested proxy branch
737
# and a subclass of that for use in overriding clone() and ....
739
result.clone = self._make_reference_clone_function(result)
743
# formats which have no format string are not discoverable
744
# and not independently creatable, so are not registered.
745
__default_format = BzrBranchFormat5()
746
BranchFormat.register_format(__default_format)
747
BranchFormat.register_format(BranchReferenceFormat())
748
BranchFormat.set_default_format(__default_format)
749
_legacy_formats = [BzrBranchFormat4(),
752
class BzrBranch(Branch):
753
"""A branch stored in the actual filesystem.
755
Note that it's "local" in the context of the filesystem; it doesn't
756
really matter if it's on an nfs/smb/afs/coda/... share, as long as
757
it's writable, and can be accessed via the normal filesystem API.
759
# We actually expect this class to be somewhat short-lived; part of its
760
# purpose is to try to isolate what bits of the branch logic are tied to
761
# filesystem access, so that in a later step, we can extricate them to
762
# a separarte ("storage") class.
763
_inventory_weave = None
765
# Map some sort of prefix into a namespace
766
# stuff like "revno:10", "revid:", etc.
767
# This should match a prefix with a function which accepts
768
REVISION_NAMESPACES = {}
770
def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
771
relax_version_check=DEPRECATED_PARAMETER, _format=None,
772
_control_files=None, a_bzrdir=None, _repository=None):
773
"""Create new branch object at a particular location.
775
transport -- A Transport object, defining how to access files.
777
init -- If True, create new control files in a previously
778
unversioned directory. If False, the branch must already
781
relax_version_check -- If true, the usual check for the branch
782
version is not applied. This is intended only for
783
upgrade/recovery type use; it's not guaranteed that
784
all operations will work on old format branches.
787
self.bzrdir = bzrdir.BzrDir.open(transport.base)
789
self.bzrdir = a_bzrdir
790
self._transport = self.bzrdir.transport.clone('..')
791
self._base = self._transport.base
792
self._format = _format
793
if _control_files is None:
794
raise BzrBadParameterMissing('_control_files')
795
self.control_files = _control_files
796
if deprecated_passed(init):
797
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
798
"deprecated as of bzr 0.8. Please use Branch.create().",
802
# this is slower than before deprecation, oh well never mind.
804
self._initialize(transport.base)
805
self._check_format(_format)
806
if deprecated_passed(relax_version_check):
807
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
808
"relax_version_check parameter is deprecated as of bzr 0.8. "
809
"Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
813
if (not relax_version_check
814
and not self._format.is_supported()):
815
raise errors.UnsupportedFormatError(
816
'sorry, branch format %r not supported' % fmt,
817
['use a different bzr version',
818
'or remove the .bzr directory'
819
' and "bzr init" again'])
820
if deprecated_passed(transport):
821
warn("BzrBranch.__init__(transport=XXX...): The transport "
822
"parameter is deprecated as of bzr 0.8. "
823
"Please use Branch.open, or bzrdir.open_branch().",
826
self.repository = _repository
829
return '%s(%r)' % (self.__class__.__name__, self.base)
834
# TODO: It might be best to do this somewhere else,
835
# but it is nice for a Branch object to automatically
836
# cache it's information.
837
# Alternatively, we could have the Transport objects cache requests
838
# See the earlier discussion about how major objects (like Branch)
839
# should never expect their __del__ function to run.
840
# XXX: cache_root seems to be unused, 2006-01-13 mbp
841
if hasattr(self, 'cache_root') and self.cache_root is not None:
843
shutil.rmtree(self.cache_root)
846
self.cache_root = None
851
base = property(_get_base, doc="The URL for the root of this branch.")
853
def _finish_transaction(self):
854
"""Exit the current transaction."""
855
return self.control_files._finish_transaction()
857
def get_transaction(self):
858
"""Return the current active transaction.
860
If no transaction is active, this returns a passthrough object
861
for which all data is immediately flushed and no caching happens.
863
# this is an explicit function so that we can do tricky stuff
864
# when the storage in rev_storage is elsewhere.
865
# we probably need to hook the two 'lock a location' and
866
# 'have a transaction' together more delicately, so that
867
# we can have two locks (branch and storage) and one transaction
868
# ... and finishing the transaction unlocks both, but unlocking
869
# does not. - RBC 20051121
870
return self.control_files.get_transaction()
872
def _set_transaction(self, transaction):
873
"""Set a new active transaction."""
874
return self.control_files._set_transaction(transaction)
876
def abspath(self, name):
877
"""See Branch.abspath."""
878
return self.control_files._transport.abspath(name)
880
def _check_format(self, format):
881
"""Identify the branch format if needed.
883
The format is stored as a reference to the format object in
884
self._format for code that needs to check it later.
886
The format parameter is either None or the branch format class
887
used to open this branch.
889
FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
892
format = BzrBranchFormat.find_format(self.bzrdir)
893
self._format = format
894
mutter("got branch format %s", self._format)
897
def get_root_id(self):
898
"""See Branch.get_root_id."""
899
tree = self.repository.revision_tree(self.last_revision())
900
return tree.inventory.root.file_id
902
def lock_write(self):
903
# TODO: test for failed two phase locks. This is known broken.
904
self.control_files.lock_write()
905
self.repository.lock_write()
908
# TODO: test for failed two phase locks. This is known broken.
909
self.control_files.lock_read()
910
self.repository.lock_read()
913
# TODO: test for failed two phase locks. This is known broken.
914
self.repository.unlock()
915
self.control_files.unlock()
917
def peek_lock_mode(self):
918
if self.control_files._lock_count == 0:
921
return self.control_files._lock_mode
924
def print_file(self, file, revision_id):
925
"""See Branch.print_file."""
926
return self.repository.print_file(file, revision_id)
929
def append_revision(self, *revision_ids):
930
"""See Branch.append_revision."""
931
for revision_id in revision_ids:
932
mutter("add {%s} to revision-history" % revision_id)
933
rev_history = self.revision_history()
934
rev_history.extend(revision_ids)
935
self.set_revision_history(rev_history)
938
def set_revision_history(self, rev_history):
939
"""See Branch.set_revision_history."""
940
self.control_files.put_utf8(
941
'revision-history', '\n'.join(rev_history))
943
def get_revision_delta(self, revno):
944
"""Return the delta for one revision.
946
The delta is relative to its mainline predecessor, or the
947
empty tree for revision 1.
949
assert isinstance(revno, int)
950
rh = self.revision_history()
951
if not (1 <= revno <= len(rh)):
952
raise InvalidRevisionNumber(revno)
954
# revno is 1-based; list is 0-based
956
new_tree = self.repository.revision_tree(rh[revno-1])
958
old_tree = EmptyTree()
960
old_tree = self.repository.revision_tree(rh[revno-2])
961
return compare_trees(old_tree, new_tree)
964
def revision_history(self):
965
"""See Branch.revision_history."""
966
# FIXME are transactions bound to control files ? RBC 20051121
967
transaction = self.get_transaction()
968
history = transaction.map.find_revision_history()
969
if history is not None:
970
mutter("cache hit for revision-history in %s", self)
972
history = [l.rstrip('\r\n') for l in
973
self.control_files.get_utf8('revision-history').readlines()]
974
transaction.map.add_revision_history(history)
975
# this call is disabled because revision_history is
976
# not really an object yet, and the transaction is for objects.
977
# transaction.register_clean(history, precious=True)
980
def update_revisions(self, other, stop_revision=None):
981
"""See Branch.update_revisions."""
982
if stop_revision is None:
983
stop_revision = other.last_revision()
984
### Should this be checking is_ancestor instead of revision_history?
985
if (stop_revision is not None and
986
stop_revision in self.revision_history()):
988
self.fetch(other, stop_revision)
989
pullable_revs = self.pullable_revisions(other, stop_revision)
990
if len(pullable_revs) > 0:
991
self.append_revision(*pullable_revs)
993
def pullable_revisions(self, other, stop_revision):
994
"""See Branch.pullable_revisions."""
995
other_revno = other.revision_id_to_revno(stop_revision)
997
return self.missing_revisions(other, other_revno)
998
except DivergedBranches, e:
1000
pullable_revs = get_intervening_revisions(self.last_revision(),
1003
assert self.last_revision() not in pullable_revs
1004
return pullable_revs
1005
except bzrlib.errors.NotAncestor:
1006
if is_ancestor(self.last_revision(), stop_revision, self):
1011
def basis_tree(self):
1012
"""See Branch.basis_tree."""
1013
return self.repository.revision_tree(self.last_revision())
1015
@deprecated_method(zero_eight)
1016
def working_tree(self):
1017
"""Create a Working tree object for this branch."""
1018
from bzrlib.workingtree import WorkingTree
1019
from bzrlib.transport.local import LocalTransport
1020
if (self.base.find('://') != -1 or
1021
not isinstance(self._transport, LocalTransport)):
1022
raise NoWorkingTree(self.base)
1023
return self.bzrdir.open_workingtree()
1026
def pull(self, source, overwrite=False, stop_revision=None):
1027
"""See Branch.pull."""
1030
old_count = len(self.revision_history())
1032
self.update_revisions(source,stop_revision)
1033
except DivergedBranches:
1037
self.set_revision_history(source.revision_history())
1038
new_count = len(self.revision_history())
1039
return new_count - old_count
1043
def get_parent(self):
1044
"""See Branch.get_parent."""
1046
_locs = ['parent', 'pull', 'x-pull']
1049
return self.control_files.get_utf8(l).read().strip('\n')
1054
def get_push_location(self):
1055
"""See Branch.get_push_location."""
1056
config = bzrlib.config.BranchConfig(self)
1057
push_loc = config.get_user_option('push_location')
1060
def set_push_location(self, location):
1061
"""See Branch.set_push_location."""
1062
config = bzrlib.config.LocationConfig(self.base)
1063
config.set_user_option('push_location', location)
1066
def set_parent(self, url):
1067
"""See Branch.set_parent."""
1068
# TODO: Maybe delete old location files?
1069
# URLs should never be unicode, even on the local fs,
1070
# FIXUP this and get_parent in a future branch format bump:
1071
# read and rewrite the file, and have the new format code read
1072
# using .get not .get_utf8. RBC 20060125
1073
self.control_files.put_utf8('parent', url + '\n')
1075
def tree_config(self):
1076
return TreeConfig(self)
1078
def _get_truncated_history(self, revision_id):
1079
history = self.revision_history()
1080
if revision_id is None:
1083
idx = history.index(revision_id)
1085
raise InvalidRevisionId(revision_id=revision, branch=self)
1086
return history[:idx+1]
1089
def _clone_weave(self, to_location, revision=None, basis_branch=None):
1091
from bzrlib.workingtree import WorkingTree
1092
assert isinstance(to_location, basestring)
1093
if basis_branch is not None:
1094
note("basis_branch is not supported for fast weave copy yet.")
1096
history = self._get_truncated_history(revision)
1097
if not bzrlib.osutils.lexists(to_location):
1098
os.mkdir(to_location)
1099
bzrdir_to = self.bzrdir._format.initialize(to_location)
1100
self.repository.clone(bzrdir_to)
1101
branch_to = bzrdir_to.create_branch()
1102
mutter("copy branch from %s to %s", self, branch_to)
1104
# FIXME duplicate code with base .clone().
1105
# .. would template method be useful here? RBC 20051207
1106
branch_to.set_parent(self.base)
1107
branch_to.append_revision(*history)
1108
WorkingTree.create(branch_to, branch_to.base)
1113
class BranchTestProviderAdapter(object):
1114
"""A tool to generate a suite testing multiple branch formats at once.
1116
This is done by copying the test once for each transport and injecting
1117
the transport_server, transport_readonly_server, and branch_format
1118
classes into each copy. Each copy is also given a new id() to make it
1122
def __init__(self, transport_server, transport_readonly_server, formats):
1123
self._transport_server = transport_server
1124
self._transport_readonly_server = transport_readonly_server
1125
self._formats = formats
1127
def adapt(self, test):
1128
result = TestSuite()
1129
for branch_format, bzrdir_format in self._formats:
1130
new_test = deepcopy(test)
1131
new_test.transport_server = self._transport_server
1132
new_test.transport_readonly_server = self._transport_readonly_server
1133
new_test.bzrdir_format = bzrdir_format
1134
new_test.branch_format = branch_format
1135
def make_new_test_id():
1136
new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1137
return lambda: new_id
1138
new_test.id = make_new_test_id()
1139
result.addTest(new_test)
1143
######################################################################
1147
@deprecated_function(zero_eight)
1148
def ScratchBranch(*args, **kwargs):
1149
"""See bzrlib.bzrdir.ScratchDir."""
1150
d = ScratchDir(*args, **kwargs)
1151
return d.open_branch()
1154
@deprecated_function(zero_eight)
1155
def is_control_file(*args, **kwargs):
1156
"""See bzrlib.workingtree.is_control_file."""
1157
return bzrlib.workingtree.is_control_file(*args, **kwargs)