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 cStringIO import StringIO
20
from bzrlib.lazy_import import lazy_import
21
lazy_import(globals(), """
22
from copy import deepcopy
23
from unittest import TestSuite
24
from warnings import warn
30
config as _mod_config,
35
revision as _mod_revision,
41
from bzrlib.config import BranchConfig, TreeConfig
42
from bzrlib.lockable_files import LockableFiles, TransportLock
45
from bzrlib.decorators import needs_read_lock, needs_write_lock
46
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches,
47
HistoryMissing, InvalidRevisionId,
48
InvalidRevisionNumber, LockError, NoSuchFile,
49
NoSuchRevision, NoWorkingTree, NotVersionedError,
50
NotBranchError, UninitializableFormat,
51
UnlistableStore, UnlistableBranch,
53
from bzrlib.symbol_versioning import (deprecated_function,
57
zero_eight, zero_nine,
59
from bzrlib.trace import mutter, note
62
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
63
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
64
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
67
# TODO: Maybe include checks for common corruption of newlines, etc?
69
# TODO: Some operations like log might retrieve the same revisions
70
# repeatedly to calculate deltas. We could perhaps have a weakref
71
# cache in memory to make this faster. In general anything can be
72
# cached in memory between lock and unlock operations. .. nb thats
73
# what the transaction identity map provides
76
######################################################################
80
"""Branch holding a history of revisions.
83
Base directory/url of the branch.
85
hooks: An instance of BranchHooks.
87
# this is really an instance variable - FIXME move it there
91
def __init__(self, *ignored, **ignored_too):
92
raise NotImplementedError('The Branch class is abstract')
95
"""Break a lock if one is present from another instance.
97
Uses the ui factory to ask for confirmation if the lock may be from
100
This will probe the repository for its lock as well.
102
self.control_files.break_lock()
103
self.repository.break_lock()
104
master = self.get_master_branch()
105
if master is not None:
109
@deprecated_method(zero_eight)
110
def open_downlevel(base):
111
"""Open a branch which may be of an old format."""
112
return Branch.open(base, _unsupported=True)
115
def open(base, _unsupported=False):
116
"""Open the branch rooted at base.
118
For instance, if the branch is at URL/.bzr/branch,
119
Branch.open(URL) -> a Branch instance.
121
control = bzrdir.BzrDir.open(base, _unsupported)
122
return control.open_branch(_unsupported)
125
def open_containing(url):
126
"""Open an existing branch which contains url.
128
This probes for a branch at url, and searches upwards from there.
130
Basically we keep looking up until we find the control directory or
131
run into the root. If there isn't one, raises NotBranchError.
132
If there is one and it is either an unrecognised format or an unsupported
133
format, UnknownFormatError or UnsupportedFormatError are raised.
134
If there is one, it is returned, along with the unused portion of url.
136
control, relpath = bzrdir.BzrDir.open_containing(url)
137
return control.open_branch(), relpath
140
@deprecated_function(zero_eight)
141
def initialize(base):
142
"""Create a new working tree and branch, rooted at 'base' (url)
144
NOTE: This will soon be deprecated in favour of creation
147
return bzrdir.BzrDir.create_standalone_workingtree(base).branch
149
@deprecated_function(zero_eight)
150
def setup_caching(self, cache_root):
151
"""Subclasses that care about caching should override this, and set
152
up cached stores located under cache_root.
154
NOTE: This is unused.
158
def get_config(self):
159
return BranchConfig(self)
162
return self.get_config().get_nickname()
164
def _set_nick(self, nick):
165
self.get_config().set_user_option('nickname', nick)
167
nick = property(_get_nick, _set_nick)
170
raise NotImplementedError(self.is_locked)
172
def lock_write(self):
173
raise NotImplementedError(self.lock_write)
176
raise NotImplementedError(self.lock_read)
179
raise NotImplementedError(self.unlock)
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 get_physical_lock_status(self):
186
raise NotImplementedError(self.get_physical_lock_status)
188
def abspath(self, name):
189
"""Return absolute filename for something in the branch
191
XXX: Robert Collins 20051017 what is this used for? why is it a branch
192
method and not a tree method.
194
raise NotImplementedError(self.abspath)
196
def bind(self, other):
197
"""Bind the local branch the other branch.
199
:param other: The branch to bind to
202
raise errors.UpgradeRequired(self.base)
205
def fetch(self, from_branch, last_revision=None, pb=None):
206
"""Copy revisions from from_branch into this branch.
208
:param from_branch: Where to copy from.
209
:param last_revision: What revision to stop at (None for at the end
211
:param pb: An optional progress bar to use.
213
Returns the copied revision count and the failed revisions in a tuple:
216
if self.base == from_branch.base:
219
nested_pb = ui.ui_factory.nested_progress_bar()
224
from_branch.lock_read()
226
if last_revision is None:
227
pb.update('get source history')
228
last_revision = from_branch.last_revision_info()[1]
229
return self.repository.fetch(from_branch.repository,
230
revision_id=last_revision,
233
if nested_pb is not None:
237
def get_bound_location(self):
238
"""Return the URL of the branch we are bound to.
240
Older format branches cannot bind, please be sure to use a metadir
245
def get_commit_builder(self, parents, config=None, timestamp=None,
246
timezone=None, committer=None, revprops=None,
248
"""Obtain a CommitBuilder for this branch.
250
:param parents: Revision ids of the parents of the new revision.
251
:param config: Optional configuration to use.
252
:param timestamp: Optional timestamp recorded for commit.
253
:param timezone: Optional timezone for timestamp.
254
:param committer: Optional committer to set for commit.
255
:param revprops: Optional dictionary of revision properties.
256
:param revision_id: Optional revision id.
260
config = self.get_config()
262
return self.repository.get_commit_builder(self, parents, config,
263
timestamp, timezone, committer, revprops, revision_id)
265
def get_master_branch(self):
266
"""Return the branch we are bound to.
268
:return: Either a Branch, or None
272
def get_revision_delta(self, revno):
273
"""Return the delta for one revision.
275
The delta is relative to its mainline predecessor, or the
276
empty tree for revision 1.
278
assert isinstance(revno, int)
279
rh = self.revision_history()
280
if not (1 <= revno <= len(rh)):
281
raise InvalidRevisionNumber(revno)
282
return self.repository.get_revision_delta(rh[revno-1])
284
def get_root_id(self):
285
"""Return the id of this branches root"""
286
raise NotImplementedError(self.get_root_id)
288
def print_file(self, file, revision_id):
289
"""Print `file` to stdout."""
290
raise NotImplementedError(self.print_file)
292
def append_revision(self, *revision_ids):
293
raise NotImplementedError(self.append_revision)
295
def set_revision_history(self, rev_history):
296
raise NotImplementedError(self.set_revision_history)
298
def revision_history(self):
299
"""Return sequence of revision hashes on to this branch."""
300
raise NotImplementedError(self.revision_history)
303
"""Return current revision number for this branch.
305
That is equivalent to the number of revisions committed to
308
return len(self.revision_history())
311
"""Older format branches cannot bind or unbind."""
312
raise errors.UpgradeRequired(self.base)
314
def last_revision(self):
315
"""Return last revision id, or None"""
316
ph = self.revision_history()
322
def last_revision_info(self):
323
"""Return information about the last revision.
325
:return: A tuple (revno, last_revision_id).
327
rh = self.revision_history()
330
return (revno, rh[-1])
332
return (0, _mod_revision.NULL_REVISION)
334
def missing_revisions(self, other, stop_revision=None):
335
"""Return a list of new revisions that would perfectly fit.
337
If self and other have not diverged, return a list of the revisions
338
present in other, but missing from self.
340
self_history = self.revision_history()
341
self_len = len(self_history)
342
other_history = other.revision_history()
343
other_len = len(other_history)
344
common_index = min(self_len, other_len) -1
345
if common_index >= 0 and \
346
self_history[common_index] != other_history[common_index]:
347
raise DivergedBranches(self, other)
349
if stop_revision is None:
350
stop_revision = other_len
352
assert isinstance(stop_revision, int)
353
if stop_revision > other_len:
354
raise errors.NoSuchRevision(self, stop_revision)
355
return other_history[self_len:stop_revision]
357
def update_revisions(self, other, stop_revision=None):
358
"""Pull in new perfect-fit revisions.
360
:param other: Another Branch to pull from
361
:param stop_revision: Updated until the given revision
364
raise NotImplementedError(self.update_revisions)
366
def revision_id_to_revno(self, revision_id):
367
"""Given a revision id, return its revno"""
368
if revision_id is None:
370
history = self.revision_history()
372
return history.index(revision_id) + 1
374
raise bzrlib.errors.NoSuchRevision(self, revision_id)
376
def get_rev_id(self, revno, history=None):
377
"""Find the revision id of the specified revno."""
381
history = self.revision_history()
382
if revno <= 0 or revno > len(history):
383
raise bzrlib.errors.NoSuchRevision(self, revno)
384
return history[revno - 1]
386
def pull(self, source, overwrite=False, stop_revision=None):
387
raise NotImplementedError(self.pull)
389
def basis_tree(self):
390
"""Return `Tree` object for last revision."""
391
return self.repository.revision_tree(self.last_revision())
393
def rename_one(self, from_rel, to_rel):
396
This can change the directory or the filename or both.
398
raise NotImplementedError(self.rename_one)
400
def move(self, from_paths, to_name):
403
to_name must exist as a versioned directory.
405
If to_name exists and is a directory, the files are moved into
406
it, keeping their old names. If it is a directory,
408
Note that to_name is only the last component of the new name;
409
this doesn't change the directory.
411
This returns a list of (from_path, to_path) pairs for each
414
raise NotImplementedError(self.move)
416
def get_parent(self):
417
"""Return the parent location of the branch.
419
This is the default location for push/pull/missing. The usual
420
pattern is that the user can override it by specifying a
423
raise NotImplementedError(self.get_parent)
425
def get_submit_branch(self):
426
"""Return the submit location of the branch.
428
This is the default location for bundle. The usual
429
pattern is that the user can override it by specifying a
432
return self.get_config().get_user_option('submit_branch')
434
def set_submit_branch(self, location):
435
"""Return the submit location of the branch.
437
This is the default location for bundle. The usual
438
pattern is that the user can override it by specifying a
441
self.get_config().set_user_option('submit_branch', location)
443
def get_push_location(self):
444
"""Return the None or the location to push this branch to."""
445
raise NotImplementedError(self.get_push_location)
447
def set_push_location(self, location):
448
"""Set a new push location for this branch."""
449
raise NotImplementedError(self.set_push_location)
451
def set_parent(self, url):
452
raise NotImplementedError(self.set_parent)
456
"""Synchronise this branch with the master branch if any.
458
:return: None or the last_revision pivoted out during the update.
462
def check_revno(self, revno):
464
Check whether a revno corresponds to any revision.
465
Zero (the NULL revision) is considered valid.
468
self.check_real_revno(revno)
470
def check_real_revno(self, revno):
472
Check whether a revno corresponds to a real revision.
473
Zero (the NULL revision) is considered invalid
475
if revno < 1 or revno > self.revno():
476
raise InvalidRevisionNumber(revno)
479
def clone(self, *args, **kwargs):
480
"""Clone this branch into to_bzrdir preserving all semantic values.
482
revision_id: if not None, the revision history in the new branch will
483
be truncated to end with revision_id.
485
# for API compatibility, until 0.8 releases we provide the old api:
486
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
487
# after 0.8 releases, the *args and **kwargs should be changed:
488
# def clone(self, to_bzrdir, revision_id=None):
489
if (kwargs.get('to_location', None) or
490
kwargs.get('revision', None) or
491
kwargs.get('basis_branch', None) or
492
(len(args) and isinstance(args[0], basestring))):
493
# backwards compatibility api:
494
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
495
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
498
basis_branch = args[2]
500
basis_branch = kwargs.get('basis_branch', None)
502
basis = basis_branch.bzrdir
507
revision_id = args[1]
509
revision_id = kwargs.get('revision', None)
514
# no default to raise if not provided.
515
url = kwargs.get('to_location')
516
return self.bzrdir.clone(url,
517
revision_id=revision_id,
518
basis=basis).open_branch()
520
# generate args by hand
522
revision_id = args[1]
524
revision_id = kwargs.get('revision_id', None)
528
# no default to raise if not provided.
529
to_bzrdir = kwargs.get('to_bzrdir')
530
result = self._format.initialize(to_bzrdir)
531
self.copy_content_into(result, revision_id=revision_id)
535
def sprout(self, to_bzrdir, revision_id=None):
536
"""Create a new line of development from the branch, into to_bzrdir.
538
revision_id: if not None, the revision history in the new branch will
539
be truncated to end with revision_id.
541
result = self._format.initialize(to_bzrdir)
542
self.copy_content_into(result, revision_id=revision_id)
543
result.set_parent(self.bzrdir.root_transport.base)
547
def copy_content_into(self, destination, revision_id=None):
548
"""Copy the content of self into destination.
550
revision_id: if not None, the revision history in the new branch will
551
be truncated to end with revision_id.
553
new_history = self.revision_history()
554
if revision_id is not None:
556
new_history = new_history[:new_history.index(revision_id) + 1]
558
rev = self.repository.get_revision(revision_id)
559
new_history = rev.get_history(self.repository)[1:]
560
destination.set_revision_history(new_history)
562
parent = self.get_parent()
563
except errors.InaccessibleParent, e:
564
mutter('parent was not accessible to copy: %s', e)
567
destination.set_parent(parent)
571
"""Check consistency of the branch.
573
In particular this checks that revisions given in the revision-history
574
do actually match up in the revision graph, and that they're all
575
present in the repository.
577
Callers will typically also want to check the repository.
579
:return: A BranchCheckResult.
581
mainline_parent_id = None
582
for revision_id in self.revision_history():
584
revision = self.repository.get_revision(revision_id)
585
except errors.NoSuchRevision, e:
586
raise errors.BzrCheckError("mainline revision {%s} not in repository"
588
# In general the first entry on the revision history has no parents.
589
# But it's not illegal for it to have parents listed; this can happen
590
# in imports from Arch when the parents weren't reachable.
591
if mainline_parent_id is not None:
592
if mainline_parent_id not in revision.parent_ids:
593
raise errors.BzrCheckError("previous revision {%s} not listed among "
595
% (mainline_parent_id, revision_id))
596
mainline_parent_id = revision_id
597
return BranchCheckResult(self)
599
def _get_checkout_format(self):
600
"""Return the most suitable metadir for a checkout of this branch.
601
Weaves are used if this branch's repostory uses weaves.
603
if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
604
from bzrlib import repository
605
format = bzrdir.BzrDirMetaFormat1()
606
format.repository_format = repository.RepositoryFormat7()
608
format = self.repository.bzrdir.cloning_metadir()
611
def create_checkout(self, to_location, revision_id=None,
613
"""Create a checkout of a branch.
615
:param to_location: The url to produce the checkout at
616
:param revision_id: The revision to check out
617
:param lightweight: If True, produce a lightweight checkout, otherwise,
618
produce a bound branch (heavyweight checkout)
619
:return: The tree of the created checkout
621
t = transport.get_transport(to_location)
624
except errors.FileExists:
627
checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
628
BranchReferenceFormat().initialize(checkout, self)
630
format = self._get_checkout_format()
631
checkout_branch = bzrdir.BzrDir.create_branch_convenience(
632
to_location, force_new_tree=False, format=format)
633
checkout = checkout_branch.bzrdir
634
checkout_branch.bind(self)
635
# pull up to the specified revision_id to set the initial
636
# branch tip correctly, and seed it with history.
637
checkout_branch.pull(self, stop_revision=revision_id)
638
return checkout.create_workingtree(revision_id)
641
class BranchFormat(object):
642
"""An encapsulation of the initialization and open routines for a format.
644
Formats provide three things:
645
* An initialization routine,
649
Formats are placed in an dict by their format string for reference
650
during branch opening. Its not required that these be instances, they
651
can be classes themselves with class methods - it simply depends on
652
whether state is needed for a given format or not.
654
Once a format is deprecated, just deprecate the initialize and open
655
methods on the format class. Do not deprecate the object, as the
656
object will be created every time regardless.
659
_default_format = None
660
"""The default format used for new branches."""
663
"""The known formats."""
666
def find_format(klass, a_bzrdir):
667
"""Return the format for the branch object in a_bzrdir."""
669
transport = a_bzrdir.get_branch_transport(None)
670
format_string = transport.get("format").read()
671
return klass._formats[format_string]
673
raise NotBranchError(path=transport.base)
675
raise errors.UnknownFormatError(format=format_string)
678
def get_default_format(klass):
679
"""Return the current default format."""
680
return klass._default_format
682
def get_format_string(self):
683
"""Return the ASCII format string that identifies this format."""
684
raise NotImplementedError(self.get_format_string)
686
def get_format_description(self):
687
"""Return the short format description for this format."""
688
raise NotImplementedError(self.get_format_string)
690
def get_reference(self, a_bzrdir):
691
"""Get the target reference of the branch in a_bzrdir.
693
format probing must have been completed before calling
694
this method - it is assumed that the format of the branch
695
in a_bzrdir is correct.
697
:param a_bzrdir: The bzrdir to get the branch data from.
698
:return: None if the branch is not a reference branch.
702
def initialize(self, a_bzrdir):
703
"""Create a branch of this format in a_bzrdir."""
704
raise NotImplementedError(self.initialize)
706
def is_supported(self):
707
"""Is this format supported?
709
Supported formats can be initialized and opened.
710
Unsupported formats may not support initialization or committing or
711
some other features depending on the reason for not being supported.
715
def open(self, a_bzrdir, _found=False):
716
"""Return the branch object for a_bzrdir
718
_found is a private parameter, do not use it. It is used to indicate
719
if format probing has already be done.
721
raise NotImplementedError(self.open)
724
def register_format(klass, format):
725
klass._formats[format.get_format_string()] = format
728
def set_default_format(klass, format):
729
klass._default_format = format
732
def unregister_format(klass, format):
733
assert klass._formats[format.get_format_string()] is format
734
del klass._formats[format.get_format_string()]
737
return self.get_format_string().rstrip()
740
class BranchHooks(dict):
741
"""A dictionary mapping hook name to a list of callables for branch hooks.
743
e.g. ['set_rh'] Is the list of items to be called when the
744
set_revision_history function is invoked.
748
"""Create the default hooks.
750
These are all empty initially, because by default nothing should get
754
# invoked whenever the revision history has been set
755
# with set_revision_history. The api signature is
756
# (branch, revision_history), and the branch will
757
# be write-locked. Introduced in 0.15.
760
def install_hook(self, hook_name, a_callable):
761
"""Install a_callable in to the hook hook_name.
763
:param hook_name: A hook name. See the __init__ method of BranchHooks
764
for the complete list of hooks.
765
:param a_callable: The callable to be invoked when the hook triggers.
766
The exact signature will depend on the hook - see the __init__
767
method of BranchHooks for details on each hook.
770
self[hook_name].append(a_callable)
772
raise errors.UnknownHook('branch', hook_name)
775
# install the default hooks into the Branch class.
776
Branch.hooks = BranchHooks()
779
class BzrBranchFormat4(BranchFormat):
780
"""Bzr branch format 4.
783
- a revision-history file.
784
- a branch-lock lock file [ to be shared with the bzrdir ]
787
def get_format_description(self):
788
"""See BranchFormat.get_format_description()."""
789
return "Branch format 4"
791
def initialize(self, a_bzrdir):
792
"""Create a branch of this format in a_bzrdir."""
793
mutter('creating branch in %s', a_bzrdir.transport.base)
794
branch_transport = a_bzrdir.get_branch_transport(self)
795
utf8_files = [('revision-history', ''),
798
control_files = lockable_files.LockableFiles(branch_transport,
799
'branch-lock', lockable_files.TransportLock)
800
control_files.create_lock()
801
control_files.lock_write()
803
for file, content in utf8_files:
804
control_files.put_utf8(file, content)
806
control_files.unlock()
807
return self.open(a_bzrdir, _found=True)
810
super(BzrBranchFormat4, self).__init__()
811
self._matchingbzrdir = bzrdir.BzrDirFormat6()
813
def open(self, a_bzrdir, _found=False):
814
"""Return the branch object for a_bzrdir
816
_found is a private parameter, do not use it. It is used to indicate
817
if format probing has already be done.
820
# we are being called directly and must probe.
821
raise NotImplementedError
822
return BzrBranch(_format=self,
823
_control_files=a_bzrdir._control_files,
825
_repository=a_bzrdir.open_repository())
828
return "Bazaar-NG branch format 4"
831
class BzrBranchFormat5(BranchFormat):
832
"""Bzr branch format 5.
835
- a revision-history file.
837
- a lock dir guarding the branch itself
838
- all of this stored in a branch/ subdirectory
839
- works with shared repositories.
841
This format is new in bzr 0.8.
844
def get_format_string(self):
845
"""See BranchFormat.get_format_string()."""
846
return "Bazaar-NG branch format 5\n"
848
def get_format_description(self):
849
"""See BranchFormat.get_format_description()."""
850
return "Branch format 5"
852
def initialize(self, a_bzrdir):
853
"""Create a branch of this format in a_bzrdir."""
854
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
855
branch_transport = a_bzrdir.get_branch_transport(self)
856
utf8_files = [('revision-history', ''),
859
control_files = lockable_files.LockableFiles(branch_transport, 'lock',
861
control_files.create_lock()
862
control_files.lock_write()
863
control_files.put_utf8('format', self.get_format_string())
865
for file, content in utf8_files:
866
control_files.put_utf8(file, content)
868
control_files.unlock()
869
return self.open(a_bzrdir, _found=True, )
872
super(BzrBranchFormat5, self).__init__()
873
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
875
def open(self, a_bzrdir, _found=False):
876
"""Return the branch object for a_bzrdir
878
_found is a private parameter, do not use it. It is used to indicate
879
if format probing has already be done.
882
format = BranchFormat.find_format(a_bzrdir)
883
assert format.__class__ == self.__class__
885
transport = a_bzrdir.get_branch_transport(None)
886
control_files = lockable_files.LockableFiles(transport, 'lock',
888
return BzrBranch5(_format=self,
889
_control_files=control_files,
891
_repository=a_bzrdir.find_repository())
893
raise NotBranchError(path=transport.base)
896
return "Bazaar-NG Metadir branch format 5"
899
class BranchReferenceFormat(BranchFormat):
900
"""Bzr branch reference format.
902
Branch references are used in implementing checkouts, they
903
act as an alias to the real branch which is at some other url.
910
def get_format_string(self):
911
"""See BranchFormat.get_format_string()."""
912
return "Bazaar-NG Branch Reference Format 1\n"
914
def get_format_description(self):
915
"""See BranchFormat.get_format_description()."""
916
return "Checkout reference format 1"
918
def get_reference(self, a_bzrdir):
919
"""See BranchFormat.get_reference()."""
920
transport = a_bzrdir.get_branch_transport(None)
921
return transport.get('location').read()
923
def initialize(self, a_bzrdir, target_branch=None):
924
"""Create a branch of this format in a_bzrdir."""
925
if target_branch is None:
926
# this format does not implement branch itself, thus the implicit
927
# creation contract must see it as uninitializable
928
raise errors.UninitializableFormat(self)
929
mutter('creating branch reference in %s', a_bzrdir.transport.base)
930
branch_transport = a_bzrdir.get_branch_transport(self)
931
branch_transport.put_bytes('location',
932
target_branch.bzrdir.root_transport.base)
933
branch_transport.put_bytes('format', self.get_format_string())
934
return self.open(a_bzrdir, _found=True)
937
super(BranchReferenceFormat, self).__init__()
938
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
940
def _make_reference_clone_function(format, a_branch):
941
"""Create a clone() routine for a branch dynamically."""
942
def clone(to_bzrdir, revision_id=None):
943
"""See Branch.clone()."""
944
return format.initialize(to_bzrdir, a_branch)
945
# cannot obey revision_id limits when cloning a reference ...
946
# FIXME RBC 20060210 either nuke revision_id for clone, or
947
# emit some sort of warning/error to the caller ?!
950
def open(self, a_bzrdir, _found=False, location=None):
951
"""Return the branch that the branch reference in a_bzrdir points at.
953
_found is a private parameter, do not use it. It is used to indicate
954
if format probing has already be done.
957
format = BranchFormat.find_format(a_bzrdir)
958
assert format.__class__ == self.__class__
960
location = self.get_reference(a_bzrdir)
961
real_bzrdir = bzrdir.BzrDir.open(location)
962
result = real_bzrdir.open_branch()
963
# this changes the behaviour of result.clone to create a new reference
964
# rather than a copy of the content of the branch.
965
# I did not use a proxy object because that needs much more extensive
966
# testing, and we are only changing one behaviour at the moment.
967
# If we decide to alter more behaviours - i.e. the implicit nickname
968
# then this should be refactored to introduce a tested proxy branch
969
# and a subclass of that for use in overriding clone() and ....
971
result.clone = self._make_reference_clone_function(result)
975
# formats which have no format string are not discoverable
976
# and not independently creatable, so are not registered.
977
__default_format = BzrBranchFormat5()
978
BranchFormat.register_format(__default_format)
979
BranchFormat.register_format(BranchReferenceFormat())
980
BranchFormat.set_default_format(__default_format)
981
_legacy_formats = [BzrBranchFormat4(),
984
class BzrBranch(Branch):
985
"""A branch stored in the actual filesystem.
987
Note that it's "local" in the context of the filesystem; it doesn't
988
really matter if it's on an nfs/smb/afs/coda/... share, as long as
989
it's writable, and can be accessed via the normal filesystem API.
992
def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
993
relax_version_check=DEPRECATED_PARAMETER, _format=None,
994
_control_files=None, a_bzrdir=None, _repository=None):
995
"""Create new branch object at a particular location.
997
transport -- A Transport object, defining how to access files.
999
init -- If True, create new control files in a previously
1000
unversioned directory. If False, the branch must already
1003
relax_version_check -- If true, the usual check for the branch
1004
version is not applied. This is intended only for
1005
upgrade/recovery type use; it's not guaranteed that
1006
all operations will work on old format branches.
1008
if a_bzrdir is None:
1009
self.bzrdir = bzrdir.BzrDir.open(transport.base)
1011
self.bzrdir = a_bzrdir
1012
self._transport = self.bzrdir.transport.clone('..')
1013
self._base = self._transport.base
1014
self._format = _format
1015
if _control_files is None:
1016
raise ValueError('BzrBranch _control_files is None')
1017
self.control_files = _control_files
1018
if deprecated_passed(init):
1019
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
1020
"deprecated as of bzr 0.8. Please use Branch.create().",
1024
# this is slower than before deprecation, oh well never mind.
1025
# -> its deprecated.
1026
self._initialize(transport.base)
1027
self._check_format(_format)
1028
if deprecated_passed(relax_version_check):
1029
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
1030
"relax_version_check parameter is deprecated as of bzr 0.8. "
1031
"Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
1035
if (not relax_version_check
1036
and not self._format.is_supported()):
1037
raise errors.UnsupportedFormatError(format=fmt)
1038
if deprecated_passed(transport):
1039
warn("BzrBranch.__init__(transport=XXX...): The transport "
1040
"parameter is deprecated as of bzr 0.8. "
1041
"Please use Branch.open, or bzrdir.open_branch().",
1044
self.repository = _repository
1047
return '%s(%r)' % (self.__class__.__name__, self.base)
1051
def _get_base(self):
1054
base = property(_get_base, doc="The URL for the root of this branch.")
1056
def _finish_transaction(self):
1057
"""Exit the current transaction."""
1058
return self.control_files._finish_transaction()
1060
def get_transaction(self):
1061
"""Return the current active transaction.
1063
If no transaction is active, this returns a passthrough object
1064
for which all data is immediately flushed and no caching happens.
1066
# this is an explicit function so that we can do tricky stuff
1067
# when the storage in rev_storage is elsewhere.
1068
# we probably need to hook the two 'lock a location' and
1069
# 'have a transaction' together more delicately, so that
1070
# we can have two locks (branch and storage) and one transaction
1071
# ... and finishing the transaction unlocks both, but unlocking
1072
# does not. - RBC 20051121
1073
return self.control_files.get_transaction()
1075
def _set_transaction(self, transaction):
1076
"""Set a new active transaction."""
1077
return self.control_files._set_transaction(transaction)
1079
def abspath(self, name):
1080
"""See Branch.abspath."""
1081
return self.control_files._transport.abspath(name)
1083
def _check_format(self, format):
1084
"""Identify the branch format if needed.
1086
The format is stored as a reference to the format object in
1087
self._format for code that needs to check it later.
1089
The format parameter is either None or the branch format class
1090
used to open this branch.
1092
FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
1095
format = BranchFormat.find_format(self.bzrdir)
1096
self._format = format
1097
mutter("got branch format %s", self._format)
1100
def get_root_id(self):
1101
"""See Branch.get_root_id."""
1102
tree = self.repository.revision_tree(self.last_revision())
1103
return tree.inventory.root.file_id
1105
def is_locked(self):
1106
return self.control_files.is_locked()
1108
def lock_write(self):
1109
self.repository.lock_write()
1111
self.control_files.lock_write()
1113
self.repository.unlock()
1116
def lock_read(self):
1117
self.repository.lock_read()
1119
self.control_files.lock_read()
1121
self.repository.unlock()
1125
# TODO: test for failed two phase locks. This is known broken.
1127
self.control_files.unlock()
1129
self.repository.unlock()
1131
def peek_lock_mode(self):
1132
if self.control_files._lock_count == 0:
1135
return self.control_files._lock_mode
1137
def get_physical_lock_status(self):
1138
return self.control_files.get_physical_lock_status()
1141
def print_file(self, file, revision_id):
1142
"""See Branch.print_file."""
1143
return self.repository.print_file(file, revision_id)
1146
def append_revision(self, *revision_ids):
1147
"""See Branch.append_revision."""
1148
for revision_id in revision_ids:
1149
_mod_revision.check_not_reserved_id(revision_id)
1150
mutter("add {%s} to revision-history" % revision_id)
1151
rev_history = self.revision_history()
1152
rev_history.extend(revision_ids)
1153
self.set_revision_history(rev_history)
1156
def set_revision_history(self, rev_history):
1157
"""See Branch.set_revision_history."""
1158
self.control_files.put_utf8(
1159
'revision-history', '\n'.join(rev_history))
1160
transaction = self.get_transaction()
1161
history = transaction.map.find_revision_history()
1162
if history is not None:
1163
# update the revision history in the identity map.
1164
history[:] = list(rev_history)
1165
# this call is disabled because revision_history is
1166
# not really an object yet, and the transaction is for objects.
1167
# transaction.register_dirty(history)
1169
transaction.map.add_revision_history(rev_history)
1170
# this call is disabled because revision_history is
1171
# not really an object yet, and the transaction is for objects.
1172
# transaction.register_clean(history)
1173
for hook in Branch.hooks['set_rh']:
1174
hook(self, rev_history)
1177
def revision_history(self):
1178
"""See Branch.revision_history."""
1179
transaction = self.get_transaction()
1180
history = transaction.map.find_revision_history()
1181
if history is not None:
1182
# mutter("cache hit for revision-history in %s", self)
1183
return list(history)
1184
decode_utf8 = cache_utf8.decode
1185
history = [decode_utf8(l.rstrip('\r\n')) for l in
1186
self.control_files.get('revision-history').readlines()]
1187
transaction.map.add_revision_history(history)
1188
# this call is disabled because revision_history is
1189
# not really an object yet, and the transaction is for objects.
1190
# transaction.register_clean(history, precious=True)
1191
return list(history)
1194
def generate_revision_history(self, revision_id, last_rev=None,
1196
"""Create a new revision history that will finish with revision_id.
1198
:param revision_id: the new tip to use.
1199
:param last_rev: The previous last_revision. If not None, then this
1200
must be a ancestory of revision_id, or DivergedBranches is raised.
1201
:param other_branch: The other branch that DivergedBranches should
1202
raise with respect to.
1204
# stop_revision must be a descendant of last_revision
1205
stop_graph = self.repository.get_revision_graph(revision_id)
1206
if last_rev is not None and last_rev not in stop_graph:
1207
# our previous tip is not merged into stop_revision
1208
raise errors.DivergedBranches(self, other_branch)
1209
# make a new revision history from the graph
1210
current_rev_id = revision_id
1212
while current_rev_id not in (None, _mod_revision.NULL_REVISION):
1213
new_history.append(current_rev_id)
1214
current_rev_id_parents = stop_graph[current_rev_id]
1216
current_rev_id = current_rev_id_parents[0]
1218
current_rev_id = None
1219
new_history.reverse()
1220
self.set_revision_history(new_history)
1223
def update_revisions(self, other, stop_revision=None):
1224
"""See Branch.update_revisions."""
1227
if stop_revision is None:
1228
stop_revision = other.last_revision()
1229
if stop_revision is None:
1230
# if there are no commits, we're done.
1232
# whats the current last revision, before we fetch [and change it
1234
last_rev = self.last_revision()
1235
# we fetch here regardless of whether we need to so that we pickup
1237
self.fetch(other, stop_revision)
1238
my_ancestry = self.repository.get_ancestry(last_rev)
1239
if stop_revision in my_ancestry:
1240
# last_revision is a descendant of stop_revision
1242
self.generate_revision_history(stop_revision, last_rev=last_rev,
1247
def basis_tree(self):
1248
"""See Branch.basis_tree."""
1249
return self.repository.revision_tree(self.last_revision())
1251
@deprecated_method(zero_eight)
1252
def working_tree(self):
1253
"""Create a Working tree object for this branch."""
1255
from bzrlib.transport.local import LocalTransport
1256
if (self.base.find('://') != -1 or
1257
not isinstance(self._transport, LocalTransport)):
1258
raise NoWorkingTree(self.base)
1259
return self.bzrdir.open_workingtree()
1262
def pull(self, source, overwrite=False, stop_revision=None):
1263
"""See Branch.pull."""
1266
old_count = self.last_revision_info()[0]
1268
self.update_revisions(source, stop_revision)
1269
except DivergedBranches:
1273
self.set_revision_history(source.revision_history())
1274
new_count = self.last_revision_info()[0]
1275
return new_count - old_count
1279
def get_parent(self):
1280
"""See Branch.get_parent."""
1282
_locs = ['parent', 'pull', 'x-pull']
1283
assert self.base[-1] == '/'
1286
parent = self.control_files.get(l).read().strip('\n')
1289
# This is an old-format absolute path to a local branch
1290
# turn it into a url
1291
if parent.startswith('/'):
1292
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1294
return urlutils.join(self.base[:-1], parent)
1295
except errors.InvalidURLJoin, e:
1296
raise errors.InaccessibleParent(parent, self.base)
1299
def get_push_location(self):
1300
"""See Branch.get_push_location."""
1301
push_loc = self.get_config().get_user_option('push_location')
1304
def set_push_location(self, location):
1305
"""See Branch.set_push_location."""
1306
self.get_config().set_user_option(
1307
'push_location', location,
1308
store=_mod_config.STORE_LOCATION_NORECURSE)
1311
def set_parent(self, url):
1312
"""See Branch.set_parent."""
1313
# TODO: Maybe delete old location files?
1314
# URLs should never be unicode, even on the local fs,
1315
# FIXUP this and get_parent in a future branch format bump:
1316
# read and rewrite the file, and have the new format code read
1317
# using .get not .get_utf8. RBC 20060125
1319
self.control_files._transport.delete('parent')
1321
if isinstance(url, unicode):
1323
url = url.encode('ascii')
1324
except UnicodeEncodeError:
1325
raise bzrlib.errors.InvalidURL(url,
1326
"Urls must be 7-bit ascii, "
1327
"use bzrlib.urlutils.escape")
1329
url = urlutils.relative_url(self.base, url)
1330
self.control_files.put('parent', StringIO(url + '\n'))
1332
@deprecated_function(zero_nine)
1333
def tree_config(self):
1334
"""DEPRECATED; call get_config instead.
1335
TreeConfig has become part of BranchConfig."""
1336
return TreeConfig(self)
1339
class BzrBranch5(BzrBranch):
1340
"""A format 5 branch. This supports new features over plan branches.
1342
It has support for a master_branch which is the data for bound branches.
1350
super(BzrBranch5, self).__init__(_format=_format,
1351
_control_files=_control_files,
1353
_repository=_repository)
1356
def pull(self, source, overwrite=False, stop_revision=None):
1357
"""Updates branch.pull to be bound branch aware."""
1358
bound_location = self.get_bound_location()
1359
if source.base != bound_location:
1360
# not pulling from master, so we need to update master.
1361
master_branch = self.get_master_branch()
1363
master_branch.pull(source)
1364
source = master_branch
1365
return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
1367
def get_bound_location(self):
1369
return self.control_files.get_utf8('bound').read()[:-1]
1370
except errors.NoSuchFile:
1374
def get_master_branch(self):
1375
"""Return the branch we are bound to.
1377
:return: Either a Branch, or None
1379
This could memoise the branch, but if thats done
1380
it must be revalidated on each new lock.
1381
So for now we just don't memoise it.
1382
# RBC 20060304 review this decision.
1384
bound_loc = self.get_bound_location()
1388
return Branch.open(bound_loc)
1389
except (errors.NotBranchError, errors.ConnectionError), e:
1390
raise errors.BoundBranchConnectionFailure(
1394
def set_bound_location(self, location):
1395
"""Set the target where this branch is bound to.
1397
:param location: URL to the target branch
1400
self.control_files.put_utf8('bound', location+'\n')
1403
self.control_files._transport.delete('bound')
1409
def bind(self, other):
1410
"""Bind this branch to the branch other.
1412
This does not push or pull data between the branches, though it does
1413
check for divergence to raise an error when the branches are not
1414
either the same, or one a prefix of the other. That behaviour may not
1415
be useful, so that check may be removed in future.
1417
:param other: The branch to bind to
1420
# TODO: jam 20051230 Consider checking if the target is bound
1421
# It is debatable whether you should be able to bind to
1422
# a branch which is itself bound.
1423
# Committing is obviously forbidden,
1424
# but binding itself may not be.
1425
# Since we *have* to check at commit time, we don't
1426
# *need* to check here
1428
# we want to raise diverged if:
1429
# last_rev is not in the other_last_rev history, AND
1430
# other_last_rev is not in our history, and do it without pulling
1432
last_rev = self.last_revision()
1433
if last_rev is not None:
1436
other_last_rev = other.last_revision()
1437
if other_last_rev is not None:
1438
# neither branch is new, we have to do some work to
1439
# ascertain diversion.
1440
remote_graph = other.repository.get_revision_graph(
1442
local_graph = self.repository.get_revision_graph(last_rev)
1443
if (last_rev not in remote_graph and
1444
other_last_rev not in local_graph):
1445
raise errors.DivergedBranches(self, other)
1448
self.set_bound_location(other.base)
1452
"""If bound, unbind"""
1453
return self.set_bound_location(None)
1457
"""Synchronise this branch with the master branch if any.
1459
:return: None or the last_revision that was pivoted out during the
1462
master = self.get_master_branch()
1463
if master is not None:
1464
old_tip = self.last_revision()
1465
self.pull(master, overwrite=True)
1466
if old_tip in self.repository.get_ancestry(self.last_revision()):
1472
class BranchTestProviderAdapter(object):
1473
"""A tool to generate a suite testing multiple branch formats at once.
1475
This is done by copying the test once for each transport and injecting
1476
the transport_server, transport_readonly_server, and branch_format
1477
classes into each copy. Each copy is also given a new id() to make it
1481
def __init__(self, transport_server, transport_readonly_server, formats):
1482
self._transport_server = transport_server
1483
self._transport_readonly_server = transport_readonly_server
1484
self._formats = formats
1486
def adapt(self, test):
1487
result = TestSuite()
1488
for branch_format, bzrdir_format in self._formats:
1489
new_test = deepcopy(test)
1490
new_test.transport_server = self._transport_server
1491
new_test.transport_readonly_server = self._transport_readonly_server
1492
new_test.bzrdir_format = bzrdir_format
1493
new_test.branch_format = branch_format
1494
def make_new_test_id():
1495
new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1496
return lambda: new_id
1497
new_test.id = make_new_test_id()
1498
result.addTest(new_test)
1502
class BranchCheckResult(object):
1503
"""Results of checking branch consistency.
1508
def __init__(self, branch):
1509
self.branch = branch
1511
def report_results(self, verbose):
1512
"""Report the check results via trace.note.
1514
:param verbose: Requests more detailed display of what was checked,
1517
note('checked branch %s format %s',
1519
self.branch._format)
1522
######################################################################
1526
@deprecated_function(zero_eight)
1527
def is_control_file(*args, **kwargs):
1528
"""See bzrlib.workingtree.is_control_file."""
1529
from bzrlib import workingtree
1530
return workingtree.is_control_file(*args, **kwargs)