1
# Copyright (C) 2005, 2006, 2007 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
43
from bzrlib.tag import (
49
from bzrlib.decorators import needs_read_lock, needs_write_lock
50
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches,
51
HistoryMissing, InvalidRevisionId,
52
InvalidRevisionNumber, LockError, NoSuchFile,
53
NoSuchRevision, NoWorkingTree, NotVersionedError,
54
NotBranchError, UninitializableFormat,
55
UnlistableStore, UnlistableBranch,
57
from bzrlib.hooks import Hooks
58
from bzrlib.symbol_versioning import (deprecated_function,
62
zero_eight, zero_nine,
64
from bzrlib.trace import mutter, note
67
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
68
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
69
BZR_BRANCH_FORMAT_6 = "Bazaar Branch Format 6 (bzr 0.15)\n"
72
# TODO: Maybe include checks for common corruption of newlines, etc?
74
# TODO: Some operations like log might retrieve the same revisions
75
# repeatedly to calculate deltas. We could perhaps have a weakref
76
# cache in memory to make this faster. In general anything can be
77
# cached in memory between lock and unlock operations. .. nb thats
78
# what the transaction identity map provides
81
######################################################################
85
"""Branch holding a history of revisions.
88
Base directory/url of the branch.
90
hooks: An instance of BranchHooks.
92
# this is really an instance variable - FIXME move it there
96
# override this to set the strategy for storing tags
98
return DisabledTags(self)
100
def __init__(self, *ignored, **ignored_too):
101
self.tags = self._make_tags()
103
def break_lock(self):
104
"""Break a lock if one is present from another instance.
106
Uses the ui factory to ask for confirmation if the lock may be from
109
This will probe the repository for its lock as well.
111
self.control_files.break_lock()
112
self.repository.break_lock()
113
master = self.get_master_branch()
114
if master is not None:
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 branch rooted at base.
127
For instance, if the branch is at URL/.bzr/branch,
128
Branch.open(URL) -> a Branch 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
@deprecated_function(zero_eight)
159
def setup_caching(self, cache_root):
160
"""Subclasses that care about caching should override this, and set
161
up cached stores located under cache_root.
163
NOTE: This is unused.
167
def get_config(self):
168
return BranchConfig(self)
171
return self.get_config().get_nickname()
173
def _set_nick(self, nick):
174
self.get_config().set_user_option('nickname', nick)
176
nick = property(_get_nick, _set_nick)
179
raise NotImplementedError(self.is_locked)
181
def lock_write(self):
182
raise NotImplementedError(self.lock_write)
185
raise NotImplementedError(self.lock_read)
188
raise NotImplementedError(self.unlock)
190
def peek_lock_mode(self):
191
"""Return lock mode for the Branch: 'r', 'w' or None"""
192
raise NotImplementedError(self.peek_lock_mode)
194
def get_physical_lock_status(self):
195
raise NotImplementedError(self.get_physical_lock_status)
197
def abspath(self, name):
198
"""Return absolute filename for something in the branch
200
XXX: Robert Collins 20051017 what is this used for? why is it a branch
201
method and not a tree method.
203
raise NotImplementedError(self.abspath)
205
def bind(self, other):
206
"""Bind the local branch the other branch.
208
:param other: The branch to bind to
211
raise errors.UpgradeRequired(self.base)
214
def fetch(self, from_branch, last_revision=None, pb=None):
215
"""Copy revisions from from_branch into this branch.
217
:param from_branch: Where to copy from.
218
:param last_revision: What revision to stop at (None for at the end
220
:param pb: An optional progress bar to use.
222
Returns the copied revision count and the failed revisions in a tuple:
225
if self.base == from_branch.base:
228
nested_pb = ui.ui_factory.nested_progress_bar()
233
from_branch.lock_read()
235
if last_revision is None:
236
pb.update('get source history')
237
last_revision = from_branch.last_revision()
238
if last_revision is None:
239
last_revision = _mod_revision.NULL_REVISION
240
return self.repository.fetch(from_branch.repository,
241
revision_id=last_revision,
244
if nested_pb is not None:
248
def get_bound_location(self):
249
"""Return the URL of the branch we are bound to.
251
Older format branches cannot bind, please be sure to use a metadir
256
def get_old_bound_location(self):
257
"""Return the URL of the branch we used to be bound to
259
raise errors.UpgradeRequired(self.base)
261
def get_commit_builder(self, parents, config=None, timestamp=None,
262
timezone=None, committer=None, revprops=None,
264
"""Obtain a CommitBuilder for this branch.
266
:param parents: Revision ids of the parents of the new revision.
267
:param config: Optional configuration to use.
268
:param timestamp: Optional timestamp recorded for commit.
269
:param timezone: Optional timezone for timestamp.
270
:param committer: Optional committer to set for commit.
271
:param revprops: Optional dictionary of revision properties.
272
:param revision_id: Optional revision id.
276
config = self.get_config()
278
return self.repository.get_commit_builder(self, parents, config,
279
timestamp, timezone, committer, revprops, revision_id)
281
def get_master_branch(self):
282
"""Return the branch we are bound to.
284
:return: Either a Branch, or None
288
def get_revision_delta(self, revno):
289
"""Return the delta for one revision.
291
The delta is relative to its mainline predecessor, or the
292
empty tree for revision 1.
294
assert isinstance(revno, int)
295
rh = self.revision_history()
296
if not (1 <= revno <= len(rh)):
297
raise InvalidRevisionNumber(revno)
298
return self.repository.get_revision_delta(rh[revno-1])
300
def get_root_id(self):
301
"""Return the id of this branches root"""
302
raise NotImplementedError(self.get_root_id)
304
def print_file(self, file, revision_id):
305
"""Print `file` to stdout."""
306
raise NotImplementedError(self.print_file)
308
def append_revision(self, *revision_ids):
309
raise NotImplementedError(self.append_revision)
311
def set_revision_history(self, rev_history):
312
raise NotImplementedError(self.set_revision_history)
314
def revision_history(self):
315
"""Return sequence of revision hashes on to this branch."""
316
raise NotImplementedError(self.revision_history)
319
"""Return current revision number for this branch.
321
That is equivalent to the number of revisions committed to
324
return len(self.revision_history())
327
"""Older format branches cannot bind or unbind."""
328
raise errors.UpgradeRequired(self.base)
330
def set_append_revisions_only(self, enabled):
331
"""Older format branches are never restricted to append-only"""
332
raise errors.UpgradeRequired(self.base)
334
def last_revision(self):
335
"""Return last revision id, or None"""
336
ph = self.revision_history()
342
def last_revision_info(self):
343
"""Return information about the last revision.
345
:return: A tuple (revno, last_revision_id).
347
rh = self.revision_history()
350
return (revno, rh[-1])
352
return (0, _mod_revision.NULL_REVISION)
354
def missing_revisions(self, other, stop_revision=None):
355
"""Return a list of new revisions that would perfectly fit.
357
If self and other have not diverged, return a list of the revisions
358
present in other, but missing from self.
360
self_history = self.revision_history()
361
self_len = len(self_history)
362
other_history = other.revision_history()
363
other_len = len(other_history)
364
common_index = min(self_len, other_len) -1
365
if common_index >= 0 and \
366
self_history[common_index] != other_history[common_index]:
367
raise DivergedBranches(self, other)
369
if stop_revision is None:
370
stop_revision = other_len
372
assert isinstance(stop_revision, int)
373
if stop_revision > other_len:
374
raise errors.NoSuchRevision(self, stop_revision)
375
return other_history[self_len:stop_revision]
377
def update_revisions(self, other, stop_revision=None):
378
"""Pull in new perfect-fit revisions.
380
:param other: Another Branch to pull from
381
:param stop_revision: Updated until the given revision
384
raise NotImplementedError(self.update_revisions)
386
def revision_id_to_revno(self, revision_id):
387
"""Given a revision id, return its revno"""
388
if revision_id is None:
390
revision_id = osutils.safe_revision_id(revision_id)
391
history = self.revision_history()
393
return history.index(revision_id) + 1
395
raise bzrlib.errors.NoSuchRevision(self, revision_id)
397
def get_rev_id(self, revno, history=None):
398
"""Find the revision id of the specified revno."""
402
history = self.revision_history()
403
if revno <= 0 or revno > len(history):
404
raise bzrlib.errors.NoSuchRevision(self, revno)
405
return history[revno - 1]
407
def pull(self, source, overwrite=False, stop_revision=None):
408
"""Mirror source into this branch.
410
This branch is considered to be 'local', having low latency.
412
:returns: PullResult instance
414
raise NotImplementedError(self.pull)
416
def push(self, target, overwrite=False, stop_revision=None):
417
"""Mirror this branch into target.
419
This branch is considered to be 'local', having low latency.
421
raise NotImplementedError(self.push)
423
def basis_tree(self):
424
"""Return `Tree` object for last revision."""
425
return self.repository.revision_tree(self.last_revision())
427
def rename_one(self, from_rel, to_rel):
430
This can change the directory or the filename or both.
432
raise NotImplementedError(self.rename_one)
434
def move(self, from_paths, to_name):
437
to_name must exist as a versioned directory.
439
If to_name exists and is a directory, the files are moved into
440
it, keeping their old names. If it is a directory,
442
Note that to_name is only the last component of the new name;
443
this doesn't change the directory.
445
This returns a list of (from_path, to_path) pairs for each
448
raise NotImplementedError(self.move)
450
def get_parent(self):
451
"""Return the parent location of the branch.
453
This is the default location for push/pull/missing. The usual
454
pattern is that the user can override it by specifying a
457
raise NotImplementedError(self.get_parent)
459
def _set_config_location(self, name, url, config=None,
460
make_relative=False):
462
config = self.get_config()
466
url = urlutils.relative_url(self.base, url)
467
config.set_user_option(name, url)
469
def _get_config_location(self, name, config=None):
471
config = self.get_config()
472
location = config.get_user_option(name)
477
def get_submit_branch(self):
478
"""Return the submit location of the branch.
480
This is the default location for bundle. The usual
481
pattern is that the user can override it by specifying a
484
return self.get_config().get_user_option('submit_branch')
486
def set_submit_branch(self, location):
487
"""Return the submit location of the branch.
489
This is the default location for bundle. The usual
490
pattern is that the user can override it by specifying a
493
self.get_config().set_user_option('submit_branch', location)
495
def get_public_branch(self):
496
"""Return the public location of the branch.
498
This is is used by merge directives.
500
return self._get_config_location('public_branch')
502
def set_public_branch(self, location):
503
"""Return the submit location of the branch.
505
This is the default location for bundle. The usual
506
pattern is that the user can override it by specifying a
509
self._set_config_location('public_branch', location)
511
def get_push_location(self):
512
"""Return the None or the location to push this branch to."""
513
raise NotImplementedError(self.get_push_location)
515
def set_push_location(self, location):
516
"""Set a new push location for this branch."""
517
raise NotImplementedError(self.set_push_location)
519
def set_parent(self, url):
520
raise NotImplementedError(self.set_parent)
524
"""Synchronise this branch with the master branch if any.
526
:return: None or the last_revision pivoted out during the update.
530
def check_revno(self, revno):
532
Check whether a revno corresponds to any revision.
533
Zero (the NULL revision) is considered valid.
536
self.check_real_revno(revno)
538
def check_real_revno(self, revno):
540
Check whether a revno corresponds to a real revision.
541
Zero (the NULL revision) is considered invalid
543
if revno < 1 or revno > self.revno():
544
raise InvalidRevisionNumber(revno)
547
def clone(self, *args, **kwargs):
548
"""Clone this branch into to_bzrdir preserving all semantic values.
550
revision_id: if not None, the revision history in the new branch will
551
be truncated to end with revision_id.
553
# for API compatibility, until 0.8 releases we provide the old api:
554
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
555
# after 0.8 releases, the *args and **kwargs should be changed:
556
# def clone(self, to_bzrdir, revision_id=None):
557
if (kwargs.get('to_location', None) or
558
kwargs.get('revision', None) or
559
kwargs.get('basis_branch', None) or
560
(len(args) and isinstance(args[0], basestring))):
561
# backwards compatibility api:
562
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
563
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
566
basis_branch = args[2]
568
basis_branch = kwargs.get('basis_branch', None)
570
basis = basis_branch.bzrdir
575
revision_id = args[1]
577
revision_id = kwargs.get('revision', None)
582
# no default to raise if not provided.
583
url = kwargs.get('to_location')
584
return self.bzrdir.clone(url,
585
revision_id=revision_id,
586
basis=basis).open_branch()
588
# generate args by hand
590
revision_id = args[1]
592
revision_id = kwargs.get('revision_id', None)
596
# no default to raise if not provided.
597
to_bzrdir = kwargs.get('to_bzrdir')
598
result = self._format.initialize(to_bzrdir)
599
self.copy_content_into(result, revision_id=revision_id)
603
def sprout(self, to_bzrdir, revision_id=None):
604
"""Create a new line of development from the branch, into to_bzrdir.
606
revision_id: if not None, the revision history in the new branch will
607
be truncated to end with revision_id.
609
result = self._format.initialize(to_bzrdir)
610
self.copy_content_into(result, revision_id=revision_id)
611
result.set_parent(self.bzrdir.root_transport.base)
614
def _synchronize_history(self, destination, revision_id):
615
"""Synchronize last revision and revision history between branches.
617
This version is most efficient when the destination is also a
618
BzrBranch5, but works for BzrBranch6 as long as the revision
619
history is the true lefthand parent history, and all of the revisions
620
are in the destination's repository. If not, set_revision_history
623
:param destination: The branch to copy the history into
624
:param revision_id: The revision-id to truncate history at. May
625
be None to copy complete history.
627
new_history = self.revision_history()
628
if revision_id is not None:
629
revision_id = osutils.safe_revision_id(revision_id)
631
new_history = new_history[:new_history.index(revision_id) + 1]
633
rev = self.repository.get_revision(revision_id)
634
new_history = rev.get_history(self.repository)[1:]
635
destination.set_revision_history(new_history)
638
def copy_content_into(self, destination, revision_id=None):
639
"""Copy the content of self into destination.
641
revision_id: if not None, the revision history in the new branch will
642
be truncated to end with revision_id.
644
self._synchronize_history(destination, revision_id)
646
parent = self.get_parent()
647
except errors.InaccessibleParent, e:
648
mutter('parent was not accessible to copy: %s', e)
651
destination.set_parent(parent)
652
self.tags.merge_to(destination.tags)
656
"""Check consistency of the branch.
658
In particular this checks that revisions given in the revision-history
659
do actually match up in the revision graph, and that they're all
660
present in the repository.
662
Callers will typically also want to check the repository.
664
:return: A BranchCheckResult.
666
mainline_parent_id = None
667
for revision_id in self.revision_history():
669
revision = self.repository.get_revision(revision_id)
670
except errors.NoSuchRevision, e:
671
raise errors.BzrCheckError("mainline revision {%s} not in repository"
673
# In general the first entry on the revision history has no parents.
674
# But it's not illegal for it to have parents listed; this can happen
675
# in imports from Arch when the parents weren't reachable.
676
if mainline_parent_id is not None:
677
if mainline_parent_id not in revision.parent_ids:
678
raise errors.BzrCheckError("previous revision {%s} not listed among "
680
% (mainline_parent_id, revision_id))
681
mainline_parent_id = revision_id
682
return BranchCheckResult(self)
684
def _get_checkout_format(self):
685
"""Return the most suitable metadir for a checkout of this branch.
686
Weaves are used if this branch's repostory uses weaves.
688
if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
689
from bzrlib.repofmt import weaverepo
690
format = bzrdir.BzrDirMetaFormat1()
691
format.repository_format = weaverepo.RepositoryFormat7()
693
format = self.repository.bzrdir.checkout_metadir()
694
format.branch_format = self._format
697
def create_checkout(self, to_location, revision_id=None,
699
"""Create a checkout of a branch.
701
:param to_location: The url to produce the checkout at
702
:param revision_id: The revision to check out
703
:param lightweight: If True, produce a lightweight checkout, otherwise,
704
produce a bound branch (heavyweight checkout)
705
:return: The tree of the created checkout
707
t = transport.get_transport(to_location)
710
except errors.FileExists:
713
format = self._get_checkout_format()
714
checkout = format.initialize_on_transport(t)
715
BranchReferenceFormat().initialize(checkout, self)
717
format = self._get_checkout_format()
718
checkout_branch = bzrdir.BzrDir.create_branch_convenience(
719
to_location, force_new_tree=False, format=format)
720
checkout = checkout_branch.bzrdir
721
checkout_branch.bind(self)
722
# pull up to the specified revision_id to set the initial
723
# branch tip correctly, and seed it with history.
724
checkout_branch.pull(self, stop_revision=revision_id)
725
tree = checkout.create_workingtree(revision_id)
726
basis_tree = tree.basis_tree()
727
basis_tree.lock_read()
729
for path, file_id in basis_tree.iter_references():
730
reference_parent = self.reference_parent(file_id, path)
731
reference_parent.create_checkout(tree.abspath(path),
732
basis_tree.get_reference_revision(file_id, path),
738
def reference_parent(self, file_id, path):
739
"""Return the parent branch for a tree-reference file_id
740
:param file_id: The file_id of the tree reference
741
:param path: The path of the file_id in the tree
742
:return: A branch associated with the file_id
744
# FIXME should provide multiple branches, based on config
745
return Branch.open(self.bzrdir.root_transport.clone(path).base)
747
def supports_tags(self):
748
return self._format.supports_tags()
751
class BranchFormat(object):
752
"""An encapsulation of the initialization and open routines for a format.
754
Formats provide three things:
755
* An initialization routine,
759
Formats are placed in an dict by their format string for reference
760
during branch opening. Its not required that these be instances, they
761
can be classes themselves with class methods - it simply depends on
762
whether state is needed for a given format or not.
764
Once a format is deprecated, just deprecate the initialize and open
765
methods on the format class. Do not deprecate the object, as the
766
object will be created every time regardless.
769
_default_format = None
770
"""The default format used for new branches."""
773
"""The known formats."""
776
def find_format(klass, a_bzrdir):
777
"""Return the format for the branch object in a_bzrdir."""
779
transport = a_bzrdir.get_branch_transport(None)
780
format_string = transport.get("format").read()
781
return klass._formats[format_string]
783
raise NotBranchError(path=transport.base)
785
raise errors.UnknownFormatError(format=format_string)
788
def get_default_format(klass):
789
"""Return the current default format."""
790
return klass._default_format
792
def get_format_string(self):
793
"""Return the ASCII format string that identifies this format."""
794
raise NotImplementedError(self.get_format_string)
796
def get_format_description(self):
797
"""Return the short format description for this format."""
798
raise NotImplementedError(self.get_format_description)
800
def _initialize_helper(self, a_bzrdir, utf8_files, lock_type='metadir',
802
"""Initialize a branch in a bzrdir, with specified files
804
:param a_bzrdir: The bzrdir to initialize the branch in
805
:param utf8_files: The files to create as a list of
806
(filename, content) tuples
807
:param set_format: If True, set the format with
808
self.get_format_string. (BzrBranch4 has its format set
810
:return: a branch in this format
812
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
813
branch_transport = a_bzrdir.get_branch_transport(self)
815
'metadir': ('lock', lockdir.LockDir),
816
'branch4': ('branch-lock', lockable_files.TransportLock),
818
lock_name, lock_class = lock_map[lock_type]
819
control_files = lockable_files.LockableFiles(branch_transport,
820
lock_name, lock_class)
821
control_files.create_lock()
822
control_files.lock_write()
824
control_files.put_utf8('format', self.get_format_string())
826
for file, content in utf8_files:
827
control_files.put_utf8(file, content)
829
control_files.unlock()
830
return self.open(a_bzrdir, _found=True)
832
def initialize(self, a_bzrdir):
833
"""Create a branch of this format in a_bzrdir."""
834
raise NotImplementedError(self.initialize)
836
def is_supported(self):
837
"""Is this format supported?
839
Supported formats can be initialized and opened.
840
Unsupported formats may not support initialization or committing or
841
some other features depending on the reason for not being supported.
845
def open(self, a_bzrdir, _found=False):
846
"""Return the branch object for a_bzrdir
848
_found is a private parameter, do not use it. It is used to indicate
849
if format probing has already be done.
851
raise NotImplementedError(self.open)
854
def register_format(klass, format):
855
klass._formats[format.get_format_string()] = format
858
def set_default_format(klass, format):
859
klass._default_format = format
862
def unregister_format(klass, format):
863
assert klass._formats[format.get_format_string()] is format
864
del klass._formats[format.get_format_string()]
867
return self.get_format_string().rstrip()
869
def supports_tags(self):
870
"""True if this format supports tags stored in the branch"""
871
return False # by default
873
# XXX: Probably doesn't really belong here -- mbp 20070212
874
def _initialize_control_files(self, a_bzrdir, utf8_files, lock_filename,
876
branch_transport = a_bzrdir.get_branch_transport(self)
877
control_files = lockable_files.LockableFiles(branch_transport,
878
lock_filename, lock_class)
879
control_files.create_lock()
880
control_files.lock_write()
882
for filename, content in utf8_files:
883
control_files.put_utf8(filename, content)
885
control_files.unlock()
888
class BranchHooks(Hooks):
889
"""A dictionary mapping hook name to a list of callables for branch hooks.
891
e.g. ['set_rh'] Is the list of items to be called when the
892
set_revision_history function is invoked.
896
"""Create the default hooks.
898
These are all empty initially, because by default nothing should get
902
# Introduced in 0.15:
903
# invoked whenever the revision history has been set
904
# with set_revision_history. The api signature is
905
# (branch, revision_history), and the branch will
908
# invoked after a push operation completes.
909
# the api signature is
911
# containing the members
912
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
913
# where local is the local branch or None, master is the target
914
# master branch, and the rest should be self explanatory. The source
915
# is read locked and the target branches write locked. Source will
916
# be the local low-latency branch.
917
self['post_push'] = []
918
# invoked after a pull operation completes.
919
# the api signature is
921
# containing the members
922
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
923
# where local is the local branch or None, master is the target
924
# master branch, and the rest should be self explanatory. The source
925
# is read locked and the target branches write locked. The local
926
# branch is the low-latency branch.
927
self['post_pull'] = []
928
# invoked after a commit operation completes.
929
# the api signature is
930
# (local, master, old_revno, old_revid, new_revno, new_revid)
931
# old_revid is NULL_REVISION for the first commit to a branch.
932
self['post_commit'] = []
933
# invoked after a uncommit operation completes.
934
# the api signature is
935
# (local, master, old_revno, old_revid, new_revno, new_revid) where
936
# local is the local branch or None, master is the target branch,
937
# and an empty branch recieves new_revno of 0, new_revid of None.
938
self['post_uncommit'] = []
941
# install the default hooks into the Branch class.
942
Branch.hooks = BranchHooks()
945
class BzrBranchFormat4(BranchFormat):
946
"""Bzr branch format 4.
949
- a revision-history file.
950
- a branch-lock lock file [ to be shared with the bzrdir ]
953
def get_format_description(self):
954
"""See BranchFormat.get_format_description()."""
955
return "Branch format 4"
957
def initialize(self, a_bzrdir):
958
"""Create a branch of this format in a_bzrdir."""
959
utf8_files = [('revision-history', ''),
962
return self._initialize_helper(a_bzrdir, utf8_files,
963
lock_type='branch4', set_format=False)
966
super(BzrBranchFormat4, self).__init__()
967
self._matchingbzrdir = bzrdir.BzrDirFormat6()
969
def open(self, a_bzrdir, _found=False):
970
"""Return the branch object for a_bzrdir
972
_found is a private parameter, do not use it. It is used to indicate
973
if format probing has already be done.
976
# we are being called directly and must probe.
977
raise NotImplementedError
978
return BzrBranch(_format=self,
979
_control_files=a_bzrdir._control_files,
981
_repository=a_bzrdir.open_repository())
984
return "Bazaar-NG branch format 4"
987
class BzrBranchFormat5(BranchFormat):
988
"""Bzr branch format 5.
991
- a revision-history file.
993
- a lock dir guarding the branch itself
994
- all of this stored in a branch/ subdirectory
995
- works with shared repositories.
997
This format is new in bzr 0.8.
1000
def get_format_string(self):
1001
"""See BranchFormat.get_format_string()."""
1002
return "Bazaar-NG branch format 5\n"
1004
def get_format_description(self):
1005
"""See BranchFormat.get_format_description()."""
1006
return "Branch format 5"
1008
def initialize(self, a_bzrdir):
1009
"""Create a branch of this format in a_bzrdir."""
1010
utf8_files = [('revision-history', ''),
1011
('branch-name', ''),
1013
return self._initialize_helper(a_bzrdir, utf8_files)
1016
super(BzrBranchFormat5, self).__init__()
1017
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1019
def open(self, a_bzrdir, _found=False):
1020
"""Return the branch object for a_bzrdir
1022
_found is a private parameter, do not use it. It is used to indicate
1023
if format probing has already be done.
1026
format = BranchFormat.find_format(a_bzrdir)
1027
assert format.__class__ == self.__class__
1028
transport = a_bzrdir.get_branch_transport(None)
1029
control_files = lockable_files.LockableFiles(transport, 'lock',
1031
return BzrBranch5(_format=self,
1032
_control_files=control_files,
1034
_repository=a_bzrdir.find_repository())
1037
class BzrBranchFormat6(BzrBranchFormat5):
1038
"""Branch format with last-revision
1040
Unlike previous formats, this has no explicit revision history. Instead,
1041
this just stores the last-revision, and the left-hand history leading
1042
up to there is the history.
1044
This format was introduced in bzr 0.15
1047
def get_format_string(self):
1048
"""See BranchFormat.get_format_string()."""
1049
return "Bazaar Branch Format 6 (bzr 0.15)\n"
1051
def get_format_description(self):
1052
"""See BranchFormat.get_format_description()."""
1053
return "Branch format 6"
1055
def initialize(self, a_bzrdir):
1056
"""Create a branch of this format in a_bzrdir."""
1057
utf8_files = [('last-revision', '0 null:\n'),
1058
('branch-name', ''),
1059
('branch.conf', ''),
1062
return self._initialize_helper(a_bzrdir, utf8_files)
1064
def open(self, a_bzrdir, _found=False):
1065
"""Return the branch object for a_bzrdir
1067
_found is a private parameter, do not use it. It is used to indicate
1068
if format probing has already be done.
1071
format = BranchFormat.find_format(a_bzrdir)
1072
assert format.__class__ == self.__class__
1073
transport = a_bzrdir.get_branch_transport(None)
1074
control_files = lockable_files.LockableFiles(transport, 'lock',
1076
return BzrBranch6(_format=self,
1077
_control_files=control_files,
1079
_repository=a_bzrdir.find_repository())
1081
def supports_tags(self):
1085
class BranchReferenceFormat(BranchFormat):
1086
"""Bzr branch reference format.
1088
Branch references are used in implementing checkouts, they
1089
act as an alias to the real branch which is at some other url.
1096
def get_format_string(self):
1097
"""See BranchFormat.get_format_string()."""
1098
return "Bazaar-NG Branch Reference Format 1\n"
1100
def get_format_description(self):
1101
"""See BranchFormat.get_format_description()."""
1102
return "Checkout reference format 1"
1104
def initialize(self, a_bzrdir, target_branch=None):
1105
"""Create a branch of this format in a_bzrdir."""
1106
if target_branch is None:
1107
# this format does not implement branch itself, thus the implicit
1108
# creation contract must see it as uninitializable
1109
raise errors.UninitializableFormat(self)
1110
mutter('creating branch reference in %s', a_bzrdir.transport.base)
1111
branch_transport = a_bzrdir.get_branch_transport(self)
1112
branch_transport.put_bytes('location',
1113
target_branch.bzrdir.root_transport.base)
1114
branch_transport.put_bytes('format', self.get_format_string())
1115
return self.open(a_bzrdir, _found=True)
1118
super(BranchReferenceFormat, self).__init__()
1119
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1121
def _make_reference_clone_function(format, a_branch):
1122
"""Create a clone() routine for a branch dynamically."""
1123
def clone(to_bzrdir, revision_id=None):
1124
"""See Branch.clone()."""
1125
return format.initialize(to_bzrdir, a_branch)
1126
# cannot obey revision_id limits when cloning a reference ...
1127
# FIXME RBC 20060210 either nuke revision_id for clone, or
1128
# emit some sort of warning/error to the caller ?!
1131
def open(self, a_bzrdir, _found=False):
1132
"""Return the branch that the branch reference in a_bzrdir points at.
1134
_found is a private parameter, do not use it. It is used to indicate
1135
if format probing has already be done.
1138
format = BranchFormat.find_format(a_bzrdir)
1139
assert format.__class__ == self.__class__
1140
transport = a_bzrdir.get_branch_transport(None)
1141
real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
1142
result = real_bzrdir.open_branch()
1143
# this changes the behaviour of result.clone to create a new reference
1144
# rather than a copy of the content of the branch.
1145
# I did not use a proxy object because that needs much more extensive
1146
# testing, and we are only changing one behaviour at the moment.
1147
# If we decide to alter more behaviours - i.e. the implicit nickname
1148
# then this should be refactored to introduce a tested proxy branch
1149
# and a subclass of that for use in overriding clone() and ....
1151
result.clone = self._make_reference_clone_function(result)
1155
# formats which have no format string are not discoverable
1156
# and not independently creatable, so are not registered.
1157
__default_format = BzrBranchFormat5()
1158
BranchFormat.register_format(__default_format)
1159
BranchFormat.register_format(BranchReferenceFormat())
1160
BranchFormat.register_format(BzrBranchFormat6())
1161
BranchFormat.set_default_format(__default_format)
1162
_legacy_formats = [BzrBranchFormat4(),
1165
class BzrBranch(Branch):
1166
"""A branch stored in the actual filesystem.
1168
Note that it's "local" in the context of the filesystem; it doesn't
1169
really matter if it's on an nfs/smb/afs/coda/... share, as long as
1170
it's writable, and can be accessed via the normal filesystem API.
1173
def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
1174
relax_version_check=DEPRECATED_PARAMETER, _format=None,
1175
_control_files=None, a_bzrdir=None, _repository=None):
1176
"""Create new branch object at a particular location.
1178
transport -- A Transport object, defining how to access files.
1180
init -- If True, create new control files in a previously
1181
unversioned directory. If False, the branch must already
1184
relax_version_check -- If true, the usual check for the branch
1185
version is not applied. This is intended only for
1186
upgrade/recovery type use; it's not guaranteed that
1187
all operations will work on old format branches.
1189
Branch.__init__(self)
1190
if a_bzrdir is None:
1191
self.bzrdir = bzrdir.BzrDir.open(transport.base)
1193
self.bzrdir = a_bzrdir
1194
# self._transport used to point to the directory containing the
1195
# control directory, but was not used - now it's just the transport
1196
# for the branch control files. mbp 20070212
1197
self._base = self.bzrdir.transport.clone('..').base
1198
self._format = _format
1199
if _control_files is None:
1200
raise ValueError('BzrBranch _control_files is None')
1201
self.control_files = _control_files
1202
self._transport = _control_files._transport
1203
if deprecated_passed(init):
1204
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
1205
"deprecated as of bzr 0.8. Please use Branch.create().",
1209
# this is slower than before deprecation, oh well never mind.
1210
# -> its deprecated.
1211
self._initialize(transport.base)
1212
self._check_format(_format)
1213
if deprecated_passed(relax_version_check):
1214
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
1215
"relax_version_check parameter is deprecated as of bzr 0.8. "
1216
"Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
1220
if (not relax_version_check
1221
and not self._format.is_supported()):
1222
raise errors.UnsupportedFormatError(format=fmt)
1223
if deprecated_passed(transport):
1224
warn("BzrBranch.__init__(transport=XXX...): The transport "
1225
"parameter is deprecated as of bzr 0.8. "
1226
"Please use Branch.open, or bzrdir.open_branch().",
1229
self.repository = _repository
1232
return '%s(%r)' % (self.__class__.__name__, self.base)
1236
def _get_base(self):
1237
"""Returns the directory containing the control directory."""
1240
base = property(_get_base, doc="The URL for the root of this branch.")
1242
def _finish_transaction(self):
1243
"""Exit the current transaction."""
1244
return self.control_files._finish_transaction()
1246
def get_transaction(self):
1247
"""Return the current active transaction.
1249
If no transaction is active, this returns a passthrough object
1250
for which all data is immediately flushed and no caching happens.
1252
# this is an explicit function so that we can do tricky stuff
1253
# when the storage in rev_storage is elsewhere.
1254
# we probably need to hook the two 'lock a location' and
1255
# 'have a transaction' together more delicately, so that
1256
# we can have two locks (branch and storage) and one transaction
1257
# ... and finishing the transaction unlocks both, but unlocking
1258
# does not. - RBC 20051121
1259
return self.control_files.get_transaction()
1261
def _set_transaction(self, transaction):
1262
"""Set a new active transaction."""
1263
return self.control_files._set_transaction(transaction)
1265
def abspath(self, name):
1266
"""See Branch.abspath."""
1267
return self.control_files._transport.abspath(name)
1269
def _check_format(self, format):
1270
"""Identify the branch format if needed.
1272
The format is stored as a reference to the format object in
1273
self._format for code that needs to check it later.
1275
The format parameter is either None or the branch format class
1276
used to open this branch.
1278
FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
1281
format = BranchFormat.find_format(self.bzrdir)
1282
self._format = format
1283
mutter("got branch format %s", self._format)
1286
def get_root_id(self):
1287
"""See Branch.get_root_id."""
1288
tree = self.repository.revision_tree(self.last_revision())
1289
return tree.inventory.root.file_id
1291
def is_locked(self):
1292
return self.control_files.is_locked()
1294
def lock_write(self):
1295
self.repository.lock_write()
1297
self.control_files.lock_write()
1299
self.repository.unlock()
1302
def lock_read(self):
1303
self.repository.lock_read()
1305
self.control_files.lock_read()
1307
self.repository.unlock()
1311
# TODO: test for failed two phase locks. This is known broken.
1313
self.control_files.unlock()
1315
self.repository.unlock()
1317
def peek_lock_mode(self):
1318
if self.control_files._lock_count == 0:
1321
return self.control_files._lock_mode
1323
def get_physical_lock_status(self):
1324
return self.control_files.get_physical_lock_status()
1327
def print_file(self, file, revision_id):
1328
"""See Branch.print_file."""
1329
return self.repository.print_file(file, revision_id)
1332
def append_revision(self, *revision_ids):
1333
"""See Branch.append_revision."""
1334
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1335
for revision_id in revision_ids:
1336
_mod_revision.check_not_reserved_id(revision_id)
1337
mutter("add {%s} to revision-history" % revision_id)
1338
rev_history = self.revision_history()
1339
rev_history.extend(revision_ids)
1340
self.set_revision_history(rev_history)
1342
def _write_revision_history(self, history):
1343
"""Factored out of set_revision_history.
1345
This performs the actual writing to disk.
1346
It is intended to be called by BzrBranch5.set_revision_history."""
1347
self.control_files.put_bytes(
1348
'revision-history', '\n'.join(history))
1351
def set_revision_history(self, rev_history):
1352
"""See Branch.set_revision_history."""
1353
rev_history = [osutils.safe_revision_id(r) for r in rev_history]
1354
self._write_revision_history(rev_history)
1355
transaction = self.get_transaction()
1356
history = transaction.map.find_revision_history()
1357
if history is not None:
1358
# update the revision history in the identity map.
1359
history[:] = list(rev_history)
1360
# this call is disabled because revision_history is
1361
# not really an object yet, and the transaction is for objects.
1362
# transaction.register_dirty(history)
1364
transaction.map.add_revision_history(rev_history)
1365
# this call is disabled because revision_history is
1366
# not really an object yet, and the transaction is for objects.
1367
# transaction.register_clean(history)
1368
for hook in Branch.hooks['set_rh']:
1369
hook(self, rev_history)
1372
def set_last_revision_info(self, revno, revision_id):
1373
revision_id = osutils.safe_revision_id(revision_id)
1374
history = self._lefthand_history(revision_id)
1375
assert len(history) == revno, '%d != %d' % (len(history), revno)
1376
self.set_revision_history(history)
1378
def _gen_revision_history(self):
1379
history = self.control_files.get('revision-history').read().split('\n')
1380
if history[-1:] == ['']:
1381
# There shouldn't be a trailing newline, but just in case.
1386
def revision_history(self):
1387
"""See Branch.revision_history."""
1388
transaction = self.get_transaction()
1389
history = transaction.map.find_revision_history()
1390
if history is not None:
1391
# mutter("cache hit for revision-history in %s", self)
1392
return list(history)
1393
history = self._gen_revision_history()
1394
transaction.map.add_revision_history(history)
1395
# this call is disabled because revision_history is
1396
# not really an object yet, and the transaction is for objects.
1397
# transaction.register_clean(history, precious=True)
1398
return list(history)
1400
def _lefthand_history(self, revision_id, last_rev=None,
1402
# stop_revision must be a descendant of last_revision
1403
stop_graph = self.repository.get_revision_graph(revision_id)
1404
if last_rev is not None and last_rev not in stop_graph:
1405
# our previous tip is not merged into stop_revision
1406
raise errors.DivergedBranches(self, other_branch)
1407
# make a new revision history from the graph
1408
current_rev_id = revision_id
1410
while current_rev_id not in (None, _mod_revision.NULL_REVISION):
1411
new_history.append(current_rev_id)
1412
current_rev_id_parents = stop_graph[current_rev_id]
1414
current_rev_id = current_rev_id_parents[0]
1416
current_rev_id = None
1417
new_history.reverse()
1421
def generate_revision_history(self, revision_id, last_rev=None,
1423
"""Create a new revision history that will finish with revision_id.
1425
:param revision_id: the new tip to use.
1426
:param last_rev: The previous last_revision. If not None, then this
1427
must be a ancestory of revision_id, or DivergedBranches is raised.
1428
:param other_branch: The other branch that DivergedBranches should
1429
raise with respect to.
1431
revision_id = osutils.safe_revision_id(revision_id)
1432
self.set_revision_history(self._lefthand_history(revision_id,
1433
last_rev, other_branch))
1436
def update_revisions(self, other, stop_revision=None):
1437
"""See Branch.update_revisions."""
1440
if stop_revision is None:
1441
stop_revision = other.last_revision()
1442
if stop_revision is None:
1443
# if there are no commits, we're done.
1446
stop_revision = osutils.safe_revision_id(stop_revision)
1447
# whats the current last revision, before we fetch [and change it
1449
last_rev = self.last_revision()
1450
# we fetch here regardless of whether we need to so that we pickup
1452
self.fetch(other, stop_revision)
1453
my_ancestry = self.repository.get_ancestry(last_rev)
1454
if stop_revision in my_ancestry:
1455
# last_revision is a descendant of stop_revision
1457
self.generate_revision_history(stop_revision, last_rev=last_rev,
1462
def basis_tree(self):
1463
"""See Branch.basis_tree."""
1464
return self.repository.revision_tree(self.last_revision())
1466
@deprecated_method(zero_eight)
1467
def working_tree(self):
1468
"""Create a Working tree object for this branch."""
1470
from bzrlib.transport.local import LocalTransport
1471
if (self.base.find('://') != -1 or
1472
not isinstance(self._transport, LocalTransport)):
1473
raise NoWorkingTree(self.base)
1474
return self.bzrdir.open_workingtree()
1477
def pull(self, source, overwrite=False, stop_revision=None,
1478
_hook_master=None, _run_hooks=True):
1481
:param _hook_master: Private parameter - set the branch to
1482
be supplied as the master to push hooks.
1483
:param _run_hooks: Private parameter - allow disabling of
1484
hooks, used when pushing to a master branch.
1486
result = PullResult()
1487
result.source_branch = source
1488
result.target_branch = self
1491
result.old_revno, result.old_revid = self.last_revision_info()
1493
self.update_revisions(source, stop_revision)
1494
except DivergedBranches:
1498
if stop_revision is None:
1499
stop_revision = source.last_revision()
1500
self.generate_revision_history(stop_revision)
1501
result.tag_conflicts = source.tags.merge_to(self.tags)
1502
result.new_revno, result.new_revid = self.last_revision_info()
1504
result.master_branch = _hook_master
1505
result.local_branch = self
1507
result.master_branch = self
1508
result.local_branch = None
1510
for hook in Branch.hooks['post_pull']:
1516
def _get_parent_location(self):
1517
_locs = ['parent', 'pull', 'x-pull']
1520
return self.control_files.get(l).read().strip('\n')
1526
def push(self, target, overwrite=False, stop_revision=None,
1527
_hook_master=None, _run_hooks=True):
1530
:param _hook_master: Private parameter - set the branch to
1531
be supplied as the master to push hooks.
1532
:param _run_hooks: Private parameter - allow disabling of
1533
hooks, used when pushing to a master branch.
1535
result = PushResult()
1536
result.source_branch = self
1537
result.target_branch = target
1540
result.old_revno, result.old_revid = target.last_revision_info()
1542
target.update_revisions(self, stop_revision)
1543
except DivergedBranches:
1547
target.set_revision_history(self.revision_history())
1548
result.tag_conflicts = self.tags.merge_to(target.tags)
1549
result.new_revno, result.new_revid = target.last_revision_info()
1551
result.master_branch = _hook_master
1552
result.local_branch = target
1554
result.master_branch = target
1555
result.local_branch = None
1557
for hook in Branch.hooks['post_push']:
1563
def get_parent(self):
1564
"""See Branch.get_parent."""
1566
assert self.base[-1] == '/'
1567
parent = self._get_parent_location()
1570
# This is an old-format absolute path to a local branch
1571
# turn it into a url
1572
if parent.startswith('/'):
1573
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1575
return urlutils.join(self.base[:-1], parent)
1576
except errors.InvalidURLJoin, e:
1577
raise errors.InaccessibleParent(parent, self.base)
1579
def get_push_location(self):
1580
"""See Branch.get_push_location."""
1581
push_loc = self.get_config().get_user_option('push_location')
1584
def set_push_location(self, location):
1585
"""See Branch.set_push_location."""
1586
self.get_config().set_user_option(
1587
'push_location', location,
1588
store=_mod_config.STORE_LOCATION_NORECURSE)
1591
def set_parent(self, url):
1592
"""See Branch.set_parent."""
1593
# TODO: Maybe delete old location files?
1594
# URLs should never be unicode, even on the local fs,
1595
# FIXUP this and get_parent in a future branch format bump:
1596
# read and rewrite the file, and have the new format code read
1597
# using .get not .get_utf8. RBC 20060125
1599
if isinstance(url, unicode):
1601
url = url.encode('ascii')
1602
except UnicodeEncodeError:
1603
raise bzrlib.errors.InvalidURL(url,
1604
"Urls must be 7-bit ascii, "
1605
"use bzrlib.urlutils.escape")
1606
url = urlutils.relative_url(self.base, url)
1607
self._set_parent_location(url)
1609
def _set_parent_location(self, url):
1611
self.control_files._transport.delete('parent')
1613
assert isinstance(url, str)
1614
self.control_files.put_bytes('parent', url + '\n')
1616
@deprecated_function(zero_nine)
1617
def tree_config(self):
1618
"""DEPRECATED; call get_config instead.
1619
TreeConfig has become part of BranchConfig."""
1620
return TreeConfig(self)
1623
class BzrBranch5(BzrBranch):
1624
"""A format 5 branch. This supports new features over plan branches.
1626
It has support for a master_branch which is the data for bound branches.
1634
super(BzrBranch5, self).__init__(_format=_format,
1635
_control_files=_control_files,
1637
_repository=_repository)
1640
def pull(self, source, overwrite=False, stop_revision=None,
1642
"""Extends branch.pull to be bound branch aware.
1644
:param _run_hooks: Private parameter used to force hook running
1645
off during bound branch double-pushing.
1647
bound_location = self.get_bound_location()
1648
master_branch = None
1649
if bound_location and source.base != bound_location:
1650
# not pulling from master, so we need to update master.
1651
master_branch = self.get_master_branch()
1652
master_branch.lock_write()
1655
# pull from source into master.
1656
master_branch.pull(source, overwrite, stop_revision,
1658
return super(BzrBranch5, self).pull(source, overwrite,
1659
stop_revision, _hook_master=master_branch,
1660
_run_hooks=_run_hooks)
1663
master_branch.unlock()
1666
def push(self, target, overwrite=False, stop_revision=None):
1667
"""Updates branch.push to be bound branch aware."""
1668
bound_location = target.get_bound_location()
1669
master_branch = None
1670
if bound_location and target.base != bound_location:
1671
# not pushing to master, so we need to update master.
1672
master_branch = target.get_master_branch()
1673
master_branch.lock_write()
1676
# push into the master from this branch.
1677
super(BzrBranch5, self).push(master_branch, overwrite,
1678
stop_revision, _run_hooks=False)
1679
# and push into the target branch from this. Note that we push from
1680
# this branch again, because its considered the highest bandwidth
1682
return super(BzrBranch5, self).push(target, overwrite,
1683
stop_revision, _hook_master=master_branch)
1686
master_branch.unlock()
1688
def get_bound_location(self):
1690
return self.control_files.get_utf8('bound').read()[:-1]
1691
except errors.NoSuchFile:
1695
def get_master_branch(self):
1696
"""Return the branch we are bound to.
1698
:return: Either a Branch, or None
1700
This could memoise the branch, but if thats done
1701
it must be revalidated on each new lock.
1702
So for now we just don't memoise it.
1703
# RBC 20060304 review this decision.
1705
bound_loc = self.get_bound_location()
1709
return Branch.open(bound_loc)
1710
except (errors.NotBranchError, errors.ConnectionError), e:
1711
raise errors.BoundBranchConnectionFailure(
1715
def set_bound_location(self, location):
1716
"""Set the target where this branch is bound to.
1718
:param location: URL to the target branch
1721
self.control_files.put_utf8('bound', location+'\n')
1724
self.control_files._transport.delete('bound')
1730
def bind(self, other):
1731
"""Bind this branch to the branch other.
1733
This does not push or pull data between the branches, though it does
1734
check for divergence to raise an error when the branches are not
1735
either the same, or one a prefix of the other. That behaviour may not
1736
be useful, so that check may be removed in future.
1738
:param other: The branch to bind to
1741
# TODO: jam 20051230 Consider checking if the target is bound
1742
# It is debatable whether you should be able to bind to
1743
# a branch which is itself bound.
1744
# Committing is obviously forbidden,
1745
# but binding itself may not be.
1746
# Since we *have* to check at commit time, we don't
1747
# *need* to check here
1749
# we want to raise diverged if:
1750
# last_rev is not in the other_last_rev history, AND
1751
# other_last_rev is not in our history, and do it without pulling
1753
last_rev = self.last_revision()
1754
if last_rev is not None:
1757
other_last_rev = other.last_revision()
1758
if other_last_rev is not None:
1759
# neither branch is new, we have to do some work to
1760
# ascertain diversion.
1761
remote_graph = other.repository.get_revision_graph(
1763
local_graph = self.repository.get_revision_graph(last_rev)
1764
if (last_rev not in remote_graph and
1765
other_last_rev not in local_graph):
1766
raise errors.DivergedBranches(self, other)
1769
self.set_bound_location(other.base)
1773
"""If bound, unbind"""
1774
return self.set_bound_location(None)
1778
"""Synchronise this branch with the master branch if any.
1780
:return: None or the last_revision that was pivoted out during the
1783
master = self.get_master_branch()
1784
if master is not None:
1785
old_tip = self.last_revision()
1786
self.pull(master, overwrite=True)
1787
if old_tip in self.repository.get_ancestry(self.last_revision()):
1793
class BzrBranchExperimental(BzrBranch5):
1794
"""Bzr experimental branch format
1797
- a revision-history file.
1799
- a lock dir guarding the branch itself
1800
- all of this stored in a branch/ subdirectory
1801
- works with shared repositories.
1802
- a tag dictionary in the branch
1804
This format is new in bzr 0.15, but shouldn't be used for real data,
1807
This class acts as it's own BranchFormat.
1810
_matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1813
def get_format_string(cls):
1814
"""See BranchFormat.get_format_string()."""
1815
return "Bazaar-NG branch format experimental\n"
1818
def get_format_description(cls):
1819
"""See BranchFormat.get_format_description()."""
1820
return "Experimental branch format"
1823
def _initialize_control_files(cls, a_bzrdir, utf8_files, lock_filename,
1825
branch_transport = a_bzrdir.get_branch_transport(cls)
1826
control_files = lockable_files.LockableFiles(branch_transport,
1827
lock_filename, lock_class)
1828
control_files.create_lock()
1829
control_files.lock_write()
1831
for filename, content in utf8_files:
1832
control_files.put_utf8(filename, content)
1834
control_files.unlock()
1837
def initialize(cls, a_bzrdir):
1838
"""Create a branch of this format in a_bzrdir."""
1839
utf8_files = [('format', cls.get_format_string()),
1840
('revision-history', ''),
1841
('branch-name', ''),
1844
cls._initialize_control_files(a_bzrdir, utf8_files,
1845
'lock', lockdir.LockDir)
1846
return cls.open(a_bzrdir, _found=True)
1849
def open(cls, a_bzrdir, _found=False):
1850
"""Return the branch object for a_bzrdir
1852
_found is a private parameter, do not use it. It is used to indicate
1853
if format probing has already be done.
1856
format = BranchFormat.find_format(a_bzrdir)
1857
assert format.__class__ == cls
1858
transport = a_bzrdir.get_branch_transport(None)
1859
control_files = lockable_files.LockableFiles(transport, 'lock',
1861
return cls(_format=cls,
1862
_control_files=control_files,
1864
_repository=a_bzrdir.find_repository())
1867
def is_supported(cls):
1870
def _make_tags(self):
1871
return BasicTags(self)
1874
def supports_tags(cls):
1878
BranchFormat.register_format(BzrBranchExperimental)
1881
class BzrBranch6(BzrBranch5):
1884
def last_revision_info(self):
1885
revision_string = self.control_files.get('last-revision').read()
1886
revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
1887
revision_id = cache_utf8.get_cached_utf8(revision_id)
1889
return revno, revision_id
1891
def last_revision(self):
1892
"""Return last revision id, or None"""
1893
revision_id = self.last_revision_info()[1]
1894
if revision_id == _mod_revision.NULL_REVISION:
1898
def _write_last_revision_info(self, revno, revision_id):
1899
"""Simply write out the revision id, with no checks.
1901
Use set_last_revision_info to perform this safely.
1903
Does not update the revision_history cache.
1904
Intended to be called by set_last_revision_info and
1905
_write_revision_history.
1907
if revision_id is None:
1908
revision_id = 'null:'
1909
out_string = '%d %s\n' % (revno, revision_id)
1910
self.control_files.put_bytes('last-revision', out_string)
1913
def set_last_revision_info(self, revno, revision_id):
1914
revision_id = osutils.safe_revision_id(revision_id)
1915
if self._get_append_revisions_only():
1916
self._check_history_violation(revision_id)
1917
self._write_last_revision_info(revno, revision_id)
1918
transaction = self.get_transaction()
1919
cached_history = transaction.map.find_revision_history()
1920
if cached_history is not None:
1921
transaction.map.remove_object(cached_history)
1923
def _check_history_violation(self, revision_id):
1924
last_revision = self.last_revision()
1925
if last_revision is None:
1927
if last_revision not in self._lefthand_history(revision_id):
1928
raise errors.AppendRevisionsOnlyViolation(self.base)
1930
def _gen_revision_history(self):
1931
"""Generate the revision history from last revision
1933
history = list(self.repository.iter_reverse_revision_history(
1934
self.last_revision()))
1938
def _write_revision_history(self, history):
1939
"""Factored out of set_revision_history.
1941
This performs the actual writing to disk, with format-specific checks.
1942
It is intended to be called by BzrBranch5.set_revision_history.
1944
if len(history) == 0:
1945
last_revision = 'null:'
1947
if history != self._lefthand_history(history[-1]):
1948
raise errors.NotLefthandHistory(history)
1949
last_revision = history[-1]
1950
if self._get_append_revisions_only():
1951
self._check_history_violation(last_revision)
1952
self._write_last_revision_info(len(history), last_revision)
1955
def append_revision(self, *revision_ids):
1956
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1957
if len(revision_ids) == 0:
1959
prev_revno, prev_revision = self.last_revision_info()
1960
for revision in self.repository.get_revisions(revision_ids):
1961
if prev_revision == _mod_revision.NULL_REVISION:
1962
if revision.parent_ids != []:
1963
raise errors.NotLeftParentDescendant(self, prev_revision,
1964
revision.revision_id)
1966
if revision.parent_ids[0] != prev_revision:
1967
raise errors.NotLeftParentDescendant(self, prev_revision,
1968
revision.revision_id)
1969
prev_revision = revision.revision_id
1970
self.set_last_revision_info(prev_revno + len(revision_ids),
1974
def _set_parent_location(self, url):
1975
"""Set the parent branch"""
1976
self._set_config_location('parent_location', url, make_relative=True)
1979
def _get_parent_location(self):
1980
"""Set the parent branch"""
1981
return self._get_config_location('parent_location')
1983
def set_push_location(self, location):
1984
"""See Branch.set_push_location."""
1985
self._set_config_location('push_location', location)
1987
def set_bound_location(self, location):
1988
"""See Branch.set_push_location."""
1990
config = self.get_config()
1991
if location is None:
1992
if config.get_user_option('bound') != 'True':
1995
config.set_user_option('bound', 'False')
1998
self._set_config_location('bound_location', location,
2000
config.set_user_option('bound', 'True')
2003
def _get_bound_location(self, bound):
2004
"""Return the bound location in the config file.
2006
Return None if the bound parameter does not match"""
2007
config = self.get_config()
2008
config_bound = (config.get_user_option('bound') == 'True')
2009
if config_bound != bound:
2011
return self._get_config_location('bound_location', config=config)
2013
def get_bound_location(self):
2014
"""See Branch.set_push_location."""
2015
return self._get_bound_location(True)
2017
def get_old_bound_location(self):
2018
"""See Branch.get_old_bound_location"""
2019
return self._get_bound_location(False)
2021
def set_append_revisions_only(self, enabled):
2026
self.get_config().set_user_option('append_revisions_only', value)
2028
def _get_append_revisions_only(self):
2029
value = self.get_config().get_user_option('append_revisions_only')
2030
return value == 'True'
2032
def _synchronize_history(self, destination, revision_id):
2033
"""Synchronize last revision and revision history between branches.
2035
This version is most efficient when the destination is also a
2036
BzrBranch6, but works for BzrBranch5, as long as the destination's
2037
repository contains all the lefthand ancestors of the intended
2038
last_revision. If not, set_last_revision_info will fail.
2040
:param destination: The branch to copy the history into
2041
:param revision_id: The revision-id to truncate history at. May
2042
be None to copy complete history.
2044
if revision_id is None:
2045
revno, revision_id = self.last_revision_info()
2047
revno = self.revision_id_to_revno(revision_id)
2048
destination.set_last_revision_info(revno, revision_id)
2050
def _make_tags(self):
2051
return BasicTags(self)
2054
class BranchTestProviderAdapter(object):
2055
"""A tool to generate a suite testing multiple branch formats at once.
2057
This is done by copying the test once for each transport and injecting
2058
the transport_server, transport_readonly_server, and branch_format
2059
classes into each copy. Each copy is also given a new id() to make it
2063
def __init__(self, transport_server, transport_readonly_server, formats):
2064
self._transport_server = transport_server
2065
self._transport_readonly_server = transport_readonly_server
2066
self._formats = formats
2068
def adapt(self, test):
2069
result = TestSuite()
2070
for branch_format, bzrdir_format in self._formats:
2071
new_test = deepcopy(test)
2072
new_test.transport_server = self._transport_server
2073
new_test.transport_readonly_server = self._transport_readonly_server
2074
new_test.bzrdir_format = bzrdir_format
2075
new_test.branch_format = branch_format
2076
def make_new_test_id():
2077
# the format can be either a class or an instance
2078
name = getattr(branch_format, '__name__',
2079
branch_format.__class__.__name__)
2080
new_id = "%s(%s)" % (new_test.id(), name)
2081
return lambda: new_id
2082
new_test.id = make_new_test_id()
2083
result.addTest(new_test)
2087
######################################################################
2088
# results of operations
2091
class _Result(object):
2093
def _show_tag_conficts(self, to_file):
2094
if not getattr(self, 'tag_conflicts', None):
2096
to_file.write('Conflicting tags:\n')
2097
for name, value1, value2 in self.tag_conflicts:
2098
to_file.write(' %s\n' % (name, ))
2101
class PullResult(_Result):
2102
"""Result of a Branch.pull operation.
2104
:ivar old_revno: Revision number before pull.
2105
:ivar new_revno: Revision number after pull.
2106
:ivar old_revid: Tip revision id before pull.
2107
:ivar new_revid: Tip revision id after pull.
2108
:ivar source_branch: Source (local) branch object.
2109
:ivar master_branch: Master branch of the target, or None.
2110
:ivar target_branch: Target/destination branch object.
2114
# DEPRECATED: pull used to return the change in revno
2115
return self.new_revno - self.old_revno
2117
def report(self, to_file):
2118
if self.old_revid == self.new_revid:
2119
to_file.write('No revisions to pull.\n')
2121
to_file.write('Now on revision %d.\n' % self.new_revno)
2122
self._show_tag_conficts(to_file)
2125
class PushResult(_Result):
2126
"""Result of a Branch.push operation.
2128
:ivar old_revno: Revision number before push.
2129
:ivar new_revno: Revision number after push.
2130
:ivar old_revid: Tip revision id before push.
2131
:ivar new_revid: Tip revision id after push.
2132
:ivar source_branch: Source branch object.
2133
:ivar master_branch: Master branch of the target, or None.
2134
:ivar target_branch: Target/destination branch object.
2138
# DEPRECATED: push used to return the change in revno
2139
return self.new_revno - self.old_revno
2141
def report(self, to_file):
2142
"""Write a human-readable description of the result."""
2143
if self.old_revid == self.new_revid:
2144
to_file.write('No new revisions to push.\n')
2146
to_file.write('Pushed up to revision %d.\n' % self.new_revno)
2147
self._show_tag_conficts(to_file)
2150
class BranchCheckResult(object):
2151
"""Results of checking branch consistency.
2156
def __init__(self, branch):
2157
self.branch = branch
2159
def report_results(self, verbose):
2160
"""Report the check results via trace.note.
2162
:param verbose: Requests more detailed display of what was checked,
2165
note('checked branch %s format %s',
2167
self.branch._format)
2170
class Converter5to6(object):
2171
"""Perform an in-place upgrade of format 5 to format 6"""
2173
def convert(self, branch):
2174
# Data for 5 and 6 can peacefully coexist.
2175
format = BzrBranchFormat6()
2176
new_branch = format.open(branch.bzrdir, _found=True)
2178
# Copy source data into target
2179
new_branch.set_last_revision_info(*branch.last_revision_info())
2180
new_branch.set_parent(branch.get_parent())
2181
new_branch.set_bound_location(branch.get_bound_location())
2182
new_branch.set_push_location(branch.get_push_location())
2184
# New branch has no tags by default
2185
new_branch.tags._set_tag_dict({})
2187
# Copying done; now update target format
2188
new_branch.control_files.put_utf8('format',
2189
format.get_format_string())
2191
# Clean up old files
2192
new_branch.control_files._transport.delete('revision-history')
2194
branch.set_parent(None)
2197
branch.set_bound_location(None)