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()
229
if last_revision is None:
230
last_revision = _mod_revision.NULL_REVISION
231
return self.repository.fetch(from_branch.repository,
232
revision_id=last_revision,
235
if nested_pb is not None:
239
def get_bound_location(self):
240
"""Return the URL of the branch we are bound to.
242
Older format branches cannot bind, please be sure to use a metadir
247
def get_commit_builder(self, parents, config=None, timestamp=None,
248
timezone=None, committer=None, revprops=None,
250
"""Obtain a CommitBuilder for this branch.
252
:param parents: Revision ids of the parents of the new revision.
253
:param config: Optional configuration to use.
254
:param timestamp: Optional timestamp recorded for commit.
255
:param timezone: Optional timezone for timestamp.
256
:param committer: Optional committer to set for commit.
257
:param revprops: Optional dictionary of revision properties.
258
:param revision_id: Optional revision id.
262
config = self.get_config()
264
return self.repository.get_commit_builder(self, parents, config,
265
timestamp, timezone, committer, revprops, revision_id)
267
def get_master_branch(self):
268
"""Return the branch we are bound to.
270
:return: Either a Branch, or None
274
def get_revision_delta(self, revno):
275
"""Return the delta for one revision.
277
The delta is relative to its mainline predecessor, or the
278
empty tree for revision 1.
280
assert isinstance(revno, int)
281
rh = self.revision_history()
282
if not (1 <= revno <= len(rh)):
283
raise InvalidRevisionNumber(revno)
284
return self.repository.get_revision_delta(rh[revno-1])
286
def get_root_id(self):
287
"""Return the id of this branches root"""
288
raise NotImplementedError(self.get_root_id)
290
def print_file(self, file, revision_id):
291
"""Print `file` to stdout."""
292
raise NotImplementedError(self.print_file)
294
def append_revision(self, *revision_ids):
295
raise NotImplementedError(self.append_revision)
297
def set_revision_history(self, rev_history):
298
raise NotImplementedError(self.set_revision_history)
300
def revision_history(self):
301
"""Return sequence of revision hashes on to this branch."""
302
raise NotImplementedError(self.revision_history)
305
"""Return current revision number for this branch.
307
That is equivalent to the number of revisions committed to
310
return len(self.revision_history())
313
"""Older format branches cannot bind or unbind."""
314
raise errors.UpgradeRequired(self.base)
316
def last_revision(self):
317
"""Return last revision id, or None"""
318
ph = self.revision_history()
324
def last_revision_info(self):
325
"""Return information about the last revision.
327
:return: A tuple (revno, last_revision_id).
329
rh = self.revision_history()
332
return (revno, rh[-1])
334
return (0, _mod_revision.NULL_REVISION)
336
def missing_revisions(self, other, stop_revision=None):
337
"""Return a list of new revisions that would perfectly fit.
339
If self and other have not diverged, return a list of the revisions
340
present in other, but missing from self.
342
self_history = self.revision_history()
343
self_len = len(self_history)
344
other_history = other.revision_history()
345
other_len = len(other_history)
346
common_index = min(self_len, other_len) -1
347
if common_index >= 0 and \
348
self_history[common_index] != other_history[common_index]:
349
raise DivergedBranches(self, other)
351
if stop_revision is None:
352
stop_revision = other_len
354
assert isinstance(stop_revision, int)
355
if stop_revision > other_len:
356
raise errors.NoSuchRevision(self, stop_revision)
357
return other_history[self_len:stop_revision]
359
def update_revisions(self, other, stop_revision=None):
360
"""Pull in new perfect-fit revisions.
362
:param other: Another Branch to pull from
363
:param stop_revision: Updated until the given revision
366
raise NotImplementedError(self.update_revisions)
368
def revision_id_to_revno(self, revision_id):
369
"""Given a revision id, return its revno"""
370
if revision_id is None:
372
history = self.revision_history()
374
return history.index(revision_id) + 1
376
raise bzrlib.errors.NoSuchRevision(self, revision_id)
378
def get_rev_id(self, revno, history=None):
379
"""Find the revision id of the specified revno."""
383
history = self.revision_history()
384
if revno <= 0 or revno > len(history):
385
raise bzrlib.errors.NoSuchRevision(self, revno)
386
return history[revno - 1]
388
def pull(self, source, overwrite=False, stop_revision=None):
389
"""Mirror source into this branch.
391
This branch is considered to be 'local', having low latency.
393
raise NotImplementedError(self.pull)
395
def push(self, target, overwrite=False, stop_revision=None):
396
"""Mirror this branch into target.
398
This branch is considered to be 'local', having low latency.
400
raise NotImplementedError(self.push)
402
def basis_tree(self):
403
"""Return `Tree` object for last revision."""
404
return self.repository.revision_tree(self.last_revision())
406
def rename_one(self, from_rel, to_rel):
409
This can change the directory or the filename or both.
411
raise NotImplementedError(self.rename_one)
413
def move(self, from_paths, to_name):
416
to_name must exist as a versioned directory.
418
If to_name exists and is a directory, the files are moved into
419
it, keeping their old names. If it is a directory,
421
Note that to_name is only the last component of the new name;
422
this doesn't change the directory.
424
This returns a list of (from_path, to_path) pairs for each
427
raise NotImplementedError(self.move)
429
def get_parent(self):
430
"""Return the parent location of the branch.
432
This is the default location for push/pull/missing. The usual
433
pattern is that the user can override it by specifying a
436
raise NotImplementedError(self.get_parent)
438
def get_submit_branch(self):
439
"""Return the submit location of the branch.
441
This is the default location for bundle. The usual
442
pattern is that the user can override it by specifying a
445
return self.get_config().get_user_option('submit_branch')
447
def set_submit_branch(self, location):
448
"""Return the submit location of the branch.
450
This is the default location for bundle. The usual
451
pattern is that the user can override it by specifying a
454
self.get_config().set_user_option('submit_branch', location)
456
def get_push_location(self):
457
"""Return the None or the location to push this branch to."""
458
raise NotImplementedError(self.get_push_location)
460
def set_push_location(self, location):
461
"""Set a new push location for this branch."""
462
raise NotImplementedError(self.set_push_location)
464
def set_parent(self, url):
465
raise NotImplementedError(self.set_parent)
469
"""Synchronise this branch with the master branch if any.
471
:return: None or the last_revision pivoted out during the update.
475
def check_revno(self, revno):
477
Check whether a revno corresponds to any revision.
478
Zero (the NULL revision) is considered valid.
481
self.check_real_revno(revno)
483
def check_real_revno(self, revno):
485
Check whether a revno corresponds to a real revision.
486
Zero (the NULL revision) is considered invalid
488
if revno < 1 or revno > self.revno():
489
raise InvalidRevisionNumber(revno)
492
def clone(self, *args, **kwargs):
493
"""Clone this branch into to_bzrdir preserving all semantic values.
495
revision_id: if not None, the revision history in the new branch will
496
be truncated to end with revision_id.
498
# for API compatibility, until 0.8 releases we provide the old api:
499
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
500
# after 0.8 releases, the *args and **kwargs should be changed:
501
# def clone(self, to_bzrdir, revision_id=None):
502
if (kwargs.get('to_location', None) or
503
kwargs.get('revision', None) or
504
kwargs.get('basis_branch', None) or
505
(len(args) and isinstance(args[0], basestring))):
506
# backwards compatibility api:
507
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
508
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
511
basis_branch = args[2]
513
basis_branch = kwargs.get('basis_branch', None)
515
basis = basis_branch.bzrdir
520
revision_id = args[1]
522
revision_id = kwargs.get('revision', None)
527
# no default to raise if not provided.
528
url = kwargs.get('to_location')
529
return self.bzrdir.clone(url,
530
revision_id=revision_id,
531
basis=basis).open_branch()
533
# generate args by hand
535
revision_id = args[1]
537
revision_id = kwargs.get('revision_id', None)
541
# no default to raise if not provided.
542
to_bzrdir = kwargs.get('to_bzrdir')
543
result = self._format.initialize(to_bzrdir)
544
self.copy_content_into(result, revision_id=revision_id)
548
def sprout(self, to_bzrdir, revision_id=None):
549
"""Create a new line of development from the branch, into to_bzrdir.
551
revision_id: if not None, the revision history in the new branch will
552
be truncated to end with revision_id.
554
result = self._format.initialize(to_bzrdir)
555
self.copy_content_into(result, revision_id=revision_id)
556
result.set_parent(self.bzrdir.root_transport.base)
559
def _synchronize_history(self, destination, revision_id):
560
new_history = self.revision_history()
561
if revision_id is not None:
563
new_history = new_history[:new_history.index(revision_id) + 1]
565
rev = self.repository.get_revision(revision_id)
566
new_history = rev.get_history(self.repository)[1:]
567
destination.set_revision_history(new_history)
570
def copy_content_into(self, destination, revision_id=None):
571
"""Copy the content of self into destination.
573
revision_id: if not None, the revision history in the new branch will
574
be truncated to end with revision_id.
576
self._synchronize_history(destination, revision_id)
578
parent = self.get_parent()
579
except errors.InaccessibleParent, e:
580
mutter('parent was not accessible to copy: %s', e)
583
destination.set_parent(parent)
587
"""Check consistency of the branch.
589
In particular this checks that revisions given in the revision-history
590
do actually match up in the revision graph, and that they're all
591
present in the repository.
593
Callers will typically also want to check the repository.
595
:return: A BranchCheckResult.
597
mainline_parent_id = None
598
for revision_id in self.revision_history():
600
revision = self.repository.get_revision(revision_id)
601
except errors.NoSuchRevision, e:
602
raise errors.BzrCheckError("mainline revision {%s} not in repository"
604
# In general the first entry on the revision history has no parents.
605
# But it's not illegal for it to have parents listed; this can happen
606
# in imports from Arch when the parents weren't reachable.
607
if mainline_parent_id is not None:
608
if mainline_parent_id not in revision.parent_ids:
609
raise errors.BzrCheckError("previous revision {%s} not listed among "
611
% (mainline_parent_id, revision_id))
612
mainline_parent_id = revision_id
613
return BranchCheckResult(self)
615
def _get_checkout_format(self):
616
"""Return the most suitable metadir for a checkout of this branch.
617
Weaves are used if this branch's repostory uses weaves.
619
if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
620
from bzrlib import repository
621
format = bzrdir.BzrDirMetaFormat1()
622
format.repository_format = repository.RepositoryFormat7()
624
format = self.repository.bzrdir.cloning_metadir()
625
format.branch_format = self._format
628
def create_checkout(self, to_location, revision_id=None,
630
"""Create a checkout of a branch.
632
:param to_location: The url to produce the checkout at
633
:param revision_id: The revision to check out
634
:param lightweight: If True, produce a lightweight checkout, otherwise,
635
produce a bound branch (heavyweight checkout)
636
:return: The tree of the created checkout
638
t = transport.get_transport(to_location)
641
except errors.FileExists:
644
checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
645
BranchReferenceFormat().initialize(checkout, self)
647
format = self._get_checkout_format()
648
checkout_branch = bzrdir.BzrDir.create_branch_convenience(
649
to_location, force_new_tree=False, format=format)
650
checkout = checkout_branch.bzrdir
651
checkout_branch.bind(self)
652
# pull up to the specified revision_id to set the initial
653
# branch tip correctly, and seed it with history.
654
checkout_branch.pull(self, stop_revision=revision_id)
655
return checkout.create_workingtree(revision_id)
658
class BranchFormat(object):
659
"""An encapsulation of the initialization and open routines for a format.
661
Formats provide three things:
662
* An initialization routine,
666
Formats are placed in an dict by their format string for reference
667
during branch opening. Its not required that these be instances, they
668
can be classes themselves with class methods - it simply depends on
669
whether state is needed for a given format or not.
671
Once a format is deprecated, just deprecate the initialize and open
672
methods on the format class. Do not deprecate the object, as the
673
object will be created every time regardless.
676
_default_format = None
677
"""The default format used for new branches."""
680
"""The known formats."""
683
def find_format(klass, a_bzrdir):
684
"""Return the format for the branch object in a_bzrdir."""
686
transport = a_bzrdir.get_branch_transport(None)
687
format_string = transport.get("format").read()
688
return klass._formats[format_string]
690
raise NotBranchError(path=transport.base)
692
raise errors.UnknownFormatError(format=format_string)
695
def get_default_format(klass):
696
"""Return the current default format."""
697
return klass._default_format
699
def get_format_string(self):
700
"""Return the ASCII format string that identifies this format."""
701
raise NotImplementedError(self.get_format_string)
703
def get_format_description(self):
704
"""Return the short format description for this format."""
705
raise NotImplementedError(self.get_format_description)
707
def initialize(self, a_bzrdir):
708
"""Create a branch of this format in a_bzrdir."""
709
raise NotImplementedError(self.initialize)
711
def is_supported(self):
712
"""Is this format supported?
714
Supported formats can be initialized and opened.
715
Unsupported formats may not support initialization or committing or
716
some other features depending on the reason for not being supported.
720
def open(self, a_bzrdir, _found=False):
721
"""Return the branch object for a_bzrdir
723
_found is a private parameter, do not use it. It is used to indicate
724
if format probing has already be done.
726
raise NotImplementedError(self.open)
729
def register_format(klass, format):
730
klass._formats[format.get_format_string()] = format
733
def set_default_format(klass, format):
734
klass._default_format = format
737
def unregister_format(klass, format):
738
assert klass._formats[format.get_format_string()] is format
739
del klass._formats[format.get_format_string()]
742
return self.get_format_string().rstrip()
745
class BranchHooks(dict):
746
"""A dictionary mapping hook name to a list of callables for branch hooks.
748
e.g. ['set_rh'] Is the list of items to be called when the
749
set_revision_history function is invoked.
753
"""Create the default hooks.
755
These are all empty initially, because by default nothing should get
759
# invoked whenever the revision history has been set
760
# with set_revision_history. The api signature is
761
# (branch, revision_history), and the branch will
762
# be write-locked. Introduced in 0.15.
765
def install_hook(self, hook_name, a_callable):
766
"""Install a_callable in to the hook hook_name.
768
:param hook_name: A hook name. See the __init__ method of BranchHooks
769
for the complete list of hooks.
770
:param a_callable: The callable to be invoked when the hook triggers.
771
The exact signature will depend on the hook - see the __init__
772
method of BranchHooks for details on each hook.
775
self[hook_name].append(a_callable)
777
raise errors.UnknownHook('branch', hook_name)
780
# install the default hooks into the Branch class.
781
Branch.hooks = BranchHooks()
784
class BzrBranchFormat4(BranchFormat):
785
"""Bzr branch format 4.
788
- a revision-history file.
789
- a branch-lock lock file [ to be shared with the bzrdir ]
792
def get_format_description(self):
793
"""See BranchFormat.get_format_description()."""
794
return "Branch format 4"
796
def initialize(self, a_bzrdir):
797
"""Create a branch of this format in a_bzrdir."""
798
mutter('creating branch in %s', a_bzrdir.transport.base)
799
branch_transport = a_bzrdir.get_branch_transport(self)
800
utf8_files = [('revision-history', ''),
803
control_files = lockable_files.LockableFiles(branch_transport,
804
'branch-lock', lockable_files.TransportLock)
805
control_files.create_lock()
806
control_files.lock_write()
808
for file, content in utf8_files:
809
control_files.put_utf8(file, content)
811
control_files.unlock()
812
return self.open(a_bzrdir, _found=True)
815
super(BzrBranchFormat4, self).__init__()
816
self._matchingbzrdir = bzrdir.BzrDirFormat6()
818
def open(self, a_bzrdir, _found=False):
819
"""Return the branch object for a_bzrdir
821
_found is a private parameter, do not use it. It is used to indicate
822
if format probing has already be done.
825
# we are being called directly and must probe.
826
raise NotImplementedError
827
return BzrBranch(_format=self,
828
_control_files=a_bzrdir._control_files,
830
_repository=a_bzrdir.open_repository())
833
return "Bazaar-NG branch format 4"
836
class BzrBranchFormat5(BranchFormat):
837
"""Bzr branch format 5.
840
- a revision-history file.
842
- a lock dir guarding the branch itself
843
- all of this stored in a branch/ subdirectory
844
- works with shared repositories.
846
This format is new in bzr 0.8.
849
def get_format_string(self):
850
"""See BranchFormat.get_format_string()."""
851
return "Bazaar-NG branch format 5\n"
853
def get_format_description(self):
854
"""See BranchFormat.get_format_description()."""
855
return "Branch format 5"
857
def initialize(self, a_bzrdir):
858
"""Create a branch of this format in a_bzrdir."""
859
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
860
branch_transport = a_bzrdir.get_branch_transport(self)
861
utf8_files = [('revision-history', ''),
864
control_files = lockable_files.LockableFiles(branch_transport, 'lock',
866
control_files.create_lock()
867
control_files.lock_write()
868
control_files.put_utf8('format', self.get_format_string())
870
for file, content in utf8_files:
871
control_files.put_utf8(file, content)
873
control_files.unlock()
874
return self.open(a_bzrdir, _found=True, )
877
super(BzrBranchFormat5, self).__init__()
878
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
880
def open(self, a_bzrdir, _found=False):
881
"""Return the branch object for a_bzrdir
883
_found is a private parameter, do not use it. It is used to indicate
884
if format probing has already be done.
887
format = BranchFormat.find_format(a_bzrdir)
888
assert format.__class__ == self.__class__
889
transport = a_bzrdir.get_branch_transport(None)
890
control_files = lockable_files.LockableFiles(transport, 'lock',
892
return BzrBranch5(_format=self,
893
_control_files=control_files,
895
_repository=a_bzrdir.find_repository())
898
return "Bazaar-NG Metadir branch format 5"
901
class BzrBranchFormat6(BzrBranchFormat5):
902
"""Branch format with last-revision
904
Unlike previous formats, this has no explicit revision history, instead
905
the left-parent revision history is used.
908
def get_format_string(self):
909
"""See BranchFormat.get_format_string()."""
910
return "Bazaar-NG branch format 6\n"
912
def get_format_description(self):
913
"""See BranchFormat.get_format_description()."""
914
return "Branch format 6"
916
def initialize(self, a_bzrdir):
917
"""Create a branch of this format in a_bzrdir."""
918
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
919
branch_transport = a_bzrdir.get_branch_transport(self)
920
utf8_files = [('last-revision', 'null:\n'),
924
control_files = lockable_files.LockableFiles(branch_transport, 'lock',
926
control_files.create_lock()
927
control_files.lock_write()
928
control_files.put_utf8('format', self.get_format_string())
930
for file, content in utf8_files:
931
control_files.put_utf8(file, content)
933
control_files.unlock()
934
return self.open(a_bzrdir, _found=True, )
936
def open(self, a_bzrdir, _found=False):
937
"""Return the branch object for a_bzrdir
939
_found is a private parameter, do not use it. It is used to indicate
940
if format probing has already be done.
943
format = BranchFormat.find_format(a_bzrdir)
944
assert format.__class__ == self.__class__
945
transport = a_bzrdir.get_branch_transport(None)
946
control_files = lockable_files.LockableFiles(transport, 'lock',
948
return BzrBranch6(_format=self,
949
_control_files=control_files,
951
_repository=a_bzrdir.find_repository())
954
class BranchReferenceFormat(BranchFormat):
955
"""Bzr branch reference format.
957
Branch references are used in implementing checkouts, they
958
act as an alias to the real branch which is at some other url.
965
def get_format_string(self):
966
"""See BranchFormat.get_format_string()."""
967
return "Bazaar-NG Branch Reference Format 1\n"
969
def get_format_description(self):
970
"""See BranchFormat.get_format_description()."""
971
return "Checkout reference format 1"
973
def initialize(self, a_bzrdir, target_branch=None):
974
"""Create a branch of this format in a_bzrdir."""
975
if target_branch is None:
976
# this format does not implement branch itself, thus the implicit
977
# creation contract must see it as uninitializable
978
raise errors.UninitializableFormat(self)
979
mutter('creating branch reference in %s', a_bzrdir.transport.base)
980
branch_transport = a_bzrdir.get_branch_transport(self)
981
branch_transport.put_bytes('location',
982
target_branch.bzrdir.root_transport.base)
983
branch_transport.put_bytes('format', self.get_format_string())
984
return self.open(a_bzrdir, _found=True)
987
super(BranchReferenceFormat, self).__init__()
988
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
990
def _make_reference_clone_function(format, a_branch):
991
"""Create a clone() routine for a branch dynamically."""
992
def clone(to_bzrdir, revision_id=None):
993
"""See Branch.clone()."""
994
return format.initialize(to_bzrdir, a_branch)
995
# cannot obey revision_id limits when cloning a reference ...
996
# FIXME RBC 20060210 either nuke revision_id for clone, or
997
# emit some sort of warning/error to the caller ?!
1000
def open(self, a_bzrdir, _found=False):
1001
"""Return the branch that the branch reference in a_bzrdir points at.
1003
_found is a private parameter, do not use it. It is used to indicate
1004
if format probing has already be done.
1007
format = BranchFormat.find_format(a_bzrdir)
1008
assert format.__class__ == self.__class__
1009
transport = a_bzrdir.get_branch_transport(None)
1010
real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
1011
result = real_bzrdir.open_branch()
1012
# this changes the behaviour of result.clone to create a new reference
1013
# rather than a copy of the content of the branch.
1014
# I did not use a proxy object because that needs much more extensive
1015
# testing, and we are only changing one behaviour at the moment.
1016
# If we decide to alter more behaviours - i.e. the implicit nickname
1017
# then this should be refactored to introduce a tested proxy branch
1018
# and a subclass of that for use in overriding clone() and ....
1020
result.clone = self._make_reference_clone_function(result)
1024
# formats which have no format string are not discoverable
1025
# and not independently creatable, so are not registered.
1026
__default_format = BzrBranchFormat5()
1027
BranchFormat.register_format(__default_format)
1028
BranchFormat.register_format(BranchReferenceFormat())
1029
BranchFormat.register_format(BzrBranchFormat6())
1030
BranchFormat.set_default_format(__default_format)
1031
_legacy_formats = [BzrBranchFormat4(),
1034
class BzrBranch(Branch):
1035
"""A branch stored in the actual filesystem.
1037
Note that it's "local" in the context of the filesystem; it doesn't
1038
really matter if it's on an nfs/smb/afs/coda/... share, as long as
1039
it's writable, and can be accessed via the normal filesystem API.
1042
def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
1043
relax_version_check=DEPRECATED_PARAMETER, _format=None,
1044
_control_files=None, a_bzrdir=None, _repository=None):
1045
"""Create new branch object at a particular location.
1047
transport -- A Transport object, defining how to access files.
1049
init -- If True, create new control files in a previously
1050
unversioned directory. If False, the branch must already
1053
relax_version_check -- If true, the usual check for the branch
1054
version is not applied. This is intended only for
1055
upgrade/recovery type use; it's not guaranteed that
1056
all operations will work on old format branches.
1058
if a_bzrdir is None:
1059
self.bzrdir = bzrdir.BzrDir.open(transport.base)
1061
self.bzrdir = a_bzrdir
1062
self._transport = self.bzrdir.transport.clone('..')
1063
self._base = self._transport.base
1064
self._format = _format
1065
if _control_files is None:
1066
raise ValueError('BzrBranch _control_files is None')
1067
self.control_files = _control_files
1068
if deprecated_passed(init):
1069
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
1070
"deprecated as of bzr 0.8. Please use Branch.create().",
1074
# this is slower than before deprecation, oh well never mind.
1075
# -> its deprecated.
1076
self._initialize(transport.base)
1077
self._check_format(_format)
1078
if deprecated_passed(relax_version_check):
1079
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
1080
"relax_version_check parameter is deprecated as of bzr 0.8. "
1081
"Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
1085
if (not relax_version_check
1086
and not self._format.is_supported()):
1087
raise errors.UnsupportedFormatError(format=fmt)
1088
if deprecated_passed(transport):
1089
warn("BzrBranch.__init__(transport=XXX...): The transport "
1090
"parameter is deprecated as of bzr 0.8. "
1091
"Please use Branch.open, or bzrdir.open_branch().",
1094
self.repository = _repository
1097
return '%s(%r)' % (self.__class__.__name__, self.base)
1101
def _get_base(self):
1104
base = property(_get_base, doc="The URL for the root of this branch.")
1106
def _finish_transaction(self):
1107
"""Exit the current transaction."""
1108
return self.control_files._finish_transaction()
1110
def get_transaction(self):
1111
"""Return the current active transaction.
1113
If no transaction is active, this returns a passthrough object
1114
for which all data is immediately flushed and no caching happens.
1116
# this is an explicit function so that we can do tricky stuff
1117
# when the storage in rev_storage is elsewhere.
1118
# we probably need to hook the two 'lock a location' and
1119
# 'have a transaction' together more delicately, so that
1120
# we can have two locks (branch and storage) and one transaction
1121
# ... and finishing the transaction unlocks both, but unlocking
1122
# does not. - RBC 20051121
1123
return self.control_files.get_transaction()
1125
def _set_transaction(self, transaction):
1126
"""Set a new active transaction."""
1127
return self.control_files._set_transaction(transaction)
1129
def abspath(self, name):
1130
"""See Branch.abspath."""
1131
return self.control_files._transport.abspath(name)
1133
def _check_format(self, format):
1134
"""Identify the branch format if needed.
1136
The format is stored as a reference to the format object in
1137
self._format for code that needs to check it later.
1139
The format parameter is either None or the branch format class
1140
used to open this branch.
1142
FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
1145
format = BranchFormat.find_format(self.bzrdir)
1146
self._format = format
1147
mutter("got branch format %s", self._format)
1150
def get_root_id(self):
1151
"""See Branch.get_root_id."""
1152
tree = self.repository.revision_tree(self.last_revision())
1153
return tree.inventory.root.file_id
1155
def is_locked(self):
1156
return self.control_files.is_locked()
1158
def lock_write(self):
1159
self.repository.lock_write()
1161
self.control_files.lock_write()
1163
self.repository.unlock()
1166
def lock_read(self):
1167
self.repository.lock_read()
1169
self.control_files.lock_read()
1171
self.repository.unlock()
1175
# TODO: test for failed two phase locks. This is known broken.
1177
self.control_files.unlock()
1179
self.repository.unlock()
1181
def peek_lock_mode(self):
1182
if self.control_files._lock_count == 0:
1185
return self.control_files._lock_mode
1187
def get_physical_lock_status(self):
1188
return self.control_files.get_physical_lock_status()
1191
def print_file(self, file, revision_id):
1192
"""See Branch.print_file."""
1193
return self.repository.print_file(file, revision_id)
1196
def append_revision(self, *revision_ids):
1197
"""See Branch.append_revision."""
1198
for revision_id in revision_ids:
1199
_mod_revision.check_not_reserved_id(revision_id)
1200
mutter("add {%s} to revision-history" % revision_id)
1201
rev_history = self.revision_history()
1202
rev_history.extend(revision_ids)
1203
self.set_revision_history(rev_history)
1206
def set_revision_history(self, rev_history):
1207
"""See Branch.set_revision_history."""
1208
self.control_files.put_utf8(
1209
'revision-history', '\n'.join(rev_history))
1210
transaction = self.get_transaction()
1211
history = transaction.map.find_revision_history()
1212
if history is not None:
1213
# update the revision history in the identity map.
1214
history[:] = list(rev_history)
1215
# this call is disabled because revision_history is
1216
# not really an object yet, and the transaction is for objects.
1217
# transaction.register_dirty(history)
1219
transaction.map.add_revision_history(rev_history)
1220
# this call is disabled because revision_history is
1221
# not really an object yet, and the transaction is for objects.
1222
# transaction.register_clean(history)
1223
for hook in Branch.hooks['set_rh']:
1224
hook(self, rev_history)
1227
def set_last_revision(self, revision_id):
1228
self.set_revision_history(self._lefthand_history(revision_id))
1231
def revision_history(self):
1232
"""See Branch.revision_history."""
1233
transaction = self.get_transaction()
1234
history = transaction.map.find_revision_history()
1235
if history is not None:
1236
# mutter("cache hit for revision-history in %s", self)
1237
return list(history)
1238
decode_utf8 = cache_utf8.decode
1239
history = [decode_utf8(l.rstrip('\r\n')) for l in
1240
self.control_files.get('revision-history').readlines()]
1241
transaction.map.add_revision_history(history)
1242
# this call is disabled because revision_history is
1243
# not really an object yet, and the transaction is for objects.
1244
# transaction.register_clean(history, precious=True)
1245
return list(history)
1247
def _lefthand_history(self, revision_id, last_rev=None,
1249
# stop_revision must be a descendant of last_revision
1250
stop_graph = self.repository.get_revision_graph(revision_id)
1251
if last_rev is not None and last_rev not in stop_graph:
1252
# our previous tip is not merged into stop_revision
1253
raise errors.DivergedBranches(self, other_branch)
1254
# make a new revision history from the graph
1255
current_rev_id = revision_id
1257
while current_rev_id not in (None, _mod_revision.NULL_REVISION):
1258
new_history.append(current_rev_id)
1259
current_rev_id_parents = stop_graph[current_rev_id]
1261
current_rev_id = current_rev_id_parents[0]
1263
current_rev_id = None
1264
new_history.reverse()
1268
def generate_revision_history(self, revision_id, last_rev=None,
1270
"""Create a new revision history that will finish with revision_id.
1272
:param revision_id: the new tip to use.
1273
:param last_rev: The previous last_revision. If not None, then this
1274
must be a ancestory of revision_id, or DivergedBranches is raised.
1275
:param other_branch: The other branch that DivergedBranches should
1276
raise with respect to.
1278
self.set_revision_history(self._lefthand_history(revision_id,
1279
last_rev, other_branch))
1282
def update_revisions(self, other, stop_revision=None):
1283
"""See Branch.update_revisions."""
1286
if stop_revision is None:
1287
stop_revision = other.last_revision()
1288
if stop_revision is None:
1289
# if there are no commits, we're done.
1291
# whats the current last revision, before we fetch [and change it
1293
last_rev = self.last_revision()
1294
# we fetch here regardless of whether we need to so that we pickup
1296
self.fetch(other, stop_revision)
1297
my_ancestry = self.repository.get_ancestry(last_rev)
1298
if stop_revision in my_ancestry:
1299
# last_revision is a descendant of stop_revision
1301
self.generate_revision_history(stop_revision, last_rev=last_rev,
1306
def basis_tree(self):
1307
"""See Branch.basis_tree."""
1308
return self.repository.revision_tree(self.last_revision())
1310
@deprecated_method(zero_eight)
1311
def working_tree(self):
1312
"""Create a Working tree object for this branch."""
1314
from bzrlib.transport.local import LocalTransport
1315
if (self.base.find('://') != -1 or
1316
not isinstance(self._transport, LocalTransport)):
1317
raise NoWorkingTree(self.base)
1318
return self.bzrdir.open_workingtree()
1321
def pull(self, source, overwrite=False, stop_revision=None):
1322
"""See Branch.pull."""
1325
old_count = self.last_revision_info()[0]
1327
self.update_revisions(source, stop_revision)
1328
except DivergedBranches:
1332
self.set_revision_history(source.revision_history())
1333
new_count = self.last_revision_info()[0]
1334
return new_count - old_count
1338
def _get_parent_location(self):
1339
_locs = ['parent', 'pull', 'x-pull']
1342
return self.control_files.get(l).read().strip('\n')
1348
def push(self, target, overwrite=False, stop_revision=None):
1349
"""See Branch.push."""
1352
old_count = len(target.revision_history())
1354
target.update_revisions(self, stop_revision)
1355
except DivergedBranches:
1359
target.set_revision_history(self.revision_history())
1360
new_count = len(target.revision_history())
1361
return new_count - old_count
1365
def get_parent(self):
1366
"""See Branch.get_parent."""
1368
assert self.base[-1] == '/'
1369
parent = self._get_parent_location()
1372
# This is an old-format absolute path to a local branch
1373
# turn it into a url
1374
if parent.startswith('/'):
1375
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1377
return urlutils.join(self.base[:-1], parent)
1378
except errors.InvalidURLJoin, e:
1379
raise errors.InaccessibleParent(parent, self.base)
1381
def get_push_location(self):
1382
"""See Branch.get_push_location."""
1383
push_loc = self.get_config().get_user_option('push_location')
1386
def set_push_location(self, location):
1387
"""See Branch.set_push_location."""
1388
self.get_config().set_user_option(
1389
'push_location', location,
1390
store=_mod_config.STORE_LOCATION_NORECURSE)
1393
def set_parent(self, url):
1394
"""See Branch.set_parent."""
1395
# TODO: Maybe delete old location files?
1396
# URLs should never be unicode, even on the local fs,
1397
# FIXUP this and get_parent in a future branch format bump:
1398
# read and rewrite the file, and have the new format code read
1399
# using .get not .get_utf8. RBC 20060125
1401
if isinstance(url, unicode):
1403
url = url.encode('ascii')
1404
except UnicodeEncodeError:
1405
raise bzrlib.errors.InvalidURL(url,
1406
"Urls must be 7-bit ascii, "
1407
"use bzrlib.urlutils.escape")
1409
url = urlutils.relative_url(self.base, url)
1410
self._set_parent_location(url)
1412
def _set_parent_location(self, url):
1414
self.control_files._transport.delete('parent')
1416
assert isinstance(url, str)
1417
self.control_files.put('parent', StringIO(url + '\n'))
1419
@deprecated_function(zero_nine)
1420
def tree_config(self):
1421
"""DEPRECATED; call get_config instead.
1422
TreeConfig has become part of BranchConfig."""
1423
return TreeConfig(self)
1426
class BzrBranch5(BzrBranch):
1427
"""A format 5 branch. This supports new features over plan branches.
1429
It has support for a master_branch which is the data for bound branches.
1437
super(BzrBranch5, self).__init__(_format=_format,
1438
_control_files=_control_files,
1440
_repository=_repository)
1443
def pull(self, source, overwrite=False, stop_revision=None):
1444
"""Extends branch.pull to be bound branch aware."""
1445
bound_location = self.get_bound_location()
1446
if source.base != bound_location:
1447
# not pulling from master, so we need to update master.
1448
master_branch = self.get_master_branch()
1450
master_branch.pull(source)
1451
source = master_branch
1452
return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
1455
def push(self, target, overwrite=False, stop_revision=None):
1456
"""Updates branch.push to be bound branch aware."""
1457
bound_location = target.get_bound_location()
1458
if target.base != bound_location:
1459
# not pushing to master, so we need to update master.
1460
master_branch = target.get_master_branch()
1462
# push into the master from this branch.
1463
super(BzrBranch5, self).push(master_branch, overwrite,
1465
# and push into the target branch from this. Note that we push from
1466
# this branch again, because its considered the highest bandwidth
1468
return super(BzrBranch5, self).push(target, overwrite, stop_revision)
1470
def get_bound_location(self):
1472
return self.control_files.get_utf8('bound').read()[:-1]
1473
except errors.NoSuchFile:
1477
def get_master_branch(self):
1478
"""Return the branch we are bound to.
1480
:return: Either a Branch, or None
1482
This could memoise the branch, but if thats done
1483
it must be revalidated on each new lock.
1484
So for now we just don't memoise it.
1485
# RBC 20060304 review this decision.
1487
bound_loc = self.get_bound_location()
1491
return Branch.open(bound_loc)
1492
except (errors.NotBranchError, errors.ConnectionError), e:
1493
raise errors.BoundBranchConnectionFailure(
1497
def set_bound_location(self, location):
1498
"""Set the target where this branch is bound to.
1500
:param location: URL to the target branch
1503
self.control_files.put_utf8('bound', location+'\n')
1506
self.control_files._transport.delete('bound')
1512
def bind(self, other):
1513
"""Bind this branch to the branch other.
1515
This does not push or pull data between the branches, though it does
1516
check for divergence to raise an error when the branches are not
1517
either the same, or one a prefix of the other. That behaviour may not
1518
be useful, so that check may be removed in future.
1520
:param other: The branch to bind to
1523
# TODO: jam 20051230 Consider checking if the target is bound
1524
# It is debatable whether you should be able to bind to
1525
# a branch which is itself bound.
1526
# Committing is obviously forbidden,
1527
# but binding itself may not be.
1528
# Since we *have* to check at commit time, we don't
1529
# *need* to check here
1531
# we want to raise diverged if:
1532
# last_rev is not in the other_last_rev history, AND
1533
# other_last_rev is not in our history, and do it without pulling
1535
last_rev = self.last_revision()
1536
if last_rev is not None:
1539
other_last_rev = other.last_revision()
1540
if other_last_rev is not None:
1541
# neither branch is new, we have to do some work to
1542
# ascertain diversion.
1543
remote_graph = other.repository.get_revision_graph(
1545
local_graph = self.repository.get_revision_graph(last_rev)
1546
if (last_rev not in remote_graph and
1547
other_last_rev not in local_graph):
1548
raise errors.DivergedBranches(self, other)
1551
self.set_bound_location(other.base)
1555
"""If bound, unbind"""
1556
return self.set_bound_location(None)
1560
"""Synchronise this branch with the master branch if any.
1562
:return: None or the last_revision that was pivoted out during the
1565
master = self.get_master_branch()
1566
if master is not None:
1567
old_tip = self.last_revision()
1568
self.pull(master, overwrite=True)
1569
if old_tip in self.repository.get_ancestry(self.last_revision()):
1575
class BzrBranch6(BzrBranch5):
1578
def last_revision(self):
1579
"""Return last revision id, or None"""
1580
revision_id = self.control_files.get_utf8('last-revision').read()
1581
revision_id = revision_id.rstrip('\n')
1582
if revision_id == _mod_revision.NULL_REVISION:
1587
def set_last_revision(self, revision_id):
1588
if revision_id is None:
1589
revision_id = 'null:'
1590
self.control_files.put_utf8('last-revision', revision_id + '\n')
1593
def revision_history(self):
1594
"""Generate the revision history from last revision
1596
return self._lefthand_history(self.last_revision())
1599
def set_revision_history(self, history):
1600
"""Set the last_revision, not revision history"""
1601
if len(history) == 0:
1602
self.set_last_revision('null:')
1604
assert history == self._lefthand_history(history[-1])
1605
self.set_last_revision(history[-1])
1606
for hook in Branch.hooks['set_rh']:
1610
def append_revision(self, *revision_ids):
1611
if len(revision_ids) == 0:
1613
prev_revision = self.last_revision()
1614
for revision in self.repository.get_revisions(revision_ids):
1615
if prev_revision is None:
1616
assert revision.parent_ids == []
1618
assert revision.parent_ids[0] == prev_revision
1619
prev_revision = revision.revision_id
1620
self.set_last_revision(revision_ids[-1])
1623
def _set_parent_location(self, url):
1624
"""Set the parent branch"""
1627
url = urlutils.relative_url(self.base, url)
1628
self.get_config().set_user_option('parent_location', url)
1631
def _get_parent_location(self):
1632
"""Set the parent branch"""
1633
parent = self.get_config().get_user_option('parent_location')
1638
def set_push_location(self, location):
1639
"""See Branch.set_push_location."""
1640
self.get_config().set_user_option('push_location', location)
1642
def set_bound_location(self, location):
1643
"""See Branch.set_push_location."""
1645
if location is None:
1646
if self.get_bound_location() is None:
1650
self.get_config().set_user_option('bound_location', location)
1653
def get_bound_location(self):
1654
"""See Branch.set_push_location."""
1655
location = self.get_config().get_user_option('bound_location')
1660
def _synchronize_history(self, destination, revision_id):
1661
if revision_id is None:
1662
revision_id = self.last_revision()
1663
destination.set_last_revision(revision_id)
1665
class BranchTestProviderAdapter(object):
1666
"""A tool to generate a suite testing multiple branch formats at once.
1668
This is done by copying the test once for each transport and injecting
1669
the transport_server, transport_readonly_server, and branch_format
1670
classes into each copy. Each copy is also given a new id() to make it
1674
def __init__(self, transport_server, transport_readonly_server, formats):
1675
self._transport_server = transport_server
1676
self._transport_readonly_server = transport_readonly_server
1677
self._formats = formats
1679
def adapt(self, test):
1680
result = TestSuite()
1681
for branch_format, bzrdir_format in self._formats:
1682
new_test = deepcopy(test)
1683
new_test.transport_server = self._transport_server
1684
new_test.transport_readonly_server = self._transport_readonly_server
1685
new_test.bzrdir_format = bzrdir_format
1686
new_test.branch_format = branch_format
1687
def make_new_test_id():
1688
new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1689
return lambda: new_id
1690
new_test.id = make_new_test_id()
1691
result.addTest(new_test)
1695
class BranchCheckResult(object):
1696
"""Results of checking branch consistency.
1701
def __init__(self, branch):
1702
self.branch = branch
1704
def report_results(self, verbose):
1705
"""Report the check results via trace.note.
1707
:param verbose: Requests more detailed display of what was checked,
1710
note('checked branch %s format %s',
1712
self.branch._format)
1715
######################################################################
1719
@deprecated_function(zero_eight)
1720
def is_control_file(*args, **kwargs):
1721
"""See bzrlib.workingtree.is_control_file."""
1722
from bzrlib import workingtree
1723
return workingtree.is_control_file(*args, **kwargs)
1726
class Converter5to6(object):
1727
"""Perform an in-place upgrade of format 5 to format 6"""
1729
def convert(self, branch):
1730
# Data for 5 and 6 can peacefully coexist.
1731
format = BzrBranchFormat6()
1732
new_branch = format.open(branch.bzrdir, _found=True)
1734
# Copy source data into target
1735
new_branch.set_last_revision(branch.last_revision())
1736
new_branch.set_parent(branch.get_parent())
1737
new_branch.set_bound_location(branch.get_bound_location())
1738
new_branch.set_push_location(branch.get_push_location())
1740
# Copying done; now update target format
1741
new_branch.control_files.put_utf8('format',
1742
format.get_format_string())
1744
# Clean up old files
1745
new_branch.control_files._transport.delete('revision-history')
1747
branch.set_parent(None)
1750
branch.set_bound_location(None)