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.symbol_versioning import (deprecated_function,
61
zero_eight, zero_nine, zero_sixteen,
63
from bzrlib.trace import mutter, note
66
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
67
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
68
BZR_BRANCH_FORMAT_6 = "Bazaar Branch Format 6 (bzr 0.15)\n"
71
# TODO: Maybe include checks for common corruption of newlines, etc?
73
# TODO: Some operations like log might retrieve the same revisions
74
# repeatedly to calculate deltas. We could perhaps have a weakref
75
# cache in memory to make this faster. In general anything can be
76
# cached in memory between lock and unlock operations. .. nb thats
77
# what the transaction identity map provides
80
######################################################################
84
"""Branch holding a history of revisions.
87
Base directory/url of the branch.
89
hooks: An instance of BranchHooks.
91
# this is really an instance variable - FIXME move it there
95
# override this to set the strategy for storing tags
97
return DisabledTags(self)
99
def __init__(self, *ignored, **ignored_too):
100
self.tags = self._make_tags()
101
self._revision_history_cache = None
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 leave_lock_in_place(self):
198
"""Tell this branch object not to release the physical lock when this
201
If lock_write doesn't return a token, then this method is not supported.
203
self.control_files.leave_in_place()
205
def dont_leave_lock_in_place(self):
206
"""Tell this branch object to release the physical lock when this
207
object is unlocked, even if it didn't originally acquire it.
209
If lock_write doesn't return a token, then this method is not supported.
211
self.control_files.dont_leave_in_place()
213
def abspath(self, name):
214
"""Return absolute filename for something in the branch
216
XXX: Robert Collins 20051017 what is this used for? why is it a branch
217
method and not a tree method.
219
raise NotImplementedError(self.abspath)
221
def bind(self, other):
222
"""Bind the local branch the other branch.
224
:param other: The branch to bind to
227
raise errors.UpgradeRequired(self.base)
230
def fetch(self, from_branch, last_revision=None, pb=None):
231
"""Copy revisions from from_branch into this branch.
233
:param from_branch: Where to copy from.
234
:param last_revision: What revision to stop at (None for at the end
236
:param pb: An optional progress bar to use.
238
Returns the copied revision count and the failed revisions in a tuple:
241
if self.base == from_branch.base:
244
nested_pb = ui.ui_factory.nested_progress_bar()
249
from_branch.lock_read()
251
if last_revision is None:
252
pb.update('get source history')
253
last_revision = from_branch.last_revision()
254
if last_revision is None:
255
last_revision = _mod_revision.NULL_REVISION
256
return self.repository.fetch(from_branch.repository,
257
revision_id=last_revision,
260
if nested_pb is not None:
264
def get_bound_location(self):
265
"""Return the URL of the branch we are bound to.
267
Older format branches cannot bind, please be sure to use a metadir
272
def get_old_bound_location(self):
273
"""Return the URL of the branch we used to be bound to
275
raise errors.UpgradeRequired(self.base)
277
def get_commit_builder(self, parents, config=None, timestamp=None,
278
timezone=None, committer=None, revprops=None,
280
"""Obtain a CommitBuilder for this branch.
282
:param parents: Revision ids of the parents of the new revision.
283
:param config: Optional configuration to use.
284
:param timestamp: Optional timestamp recorded for commit.
285
:param timezone: Optional timezone for timestamp.
286
:param committer: Optional committer to set for commit.
287
:param revprops: Optional dictionary of revision properties.
288
:param revision_id: Optional revision id.
292
config = self.get_config()
294
return self.repository.get_commit_builder(self, parents, config,
295
timestamp, timezone, committer, revprops, revision_id)
297
def get_master_branch(self):
298
"""Return the branch we are bound to.
300
:return: Either a Branch, or None
304
def get_revision_delta(self, revno):
305
"""Return the delta for one revision.
307
The delta is relative to its mainline predecessor, or the
308
empty tree for revision 1.
310
assert isinstance(revno, int)
311
rh = self.revision_history()
312
if not (1 <= revno <= len(rh)):
313
raise InvalidRevisionNumber(revno)
314
return self.repository.get_revision_delta(rh[revno-1])
316
@deprecated_method(zero_sixteen)
317
def get_root_id(self):
318
"""Return the id of this branches root
320
Deprecated: branches don't have root ids-- trees do.
321
Use basis_tree().get_root_id() instead.
323
raise NotImplementedError(self.get_root_id)
325
def print_file(self, file, revision_id):
326
"""Print `file` to stdout."""
327
raise NotImplementedError(self.print_file)
329
def append_revision(self, *revision_ids):
330
raise NotImplementedError(self.append_revision)
332
def set_revision_history(self, rev_history):
333
raise NotImplementedError(self.set_revision_history)
335
def _cache_revision_history(self, rev_history):
336
"""Set the cached revision history to rev_history.
338
The revision_history method will use this cache to avoid regenerating
339
the revision history.
341
This API is semi-public; it only for use by subclasses, all other code
342
should consider it to be private.
344
self._revision_history_cache = rev_history
346
def _clear_cached_state(self):
347
"""Clear any cached data on this branch, e.g. cached revision history.
349
This means the next call to revision_history will need to call
350
_gen_revision_history.
352
This API is semi-public; it only for use by subclasses, all other code
353
should consider it to be private.
355
self._revision_history_cache = None
357
def _gen_revision_history(self):
358
"""Return sequence of revision hashes on to this branch.
360
Unlike revision_history, this method always regenerates or rereads the
361
revision history, i.e. it does not cache the result, so repeated calls
364
Concrete subclasses should override this instead of revision_history so
365
that subclasses do not need to deal with caching logic.
367
This API is semi-public; it only for use by subclasses, all other code
368
should consider it to be private.
370
raise NotImplementedError(self._gen_revision_history)
373
def revision_history(self):
374
"""Return sequence of revision hashes on to this branch.
376
This method will cache the revision history for as long as it is safe to
379
if self._revision_history_cache is not None:
380
history = self._revision_history_cache
382
history = self._gen_revision_history()
383
self._cache_revision_history(history)
387
"""Return current revision number for this branch.
389
That is equivalent to the number of revisions committed to
392
return len(self.revision_history())
395
"""Older format branches cannot bind or unbind."""
396
raise errors.UpgradeRequired(self.base)
398
def set_append_revisions_only(self, enabled):
399
"""Older format branches are never restricted to append-only"""
400
raise errors.UpgradeRequired(self.base)
402
def last_revision(self):
403
"""Return last revision id, or None"""
404
ph = self.revision_history()
410
def last_revision_info(self):
411
"""Return information about the last revision.
413
:return: A tuple (revno, last_revision_id).
415
rh = self.revision_history()
418
return (revno, rh[-1])
420
return (0, _mod_revision.NULL_REVISION)
422
def missing_revisions(self, other, stop_revision=None):
423
"""Return a list of new revisions that would perfectly fit.
425
If self and other have not diverged, return a list of the revisions
426
present in other, but missing from self.
428
self_history = self.revision_history()
429
self_len = len(self_history)
430
other_history = other.revision_history()
431
other_len = len(other_history)
432
common_index = min(self_len, other_len) -1
433
if common_index >= 0 and \
434
self_history[common_index] != other_history[common_index]:
435
raise DivergedBranches(self, other)
437
if stop_revision is None:
438
stop_revision = other_len
440
assert isinstance(stop_revision, int)
441
if stop_revision > other_len:
442
raise errors.NoSuchRevision(self, stop_revision)
443
return other_history[self_len:stop_revision]
445
def update_revisions(self, other, stop_revision=None):
446
"""Pull in new perfect-fit revisions.
448
:param other: Another Branch to pull from
449
:param stop_revision: Updated until the given revision
452
raise NotImplementedError(self.update_revisions)
454
def revision_id_to_revno(self, revision_id):
455
"""Given a revision id, return its revno"""
456
if revision_id is None:
458
revision_id = osutils.safe_revision_id(revision_id)
459
history = self.revision_history()
461
return history.index(revision_id) + 1
463
raise bzrlib.errors.NoSuchRevision(self, revision_id)
465
def get_rev_id(self, revno, history=None):
466
"""Find the revision id of the specified revno."""
470
history = self.revision_history()
471
if revno <= 0 or revno > len(history):
472
raise bzrlib.errors.NoSuchRevision(self, revno)
473
return history[revno - 1]
475
def pull(self, source, overwrite=False, stop_revision=None):
476
"""Mirror source into this branch.
478
This branch is considered to be 'local', having low latency.
480
:returns: PullResult instance
482
raise NotImplementedError(self.pull)
484
def push(self, target, overwrite=False, stop_revision=None):
485
"""Mirror this branch into target.
487
This branch is considered to be 'local', having low latency.
489
raise NotImplementedError(self.push)
491
def basis_tree(self):
492
"""Return `Tree` object for last revision."""
493
return self.repository.revision_tree(self.last_revision())
495
def rename_one(self, from_rel, to_rel):
498
This can change the directory or the filename or both.
500
raise NotImplementedError(self.rename_one)
502
def move(self, from_paths, to_name):
505
to_name must exist as a versioned directory.
507
If to_name exists and is a directory, the files are moved into
508
it, keeping their old names. If it is a directory,
510
Note that to_name is only the last component of the new name;
511
this doesn't change the directory.
513
This returns a list of (from_path, to_path) pairs for each
516
raise NotImplementedError(self.move)
518
def get_parent(self):
519
"""Return the parent location of the branch.
521
This is the default location for push/pull/missing. The usual
522
pattern is that the user can override it by specifying a
525
raise NotImplementedError(self.get_parent)
527
def _set_config_location(self, name, url, config=None,
528
make_relative=False):
530
config = self.get_config()
534
url = urlutils.relative_url(self.base, url)
535
config.set_user_option(name, url)
537
def _get_config_location(self, name, config=None):
539
config = self.get_config()
540
location = config.get_user_option(name)
545
def get_submit_branch(self):
546
"""Return the submit location of the branch.
548
This is the default location for bundle. The usual
549
pattern is that the user can override it by specifying a
552
return self.get_config().get_user_option('submit_branch')
554
def set_submit_branch(self, location):
555
"""Return the submit location of the branch.
557
This is the default location for bundle. The usual
558
pattern is that the user can override it by specifying a
561
self.get_config().set_user_option('submit_branch', location)
563
def get_public_branch(self):
564
"""Return the public location of the branch.
566
This is is used by merge directives.
568
return self._get_config_location('public_branch')
570
def set_public_branch(self, location):
571
"""Return the submit location of the branch.
573
This is the default location for bundle. The usual
574
pattern is that the user can override it by specifying a
577
self._set_config_location('public_branch', location)
579
def get_push_location(self):
580
"""Return the None or the location to push this branch to."""
581
push_loc = self.get_config().get_user_option('push_location')
584
def set_push_location(self, location):
585
"""Set a new push location for this branch."""
586
raise NotImplementedError(self.set_push_location)
588
def set_parent(self, url):
589
raise NotImplementedError(self.set_parent)
593
"""Synchronise this branch with the master branch if any.
595
:return: None or the last_revision pivoted out during the update.
599
def check_revno(self, revno):
601
Check whether a revno corresponds to any revision.
602
Zero (the NULL revision) is considered valid.
605
self.check_real_revno(revno)
607
def check_real_revno(self, revno):
609
Check whether a revno corresponds to a real revision.
610
Zero (the NULL revision) is considered invalid
612
if revno < 1 or revno > self.revno():
613
raise InvalidRevisionNumber(revno)
616
def clone(self, to_bzrdir, revision_id=None):
617
"""Clone this branch into to_bzrdir preserving all semantic values.
619
revision_id: if not None, the revision history in the new branch will
620
be truncated to end with revision_id.
622
result = self._format.initialize(to_bzrdir)
623
self.copy_content_into(result, revision_id=revision_id)
627
def sprout(self, to_bzrdir, revision_id=None):
628
"""Create a new line of development from the branch, into to_bzrdir.
630
revision_id: if not None, the revision history in the new branch will
631
be truncated to end with revision_id.
633
result = self._format.initialize(to_bzrdir)
634
self.copy_content_into(result, revision_id=revision_id)
635
result.set_parent(self.bzrdir.root_transport.base)
638
def _synchronize_history(self, destination, revision_id):
639
"""Synchronize last revision and revision history between branches.
641
This version is most efficient when the destination is also a
642
BzrBranch5, but works for BzrBranch6 as long as the revision
643
history is the true lefthand parent history, and all of the revisions
644
are in the destination's repository. If not, set_revision_history
647
:param destination: The branch to copy the history into
648
:param revision_id: The revision-id to truncate history at. May
649
be None to copy complete history.
651
new_history = self.revision_history()
652
if revision_id is not None:
653
revision_id = osutils.safe_revision_id(revision_id)
655
new_history = new_history[:new_history.index(revision_id) + 1]
657
rev = self.repository.get_revision(revision_id)
658
new_history = rev.get_history(self.repository)[1:]
659
destination.set_revision_history(new_history)
662
def copy_content_into(self, destination, revision_id=None):
663
"""Copy the content of self into destination.
665
revision_id: if not None, the revision history in the new branch will
666
be truncated to end with revision_id.
668
self._synchronize_history(destination, revision_id)
670
parent = self.get_parent()
671
except errors.InaccessibleParent, e:
672
mutter('parent was not accessible to copy: %s', e)
675
destination.set_parent(parent)
676
self.tags.merge_to(destination.tags)
680
"""Check consistency of the branch.
682
In particular this checks that revisions given in the revision-history
683
do actually match up in the revision graph, and that they're all
684
present in the repository.
686
Callers will typically also want to check the repository.
688
:return: A BranchCheckResult.
690
mainline_parent_id = None
691
for revision_id in self.revision_history():
693
revision = self.repository.get_revision(revision_id)
694
except errors.NoSuchRevision, e:
695
raise errors.BzrCheckError("mainline revision {%s} not in repository"
697
# In general the first entry on the revision history has no parents.
698
# But it's not illegal for it to have parents listed; this can happen
699
# in imports from Arch when the parents weren't reachable.
700
if mainline_parent_id is not None:
701
if mainline_parent_id not in revision.parent_ids:
702
raise errors.BzrCheckError("previous revision {%s} not listed among "
704
% (mainline_parent_id, revision_id))
705
mainline_parent_id = revision_id
706
return BranchCheckResult(self)
708
def _get_checkout_format(self):
709
"""Return the most suitable metadir for a checkout of this branch.
710
Weaves are used if this branch's repository uses weaves.
712
from bzrlib.remote import RemoteBzrDir
713
if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
714
from bzrlib.repofmt import weaverepo
715
format = bzrdir.BzrDirMetaFormat1()
716
format.repository_format = weaverepo.RepositoryFormat7()
717
elif isinstance(self.bzrdir, RemoteBzrDir):
718
format = bzrdir.BzrDirMetaFormat1()
720
format = self.repository.bzrdir.checkout_metadir()
721
format.set_branch_format(self._format)
724
def create_checkout(self, to_location, revision_id=None,
726
"""Create a checkout of a branch.
728
:param to_location: The url to produce the checkout at
729
:param revision_id: The revision to check out
730
:param lightweight: If True, produce a lightweight checkout, otherwise,
731
produce a bound branch (heavyweight checkout)
732
:return: The tree of the created checkout
734
t = transport.get_transport(to_location)
737
except errors.FileExists:
740
format = self._get_checkout_format()
741
checkout = format.initialize_on_transport(t)
742
BranchReferenceFormat().initialize(checkout, self)
744
format = self._get_checkout_format()
745
checkout_branch = bzrdir.BzrDir.create_branch_convenience(
746
to_location, force_new_tree=False, format=format)
747
checkout = checkout_branch.bzrdir
748
checkout_branch.bind(self)
749
# pull up to the specified revision_id to set the initial
750
# branch tip correctly, and seed it with history.
751
checkout_branch.pull(self, stop_revision=revision_id)
752
tree = checkout.create_workingtree(revision_id)
753
basis_tree = tree.basis_tree()
754
basis_tree.lock_read()
756
for path, file_id in basis_tree.iter_references():
757
reference_parent = self.reference_parent(file_id, path)
758
reference_parent.create_checkout(tree.abspath(path),
759
basis_tree.get_reference_revision(file_id, path),
765
def reference_parent(self, file_id, path):
766
"""Return the parent branch for a tree-reference file_id
767
:param file_id: The file_id of the tree reference
768
:param path: The path of the file_id in the tree
769
:return: A branch associated with the file_id
771
# FIXME should provide multiple branches, based on config
772
return Branch.open(self.bzrdir.root_transport.clone(path).base)
774
def supports_tags(self):
775
return self._format.supports_tags()
778
class BranchFormat(object):
779
"""An encapsulation of the initialization and open routines for a format.
781
Formats provide three things:
782
* An initialization routine,
786
Formats are placed in an dict by their format string for reference
787
during branch opening. Its not required that these be instances, they
788
can be classes themselves with class methods - it simply depends on
789
whether state is needed for a given format or not.
791
Once a format is deprecated, just deprecate the initialize and open
792
methods on the format class. Do not deprecate the object, as the
793
object will be created every time regardless.
796
_default_format = None
797
"""The default format used for new branches."""
800
"""The known formats."""
803
def find_format(klass, a_bzrdir):
804
"""Return the format for the branch object in a_bzrdir."""
806
transport = a_bzrdir.get_branch_transport(None)
807
format_string = transport.get("format").read()
808
return klass._formats[format_string]
810
raise NotBranchError(path=transport.base)
812
raise errors.UnknownFormatError(format=format_string)
815
def get_default_format(klass):
816
"""Return the current default format."""
817
return klass._default_format
819
def get_reference(self, a_bzrdir):
820
"""Get the target reference of the branch in a_bzrdir.
822
format probing must have been completed before calling
823
this method - it is assumed that the format of the branch
824
in a_bzrdir is correct.
826
:param a_bzrdir: The bzrdir to get the branch data from.
827
:return: None if the branch is not a reference branch.
831
def get_format_string(self):
832
"""Return the ASCII format string that identifies this format."""
833
raise NotImplementedError(self.get_format_string)
835
def get_format_description(self):
836
"""Return the short format description for this format."""
837
raise NotImplementedError(self.get_format_description)
839
def _initialize_helper(self, a_bzrdir, utf8_files, lock_type='metadir',
841
"""Initialize a branch in a bzrdir, with specified files
843
:param a_bzrdir: The bzrdir to initialize the branch in
844
:param utf8_files: The files to create as a list of
845
(filename, content) tuples
846
:param set_format: If True, set the format with
847
self.get_format_string. (BzrBranch4 has its format set
849
:return: a branch in this format
851
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
852
branch_transport = a_bzrdir.get_branch_transport(self)
854
'metadir': ('lock', lockdir.LockDir),
855
'branch4': ('branch-lock', lockable_files.TransportLock),
857
lock_name, lock_class = lock_map[lock_type]
858
control_files = lockable_files.LockableFiles(branch_transport,
859
lock_name, lock_class)
860
control_files.create_lock()
861
control_files.lock_write()
863
control_files.put_utf8('format', self.get_format_string())
865
for file, content in utf8_files:
866
control_files.put_utf8(file, content)
868
control_files.unlock()
869
return self.open(a_bzrdir, _found=True)
871
def initialize(self, a_bzrdir):
872
"""Create a branch of this format in a_bzrdir."""
873
raise NotImplementedError(self.initialize)
875
def is_supported(self):
876
"""Is this format supported?
878
Supported formats can be initialized and opened.
879
Unsupported formats may not support initialization or committing or
880
some other features depending on the reason for not being supported.
884
def open(self, a_bzrdir, _found=False):
885
"""Return the branch object for a_bzrdir
887
_found is a private parameter, do not use it. It is used to indicate
888
if format probing has already be done.
890
raise NotImplementedError(self.open)
893
def register_format(klass, format):
894
klass._formats[format.get_format_string()] = format
897
def set_default_format(klass, format):
898
klass._default_format = format
901
def unregister_format(klass, format):
902
assert klass._formats[format.get_format_string()] is format
903
del klass._formats[format.get_format_string()]
906
return self.get_format_string().rstrip()
908
def supports_tags(self):
909
"""True if this format supports tags stored in the branch"""
910
return False # by default
912
# XXX: Probably doesn't really belong here -- mbp 20070212
913
def _initialize_control_files(self, a_bzrdir, utf8_files, lock_filename,
915
branch_transport = a_bzrdir.get_branch_transport(self)
916
control_files = lockable_files.LockableFiles(branch_transport,
917
lock_filename, lock_class)
918
control_files.create_lock()
919
control_files.lock_write()
921
for filename, content in utf8_files:
922
control_files.put_utf8(filename, content)
924
control_files.unlock()
927
class BranchHooks(dict):
928
"""A dictionary mapping hook name to a list of callables for branch hooks.
930
e.g. ['set_rh'] Is the list of items to be called when the
931
set_revision_history function is invoked.
935
"""Create the default hooks.
937
These are all empty initially, because by default nothing should get
941
# Introduced in 0.15:
942
# invoked whenever the revision history has been set
943
# with set_revision_history. The api signature is
944
# (branch, revision_history), and the branch will
947
# invoked after a push operation completes.
948
# the api signature is
950
# containing the members
951
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
952
# where local is the local branch or None, master is the target
953
# master branch, and the rest should be self explanatory. The source
954
# is read locked and the target branches write locked. Source will
955
# be the local low-latency branch.
956
self['post_push'] = []
957
# invoked after a pull operation completes.
958
# the api signature is
960
# containing the members
961
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
962
# where local is the local branch or None, master is the target
963
# master branch, and the rest should be self explanatory. The source
964
# is read locked and the target branches write locked. The local
965
# branch is the low-latency branch.
966
self['post_pull'] = []
967
# invoked after a commit operation completes.
968
# the api signature is
969
# (local, master, old_revno, old_revid, new_revno, new_revid)
970
# old_revid is NULL_REVISION for the first commit to a branch.
971
self['post_commit'] = []
972
# invoked after a uncommit operation completes.
973
# the api signature is
974
# (local, master, old_revno, old_revid, new_revno, new_revid) where
975
# local is the local branch or None, master is the target branch,
976
# and an empty branch recieves new_revno of 0, new_revid of None.
977
self['post_uncommit'] = []
979
def install_hook(self, hook_name, a_callable):
980
"""Install a_callable in to the hook hook_name.
982
:param hook_name: A hook name. See the __init__ method of BranchHooks
983
for the complete list of hooks.
984
:param a_callable: The callable to be invoked when the hook triggers.
985
The exact signature will depend on the hook - see the __init__
986
method of BranchHooks for details on each hook.
989
self[hook_name].append(a_callable)
991
raise errors.UnknownHook('branch', hook_name)
994
# install the default hooks into the Branch class.
995
Branch.hooks = BranchHooks()
998
class BzrBranchFormat4(BranchFormat):
999
"""Bzr branch format 4.
1002
- a revision-history file.
1003
- a branch-lock lock file [ to be shared with the bzrdir ]
1006
def get_format_description(self):
1007
"""See BranchFormat.get_format_description()."""
1008
return "Branch format 4"
1010
def initialize(self, a_bzrdir):
1011
"""Create a branch of this format in a_bzrdir."""
1012
utf8_files = [('revision-history', ''),
1013
('branch-name', ''),
1015
return self._initialize_helper(a_bzrdir, utf8_files,
1016
lock_type='branch4', set_format=False)
1019
super(BzrBranchFormat4, self).__init__()
1020
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1022
def open(self, a_bzrdir, _found=False):
1023
"""Return the branch object for a_bzrdir
1025
_found is a private parameter, do not use it. It is used to indicate
1026
if format probing has already be done.
1029
# we are being called directly and must probe.
1030
raise NotImplementedError
1031
return BzrBranch(_format=self,
1032
_control_files=a_bzrdir._control_files,
1034
_repository=a_bzrdir.open_repository())
1037
return "Bazaar-NG branch format 4"
1040
class BzrBranchFormat5(BranchFormat):
1041
"""Bzr branch format 5.
1044
- a revision-history file.
1046
- a lock dir guarding the branch itself
1047
- all of this stored in a branch/ subdirectory
1048
- works with shared repositories.
1050
This format is new in bzr 0.8.
1053
def get_format_string(self):
1054
"""See BranchFormat.get_format_string()."""
1055
return "Bazaar-NG branch format 5\n"
1057
def get_format_description(self):
1058
"""See BranchFormat.get_format_description()."""
1059
return "Branch format 5"
1061
def initialize(self, a_bzrdir):
1062
"""Create a branch of this format in a_bzrdir."""
1063
utf8_files = [('revision-history', ''),
1064
('branch-name', ''),
1066
return self._initialize_helper(a_bzrdir, utf8_files)
1069
super(BzrBranchFormat5, self).__init__()
1070
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1072
def open(self, a_bzrdir, _found=False):
1073
"""Return the branch object for a_bzrdir
1075
_found is a private parameter, do not use it. It is used to indicate
1076
if format probing has already be done.
1079
format = BranchFormat.find_format(a_bzrdir)
1080
assert format.__class__ == self.__class__
1082
transport = a_bzrdir.get_branch_transport(None)
1083
control_files = lockable_files.LockableFiles(transport, 'lock',
1085
return BzrBranch5(_format=self,
1086
_control_files=control_files,
1088
_repository=a_bzrdir.find_repository())
1090
raise NotBranchError(path=transport.base)
1093
class BzrBranchFormat6(BzrBranchFormat5):
1094
"""Branch format with last-revision
1096
Unlike previous formats, this has no explicit revision history. Instead,
1097
this just stores the last-revision, and the left-hand history leading
1098
up to there is the history.
1100
This format was introduced in bzr 0.15
1103
def get_format_string(self):
1104
"""See BranchFormat.get_format_string()."""
1105
return "Bazaar Branch Format 6 (bzr 0.15)\n"
1107
def get_format_description(self):
1108
"""See BranchFormat.get_format_description()."""
1109
return "Branch format 6"
1111
def initialize(self, a_bzrdir):
1112
"""Create a branch of this format in a_bzrdir."""
1113
utf8_files = [('last-revision', '0 null:\n'),
1114
('branch-name', ''),
1115
('branch.conf', ''),
1118
return self._initialize_helper(a_bzrdir, utf8_files)
1120
def open(self, a_bzrdir, _found=False):
1121
"""Return the branch object for a_bzrdir
1123
_found is a private parameter, do not use it. It is used to indicate
1124
if format probing has already be done.
1127
format = BranchFormat.find_format(a_bzrdir)
1128
assert format.__class__ == self.__class__
1129
transport = a_bzrdir.get_branch_transport(None)
1130
control_files = lockable_files.LockableFiles(transport, 'lock',
1132
return BzrBranch6(_format=self,
1133
_control_files=control_files,
1135
_repository=a_bzrdir.find_repository())
1137
def supports_tags(self):
1141
class BranchReferenceFormat(BranchFormat):
1142
"""Bzr branch reference format.
1144
Branch references are used in implementing checkouts, they
1145
act as an alias to the real branch which is at some other url.
1152
def get_format_string(self):
1153
"""See BranchFormat.get_format_string()."""
1154
return "Bazaar-NG Branch Reference Format 1\n"
1156
def get_format_description(self):
1157
"""See BranchFormat.get_format_description()."""
1158
return "Checkout reference format 1"
1160
def get_reference(self, a_bzrdir):
1161
"""See BranchFormat.get_reference()."""
1162
transport = a_bzrdir.get_branch_transport(None)
1163
return transport.get('location').read()
1165
def initialize(self, a_bzrdir, target_branch=None):
1166
"""Create a branch of this format in a_bzrdir."""
1167
if target_branch is None:
1168
# this format does not implement branch itself, thus the implicit
1169
# creation contract must see it as uninitializable
1170
raise errors.UninitializableFormat(self)
1171
mutter('creating branch reference in %s', a_bzrdir.transport.base)
1172
branch_transport = a_bzrdir.get_branch_transport(self)
1173
branch_transport.put_bytes('location',
1174
target_branch.bzrdir.root_transport.base)
1175
branch_transport.put_bytes('format', self.get_format_string())
1176
return self.open(a_bzrdir, _found=True)
1179
super(BranchReferenceFormat, self).__init__()
1180
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1182
def _make_reference_clone_function(format, a_branch):
1183
"""Create a clone() routine for a branch dynamically."""
1184
def clone(to_bzrdir, revision_id=None):
1185
"""See Branch.clone()."""
1186
return format.initialize(to_bzrdir, a_branch)
1187
# cannot obey revision_id limits when cloning a reference ...
1188
# FIXME RBC 20060210 either nuke revision_id for clone, or
1189
# emit some sort of warning/error to the caller ?!
1192
def open(self, a_bzrdir, _found=False, location=None):
1193
"""Return the branch that the branch reference in a_bzrdir points at.
1195
_found is a private parameter, do not use it. It is used to indicate
1196
if format probing has already be done.
1199
format = BranchFormat.find_format(a_bzrdir)
1200
assert format.__class__ == self.__class__
1201
if location is None:
1202
location = self.get_reference(a_bzrdir)
1203
real_bzrdir = bzrdir.BzrDir.open(location)
1204
result = real_bzrdir.open_branch()
1205
# this changes the behaviour of result.clone to create a new reference
1206
# rather than a copy of the content of the branch.
1207
# I did not use a proxy object because that needs much more extensive
1208
# testing, and we are only changing one behaviour at the moment.
1209
# If we decide to alter more behaviours - i.e. the implicit nickname
1210
# then this should be refactored to introduce a tested proxy branch
1211
# and a subclass of that for use in overriding clone() and ....
1213
result.clone = self._make_reference_clone_function(result)
1217
# formats which have no format string are not discoverable
1218
# and not independently creatable, so are not registered.
1219
__default_format = BzrBranchFormat5()
1220
BranchFormat.register_format(__default_format)
1221
BranchFormat.register_format(BranchReferenceFormat())
1222
BranchFormat.register_format(BzrBranchFormat6())
1223
BranchFormat.set_default_format(__default_format)
1224
_legacy_formats = [BzrBranchFormat4(),
1227
class BzrBranch(Branch):
1228
"""A branch stored in the actual filesystem.
1230
Note that it's "local" in the context of the filesystem; it doesn't
1231
really matter if it's on an nfs/smb/afs/coda/... share, as long as
1232
it's writable, and can be accessed via the normal filesystem API.
1235
def __init__(self, _format=None,
1236
_control_files=None, a_bzrdir=None, _repository=None):
1237
"""Create new branch object at a particular location."""
1238
Branch.__init__(self)
1239
if a_bzrdir is None:
1240
raise ValueError('a_bzrdir must be supplied')
1242
self.bzrdir = a_bzrdir
1243
# self._transport used to point to the directory containing the
1244
# control directory, but was not used - now it's just the transport
1245
# for the branch control files. mbp 20070212
1246
self._base = self.bzrdir.transport.clone('..').base
1247
self._format = _format
1248
if _control_files is None:
1249
raise ValueError('BzrBranch _control_files is None')
1250
self.control_files = _control_files
1251
self._transport = _control_files._transport
1252
self.repository = _repository
1255
return '%s(%r)' % (self.__class__.__name__, self.base)
1259
def _get_base(self):
1260
"""Returns the directory containing the control directory."""
1263
base = property(_get_base, doc="The URL for the root of this branch.")
1265
def abspath(self, name):
1266
"""See Branch.abspath."""
1267
return self.control_files._transport.abspath(name)
1270
@deprecated_method(zero_sixteen)
1272
def get_root_id(self):
1273
"""See Branch.get_root_id."""
1274
tree = self.repository.revision_tree(self.last_revision())
1275
return tree.inventory.root.file_id
1277
def is_locked(self):
1278
return self.control_files.is_locked()
1280
def lock_write(self, tokens=None):
1281
if tokens is not None:
1282
branch_token, repo_token = tokens
1284
branch_token = repo_token = None
1285
repo_token = self.repository.lock_write(token=repo_token)
1287
branch_token = self.control_files.lock_write(token=branch_token)
1289
self.repository.unlock()
1292
tokens = (branch_token, repo_token)
1293
assert tokens == (None, None) or None not in tokens, (
1294
'Both branch and repository locks must return tokens, or else '
1295
'neither must return tokens. Got %r.' % (tokens,))
1296
if tokens == (None, None):
1301
def lock_read(self):
1302
self.repository.lock_read()
1304
self.control_files.lock_read()
1306
self.repository.unlock()
1310
# TODO: test for failed two phase locks. This is known broken.
1312
self.control_files.unlock()
1314
self.repository.unlock()
1315
if not self.control_files.is_locked():
1316
# we just released the lock
1317
self._clear_cached_state()
1319
def peek_lock_mode(self):
1320
if self.control_files._lock_count == 0:
1323
return self.control_files._lock_mode
1325
def get_physical_lock_status(self):
1326
return self.control_files.get_physical_lock_status()
1329
def print_file(self, file, revision_id):
1330
"""See Branch.print_file."""
1331
return self.repository.print_file(file, revision_id)
1334
def append_revision(self, *revision_ids):
1335
"""See Branch.append_revision."""
1336
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1337
for revision_id in revision_ids:
1338
_mod_revision.check_not_reserved_id(revision_id)
1339
mutter("add {%s} to revision-history" % revision_id)
1340
rev_history = self.revision_history()
1341
rev_history.extend(revision_ids)
1342
self.set_revision_history(rev_history)
1344
def _write_revision_history(self, history):
1345
"""Factored out of set_revision_history.
1347
This performs the actual writing to disk.
1348
It is intended to be called by BzrBranch5.set_revision_history."""
1349
self.control_files.put_bytes(
1350
'revision-history', '\n'.join(history))
1353
def set_revision_history(self, rev_history):
1354
"""See Branch.set_revision_history."""
1355
rev_history = [osutils.safe_revision_id(r) for r in rev_history]
1356
self._write_revision_history(rev_history)
1357
self._cache_revision_history(rev_history)
1358
for hook in Branch.hooks['set_rh']:
1359
hook(self, rev_history)
1362
def set_last_revision_info(self, revno, revision_id):
1363
revision_id = osutils.safe_revision_id(revision_id)
1364
history = self._lefthand_history(revision_id)
1365
assert len(history) == revno, '%d != %d' % (len(history), revno)
1366
self.set_revision_history(history)
1368
def _gen_revision_history(self):
1369
history = self.control_files.get('revision-history').read().split('\n')
1370
if history[-1:] == ['']:
1371
# There shouldn't be a trailing newline, but just in case.
1375
def _lefthand_history(self, revision_id, last_rev=None,
1377
# stop_revision must be a descendant of last_revision
1378
stop_graph = self.repository.get_revision_graph(revision_id)
1379
if last_rev is not None and last_rev not in stop_graph:
1380
# our previous tip is not merged into stop_revision
1381
raise errors.DivergedBranches(self, other_branch)
1382
# make a new revision history from the graph
1383
current_rev_id = revision_id
1385
while current_rev_id not in (None, _mod_revision.NULL_REVISION):
1386
new_history.append(current_rev_id)
1387
current_rev_id_parents = stop_graph[current_rev_id]
1389
current_rev_id = current_rev_id_parents[0]
1391
current_rev_id = None
1392
new_history.reverse()
1396
def generate_revision_history(self, revision_id, last_rev=None,
1398
"""Create a new revision history that will finish with revision_id.
1400
:param revision_id: the new tip to use.
1401
:param last_rev: The previous last_revision. If not None, then this
1402
must be a ancestory of revision_id, or DivergedBranches is raised.
1403
:param other_branch: The other branch that DivergedBranches should
1404
raise with respect to.
1406
revision_id = osutils.safe_revision_id(revision_id)
1407
self.set_revision_history(self._lefthand_history(revision_id,
1408
last_rev, other_branch))
1411
def update_revisions(self, other, stop_revision=None):
1412
"""See Branch.update_revisions."""
1415
if stop_revision is None:
1416
stop_revision = other.last_revision()
1417
if stop_revision is None:
1418
# if there are no commits, we're done.
1421
stop_revision = osutils.safe_revision_id(stop_revision)
1422
# whats the current last revision, before we fetch [and change it
1424
last_rev = self.last_revision()
1425
# we fetch here regardless of whether we need to so that we pickup
1427
self.fetch(other, stop_revision)
1428
my_ancestry = self.repository.get_ancestry(last_rev)
1429
if stop_revision in my_ancestry:
1430
# last_revision is a descendant of stop_revision
1432
self.generate_revision_history(stop_revision, last_rev=last_rev,
1437
def basis_tree(self):
1438
"""See Branch.basis_tree."""
1439
return self.repository.revision_tree(self.last_revision())
1441
@deprecated_method(zero_eight)
1442
def working_tree(self):
1443
"""Create a Working tree object for this branch."""
1445
from bzrlib.transport.local import LocalTransport
1446
if (self.base.find('://') != -1 or
1447
not isinstance(self._transport, LocalTransport)):
1448
raise NoWorkingTree(self.base)
1449
return self.bzrdir.open_workingtree()
1452
def pull(self, source, overwrite=False, stop_revision=None,
1453
_hook_master=None, _run_hooks=True):
1456
:param _hook_master: Private parameter - set the branch to
1457
be supplied as the master to push hooks.
1458
:param _run_hooks: Private parameter - allow disabling of
1459
hooks, used when pushing to a master branch.
1461
result = PullResult()
1462
result.source_branch = source
1463
result.target_branch = self
1466
result.old_revno, result.old_revid = self.last_revision_info()
1468
self.update_revisions(source, stop_revision)
1469
except DivergedBranches:
1473
if stop_revision is None:
1474
stop_revision = source.last_revision()
1475
self.generate_revision_history(stop_revision)
1476
result.tag_conflicts = source.tags.merge_to(self.tags)
1477
result.new_revno, result.new_revid = self.last_revision_info()
1479
result.master_branch = _hook_master
1480
result.local_branch = self
1482
result.master_branch = self
1483
result.local_branch = None
1485
for hook in Branch.hooks['post_pull']:
1491
def _get_parent_location(self):
1492
_locs = ['parent', 'pull', 'x-pull']
1495
return self.control_files.get(l).read().strip('\n')
1501
def push(self, target, overwrite=False, stop_revision=None,
1502
_hook_master=None, _run_hooks=True):
1505
:param _hook_master: Private parameter - set the branch to
1506
be supplied as the master to push hooks.
1507
:param _run_hooks: Private parameter - allow disabling of
1508
hooks, used when pushing to a master branch.
1510
result = PushResult()
1511
result.source_branch = self
1512
result.target_branch = target
1515
result.old_revno, result.old_revid = target.last_revision_info()
1517
target.update_revisions(self, stop_revision)
1518
except DivergedBranches:
1522
target.set_revision_history(self.revision_history())
1523
result.tag_conflicts = self.tags.merge_to(target.tags)
1524
result.new_revno, result.new_revid = target.last_revision_info()
1526
result.master_branch = _hook_master
1527
result.local_branch = target
1529
result.master_branch = target
1530
result.local_branch = None
1532
for hook in Branch.hooks['post_push']:
1538
def get_parent(self):
1539
"""See Branch.get_parent."""
1541
assert self.base[-1] == '/'
1542
parent = self._get_parent_location()
1545
# This is an old-format absolute path to a local branch
1546
# turn it into a url
1547
if parent.startswith('/'):
1548
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1550
return urlutils.join(self.base[:-1], parent)
1551
except errors.InvalidURLJoin, e:
1552
raise errors.InaccessibleParent(parent, self.base)
1554
def set_push_location(self, location):
1555
"""See Branch.set_push_location."""
1556
self.get_config().set_user_option(
1557
'push_location', location,
1558
store=_mod_config.STORE_LOCATION_NORECURSE)
1561
def set_parent(self, url):
1562
"""See Branch.set_parent."""
1563
# TODO: Maybe delete old location files?
1564
# URLs should never be unicode, even on the local fs,
1565
# FIXUP this and get_parent in a future branch format bump:
1566
# read and rewrite the file, and have the new format code read
1567
# using .get not .get_utf8. RBC 20060125
1569
if isinstance(url, unicode):
1571
url = url.encode('ascii')
1572
except UnicodeEncodeError:
1573
raise bzrlib.errors.InvalidURL(url,
1574
"Urls must be 7-bit ascii, "
1575
"use bzrlib.urlutils.escape")
1576
url = urlutils.relative_url(self.base, url)
1577
self._set_parent_location(url)
1579
def _set_parent_location(self, url):
1581
self.control_files._transport.delete('parent')
1583
assert isinstance(url, str)
1584
self.control_files.put_bytes('parent', url + '\n')
1586
@deprecated_function(zero_nine)
1587
def tree_config(self):
1588
"""DEPRECATED; call get_config instead.
1589
TreeConfig has become part of BranchConfig."""
1590
return TreeConfig(self)
1593
class BzrBranch5(BzrBranch):
1594
"""A format 5 branch. This supports new features over plan branches.
1596
It has support for a master_branch which is the data for bound branches.
1604
super(BzrBranch5, self).__init__(_format=_format,
1605
_control_files=_control_files,
1607
_repository=_repository)
1610
def pull(self, source, overwrite=False, stop_revision=None,
1612
"""Extends branch.pull to be bound branch aware.
1614
:param _run_hooks: Private parameter used to force hook running
1615
off during bound branch double-pushing.
1617
bound_location = self.get_bound_location()
1618
master_branch = None
1619
if bound_location and source.base != bound_location:
1620
# not pulling from master, so we need to update master.
1621
master_branch = self.get_master_branch()
1622
master_branch.lock_write()
1625
# pull from source into master.
1626
master_branch.pull(source, overwrite, stop_revision,
1628
return super(BzrBranch5, self).pull(source, overwrite,
1629
stop_revision, _hook_master=master_branch,
1630
_run_hooks=_run_hooks)
1633
master_branch.unlock()
1636
def push(self, target, overwrite=False, stop_revision=None):
1637
"""Updates branch.push to be bound branch aware."""
1638
bound_location = target.get_bound_location()
1639
master_branch = None
1640
if bound_location and target.base != bound_location:
1641
# not pushing to master, so we need to update master.
1642
master_branch = target.get_master_branch()
1643
master_branch.lock_write()
1646
# push into the master from this branch.
1647
super(BzrBranch5, self).push(master_branch, overwrite,
1648
stop_revision, _run_hooks=False)
1649
# and push into the target branch from this. Note that we push from
1650
# this branch again, because its considered the highest bandwidth
1652
return super(BzrBranch5, self).push(target, overwrite,
1653
stop_revision, _hook_master=master_branch)
1656
master_branch.unlock()
1658
def get_bound_location(self):
1660
return self.control_files.get_utf8('bound').read()[:-1]
1661
except errors.NoSuchFile:
1665
def get_master_branch(self):
1666
"""Return the branch we are bound to.
1668
:return: Either a Branch, or None
1670
This could memoise the branch, but if thats done
1671
it must be revalidated on each new lock.
1672
So for now we just don't memoise it.
1673
# RBC 20060304 review this decision.
1675
bound_loc = self.get_bound_location()
1679
return Branch.open(bound_loc)
1680
except (errors.NotBranchError, errors.ConnectionError), e:
1681
raise errors.BoundBranchConnectionFailure(
1685
def set_bound_location(self, location):
1686
"""Set the target where this branch is bound to.
1688
:param location: URL to the target branch
1691
self.control_files.put_utf8('bound', location+'\n')
1694
self.control_files._transport.delete('bound')
1700
def bind(self, other):
1701
"""Bind this branch to the branch other.
1703
This does not push or pull data between the branches, though it does
1704
check for divergence to raise an error when the branches are not
1705
either the same, or one a prefix of the other. That behaviour may not
1706
be useful, so that check may be removed in future.
1708
:param other: The branch to bind to
1711
# TODO: jam 20051230 Consider checking if the target is bound
1712
# It is debatable whether you should be able to bind to
1713
# a branch which is itself bound.
1714
# Committing is obviously forbidden,
1715
# but binding itself may not be.
1716
# Since we *have* to check at commit time, we don't
1717
# *need* to check here
1719
# we want to raise diverged if:
1720
# last_rev is not in the other_last_rev history, AND
1721
# other_last_rev is not in our history, and do it without pulling
1723
last_rev = self.last_revision()
1724
if last_rev is not None:
1727
other_last_rev = other.last_revision()
1728
if other_last_rev is not None:
1729
# neither branch is new, we have to do some work to
1730
# ascertain diversion.
1731
remote_graph = other.repository.get_revision_graph(
1733
local_graph = self.repository.get_revision_graph(last_rev)
1734
if (last_rev not in remote_graph and
1735
other_last_rev not in local_graph):
1736
raise errors.DivergedBranches(self, other)
1739
self.set_bound_location(other.base)
1743
"""If bound, unbind"""
1744
return self.set_bound_location(None)
1748
"""Synchronise this branch with the master branch if any.
1750
:return: None or the last_revision that was pivoted out during the
1753
master = self.get_master_branch()
1754
if master is not None:
1755
old_tip = self.last_revision()
1756
self.pull(master, overwrite=True)
1757
if old_tip in self.repository.get_ancestry(self.last_revision()):
1763
class BzrBranchExperimental(BzrBranch5):
1764
"""Bzr experimental branch format
1767
- a revision-history file.
1769
- a lock dir guarding the branch itself
1770
- all of this stored in a branch/ subdirectory
1771
- works with shared repositories.
1772
- a tag dictionary in the branch
1774
This format is new in bzr 0.15, but shouldn't be used for real data,
1777
This class acts as it's own BranchFormat.
1780
_matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1783
def get_format_string(cls):
1784
"""See BranchFormat.get_format_string()."""
1785
return "Bazaar-NG branch format experimental\n"
1788
def get_format_description(cls):
1789
"""See BranchFormat.get_format_description()."""
1790
return "Experimental branch format"
1793
def get_reference(cls, a_bzrdir):
1794
"""Get the target reference of the branch in a_bzrdir.
1796
format probing must have been completed before calling
1797
this method - it is assumed that the format of the branch
1798
in a_bzrdir is correct.
1800
:param a_bzrdir: The bzrdir to get the branch data from.
1801
:return: None if the branch is not a reference branch.
1806
def _initialize_control_files(cls, a_bzrdir, utf8_files, lock_filename,
1808
branch_transport = a_bzrdir.get_branch_transport(cls)
1809
control_files = lockable_files.LockableFiles(branch_transport,
1810
lock_filename, lock_class)
1811
control_files.create_lock()
1812
control_files.lock_write()
1814
for filename, content in utf8_files:
1815
control_files.put_utf8(filename, content)
1817
control_files.unlock()
1820
def initialize(cls, a_bzrdir):
1821
"""Create a branch of this format in a_bzrdir."""
1822
utf8_files = [('format', cls.get_format_string()),
1823
('revision-history', ''),
1824
('branch-name', ''),
1827
cls._initialize_control_files(a_bzrdir, utf8_files,
1828
'lock', lockdir.LockDir)
1829
return cls.open(a_bzrdir, _found=True)
1832
def open(cls, a_bzrdir, _found=False):
1833
"""Return the branch object for a_bzrdir
1835
_found is a private parameter, do not use it. It is used to indicate
1836
if format probing has already be done.
1839
format = BranchFormat.find_format(a_bzrdir)
1840
assert format.__class__ == cls
1841
transport = a_bzrdir.get_branch_transport(None)
1842
control_files = lockable_files.LockableFiles(transport, 'lock',
1844
return cls(_format=cls,
1845
_control_files=control_files,
1847
_repository=a_bzrdir.find_repository())
1850
def is_supported(cls):
1853
def _make_tags(self):
1854
return BasicTags(self)
1857
def supports_tags(cls):
1861
BranchFormat.register_format(BzrBranchExperimental)
1864
class BzrBranch6(BzrBranch5):
1867
def last_revision_info(self):
1868
revision_string = self.control_files.get('last-revision').read()
1869
revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
1870
revision_id = cache_utf8.get_cached_utf8(revision_id)
1872
return revno, revision_id
1874
def last_revision(self):
1875
"""Return last revision id, or None"""
1876
revision_id = self.last_revision_info()[1]
1877
if revision_id == _mod_revision.NULL_REVISION:
1881
def _write_last_revision_info(self, revno, revision_id):
1882
"""Simply write out the revision id, with no checks.
1884
Use set_last_revision_info to perform this safely.
1886
Does not update the revision_history cache.
1887
Intended to be called by set_last_revision_info and
1888
_write_revision_history.
1890
if revision_id is None:
1891
revision_id = 'null:'
1892
out_string = '%d %s\n' % (revno, revision_id)
1893
self.control_files.put_bytes('last-revision', out_string)
1896
def set_last_revision_info(self, revno, revision_id):
1897
revision_id = osutils.safe_revision_id(revision_id)
1898
if self._get_append_revisions_only():
1899
self._check_history_violation(revision_id)
1900
self._write_last_revision_info(revno, revision_id)
1901
self._clear_cached_state()
1903
def _check_history_violation(self, revision_id):
1904
last_revision = self.last_revision()
1905
if last_revision is None:
1907
if last_revision not in self._lefthand_history(revision_id):
1908
raise errors.AppendRevisionsOnlyViolation(self.base)
1910
def _gen_revision_history(self):
1911
"""Generate the revision history from last revision
1913
history = list(self.repository.iter_reverse_revision_history(
1914
self.last_revision()))
1918
def _write_revision_history(self, history):
1919
"""Factored out of set_revision_history.
1921
This performs the actual writing to disk, with format-specific checks.
1922
It is intended to be called by BzrBranch5.set_revision_history.
1924
if len(history) == 0:
1925
last_revision = 'null:'
1927
if history != self._lefthand_history(history[-1]):
1928
raise errors.NotLefthandHistory(history)
1929
last_revision = history[-1]
1930
if self._get_append_revisions_only():
1931
self._check_history_violation(last_revision)
1932
self._write_last_revision_info(len(history), last_revision)
1935
def append_revision(self, *revision_ids):
1936
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1937
if len(revision_ids) == 0:
1939
prev_revno, prev_revision = self.last_revision_info()
1940
for revision in self.repository.get_revisions(revision_ids):
1941
if prev_revision == _mod_revision.NULL_REVISION:
1942
if revision.parent_ids != []:
1943
raise errors.NotLeftParentDescendant(self, prev_revision,
1944
revision.revision_id)
1946
if revision.parent_ids[0] != prev_revision:
1947
raise errors.NotLeftParentDescendant(self, prev_revision,
1948
revision.revision_id)
1949
prev_revision = revision.revision_id
1950
self.set_last_revision_info(prev_revno + len(revision_ids),
1954
def _set_parent_location(self, url):
1955
"""Set the parent branch"""
1956
self._set_config_location('parent_location', url, make_relative=True)
1959
def _get_parent_location(self):
1960
"""Set the parent branch"""
1961
return self._get_config_location('parent_location')
1963
def set_push_location(self, location):
1964
"""See Branch.set_push_location."""
1965
self._set_config_location('push_location', location)
1967
def set_bound_location(self, location):
1968
"""See Branch.set_push_location."""
1970
config = self.get_config()
1971
if location is None:
1972
if config.get_user_option('bound') != 'True':
1975
config.set_user_option('bound', 'False')
1978
self._set_config_location('bound_location', location,
1980
config.set_user_option('bound', 'True')
1983
def _get_bound_location(self, bound):
1984
"""Return the bound location in the config file.
1986
Return None if the bound parameter does not match"""
1987
config = self.get_config()
1988
config_bound = (config.get_user_option('bound') == 'True')
1989
if config_bound != bound:
1991
return self._get_config_location('bound_location', config=config)
1993
def get_bound_location(self):
1994
"""See Branch.set_push_location."""
1995
return self._get_bound_location(True)
1997
def get_old_bound_location(self):
1998
"""See Branch.get_old_bound_location"""
1999
return self._get_bound_location(False)
2001
def set_append_revisions_only(self, enabled):
2006
self.get_config().set_user_option('append_revisions_only', value)
2008
def _get_append_revisions_only(self):
2009
value = self.get_config().get_user_option('append_revisions_only')
2010
return value == 'True'
2012
def _synchronize_history(self, destination, revision_id):
2013
"""Synchronize last revision and revision history between branches.
2015
This version is most efficient when the destination is also a
2016
BzrBranch6, but works for BzrBranch5, as long as the destination's
2017
repository contains all the lefthand ancestors of the intended
2018
last_revision. If not, set_last_revision_info will fail.
2020
:param destination: The branch to copy the history into
2021
:param revision_id: The revision-id to truncate history at. May
2022
be None to copy complete history.
2024
if revision_id is None:
2025
revno, revision_id = self.last_revision_info()
2027
revno = self.revision_id_to_revno(revision_id)
2028
destination.set_last_revision_info(revno, revision_id)
2030
def _make_tags(self):
2031
return BasicTags(self)
2034
class BranchTestProviderAdapter(object):
2035
"""A tool to generate a suite testing multiple branch formats at once.
2037
This is done by copying the test once for each transport and injecting
2038
the transport_server, transport_readonly_server, and branch_format
2039
classes into each copy. Each copy is also given a new id() to make it
2043
def __init__(self, transport_server, transport_readonly_server, formats,
2044
vfs_transport_factory=None):
2045
self._transport_server = transport_server
2046
self._transport_readonly_server = transport_readonly_server
2047
self._formats = formats
2049
def adapt(self, test):
2050
result = TestSuite()
2051
for branch_format, bzrdir_format in self._formats:
2052
new_test = deepcopy(test)
2053
new_test.transport_server = self._transport_server
2054
new_test.transport_readonly_server = self._transport_readonly_server
2055
new_test.bzrdir_format = bzrdir_format
2056
new_test.branch_format = branch_format
2057
def make_new_test_id():
2058
# the format can be either a class or an instance
2059
name = getattr(branch_format, '__name__',
2060
branch_format.__class__.__name__)
2061
new_id = "%s(%s)" % (new_test.id(), name)
2062
return lambda: new_id
2063
new_test.id = make_new_test_id()
2064
result.addTest(new_test)
2068
######################################################################
2069
# results of operations
2072
class _Result(object):
2074
def _show_tag_conficts(self, to_file):
2075
if not getattr(self, 'tag_conflicts', None):
2077
to_file.write('Conflicting tags:\n')
2078
for name, value1, value2 in self.tag_conflicts:
2079
to_file.write(' %s\n' % (name, ))
2082
class PullResult(_Result):
2083
"""Result of a Branch.pull operation.
2085
:ivar old_revno: Revision number before pull.
2086
:ivar new_revno: Revision number after pull.
2087
:ivar old_revid: Tip revision id before pull.
2088
:ivar new_revid: Tip revision id after pull.
2089
:ivar source_branch: Source (local) branch object.
2090
:ivar master_branch: Master branch of the target, or None.
2091
:ivar target_branch: Target/destination branch object.
2095
# DEPRECATED: pull used to return the change in revno
2096
return self.new_revno - self.old_revno
2098
def report(self, to_file):
2099
if self.old_revid == self.new_revid:
2100
to_file.write('No revisions to pull.\n')
2102
to_file.write('Now on revision %d.\n' % self.new_revno)
2103
self._show_tag_conficts(to_file)
2106
class PushResult(_Result):
2107
"""Result of a Branch.push operation.
2109
:ivar old_revno: Revision number before push.
2110
:ivar new_revno: Revision number after push.
2111
:ivar old_revid: Tip revision id before push.
2112
:ivar new_revid: Tip revision id after push.
2113
:ivar source_branch: Source branch object.
2114
:ivar master_branch: Master branch of the target, or None.
2115
:ivar target_branch: Target/destination branch object.
2119
# DEPRECATED: push used to return the change in revno
2120
return self.new_revno - self.old_revno
2122
def report(self, to_file):
2123
"""Write a human-readable description of the result."""
2124
if self.old_revid == self.new_revid:
2125
to_file.write('No new revisions to push.\n')
2127
to_file.write('Pushed up to revision %d.\n' % self.new_revno)
2128
self._show_tag_conficts(to_file)
2131
class BranchCheckResult(object):
2132
"""Results of checking branch consistency.
2137
def __init__(self, branch):
2138
self.branch = branch
2140
def report_results(self, verbose):
2141
"""Report the check results via trace.note.
2143
:param verbose: Requests more detailed display of what was checked,
2146
note('checked branch %s format %s',
2148
self.branch._format)
2151
class Converter5to6(object):
2152
"""Perform an in-place upgrade of format 5 to format 6"""
2154
def convert(self, branch):
2155
# Data for 5 and 6 can peacefully coexist.
2156
format = BzrBranchFormat6()
2157
new_branch = format.open(branch.bzrdir, _found=True)
2159
# Copy source data into target
2160
new_branch.set_last_revision_info(*branch.last_revision_info())
2161
new_branch.set_parent(branch.get_parent())
2162
new_branch.set_bound_location(branch.get_bound_location())
2163
new_branch.set_push_location(branch.get_push_location())
2165
# New branch has no tags by default
2166
new_branch.tags._set_tag_dict({})
2168
# Copying done; now update target format
2169
new_branch.control_files.put_utf8('format',
2170
format.get_format_string())
2172
# Clean up old files
2173
new_branch.control_files._transport.delete('revision-history')
2175
branch.set_parent(None)
2178
branch.set_bound_location(None)