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,
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
def get_root_id(self):
317
"""Return the id of this branches root"""
318
raise NotImplementedError(self.get_root_id)
320
def print_file(self, file, revision_id):
321
"""Print `file` to stdout."""
322
raise NotImplementedError(self.print_file)
324
def append_revision(self, *revision_ids):
325
raise NotImplementedError(self.append_revision)
327
def set_revision_history(self, rev_history):
328
raise NotImplementedError(self.set_revision_history)
330
def _cache_revision_history(self, rev_history):
331
"""Set the cached revision history to rev_history.
333
The revision_history method will use this cache to avoid regenerating
334
the revision history.
336
This API is semi-public; it only for use by subclasses, all other code
337
should consider it to be private.
339
self._revision_history_cache = rev_history
341
def _clear_cached_state(self):
342
"""Clear any cached data on this branch, e.g. cached revision history.
344
This means the next call to revision_history will need to call
345
_gen_revision_history.
347
This API is semi-public; it only for use by subclasses, all other code
348
should consider it to be private.
350
self._revision_history_cache = None
352
def _gen_revision_history(self):
353
"""Return sequence of revision hashes on to this branch.
355
Unlike revision_history, this method always regenerates or rereads the
356
revision history, i.e. it does not cache the result, so repeated calls
359
Concrete subclasses should override this instead of revision_history so
360
that subclasses do not need to deal with caching logic.
362
This API is semi-public; it only for use by subclasses, all other code
363
should consider it to be private.
365
raise NotImplementedError(self._gen_revision_history)
368
def revision_history(self):
369
"""Return sequence of revision hashes on to this branch.
371
This method will cache the revision history for as long as it is safe to
374
if self._revision_history_cache is not None:
375
history = self._revision_history_cache
377
history = self._gen_revision_history()
378
self._cache_revision_history(history)
382
"""Return current revision number for this branch.
384
That is equivalent to the number of revisions committed to
387
return len(self.revision_history())
390
"""Older format branches cannot bind or unbind."""
391
raise errors.UpgradeRequired(self.base)
393
def set_append_revisions_only(self, enabled):
394
"""Older format branches are never restricted to append-only"""
395
raise errors.UpgradeRequired(self.base)
397
def last_revision(self):
398
"""Return last revision id, or None"""
399
ph = self.revision_history()
405
def last_revision_info(self):
406
"""Return information about the last revision.
408
:return: A tuple (revno, last_revision_id).
410
rh = self.revision_history()
413
return (revno, rh[-1])
415
return (0, _mod_revision.NULL_REVISION)
417
def missing_revisions(self, other, stop_revision=None):
418
"""Return a list of new revisions that would perfectly fit.
420
If self and other have not diverged, return a list of the revisions
421
present in other, but missing from self.
423
self_history = self.revision_history()
424
self_len = len(self_history)
425
other_history = other.revision_history()
426
other_len = len(other_history)
427
common_index = min(self_len, other_len) -1
428
if common_index >= 0 and \
429
self_history[common_index] != other_history[common_index]:
430
raise DivergedBranches(self, other)
432
if stop_revision is None:
433
stop_revision = other_len
435
assert isinstance(stop_revision, int)
436
if stop_revision > other_len:
437
raise errors.NoSuchRevision(self, stop_revision)
438
return other_history[self_len:stop_revision]
440
def update_revisions(self, other, stop_revision=None):
441
"""Pull in new perfect-fit revisions.
443
:param other: Another Branch to pull from
444
:param stop_revision: Updated until the given revision
447
raise NotImplementedError(self.update_revisions)
449
def revision_id_to_revno(self, revision_id):
450
"""Given a revision id, return its revno"""
451
if revision_id is None:
453
revision_id = osutils.safe_revision_id(revision_id)
454
history = self.revision_history()
456
return history.index(revision_id) + 1
458
raise bzrlib.errors.NoSuchRevision(self, revision_id)
460
def get_rev_id(self, revno, history=None):
461
"""Find the revision id of the specified revno."""
465
history = self.revision_history()
466
if revno <= 0 or revno > len(history):
467
raise bzrlib.errors.NoSuchRevision(self, revno)
468
return history[revno - 1]
470
def pull(self, source, overwrite=False, stop_revision=None):
471
"""Mirror source into this branch.
473
This branch is considered to be 'local', having low latency.
475
:returns: PullResult instance
477
raise NotImplementedError(self.pull)
479
def push(self, target, overwrite=False, stop_revision=None):
480
"""Mirror this branch into target.
482
This branch is considered to be 'local', having low latency.
484
raise NotImplementedError(self.push)
486
def basis_tree(self):
487
"""Return `Tree` object for last revision."""
488
return self.repository.revision_tree(self.last_revision())
490
def rename_one(self, from_rel, to_rel):
493
This can change the directory or the filename or both.
495
raise NotImplementedError(self.rename_one)
497
def move(self, from_paths, to_name):
500
to_name must exist as a versioned directory.
502
If to_name exists and is a directory, the files are moved into
503
it, keeping their old names. If it is a directory,
505
Note that to_name is only the last component of the new name;
506
this doesn't change the directory.
508
This returns a list of (from_path, to_path) pairs for each
511
raise NotImplementedError(self.move)
513
def get_parent(self):
514
"""Return the parent location of the branch.
516
This is the default location for push/pull/missing. The usual
517
pattern is that the user can override it by specifying a
520
raise NotImplementedError(self.get_parent)
522
def _set_config_location(self, name, url, config=None,
523
make_relative=False):
525
config = self.get_config()
529
url = urlutils.relative_url(self.base, url)
530
config.set_user_option(name, url)
532
def _get_config_location(self, name, config=None):
534
config = self.get_config()
535
location = config.get_user_option(name)
540
def get_submit_branch(self):
541
"""Return the submit location of the branch.
543
This is the default location for bundle. The usual
544
pattern is that the user can override it by specifying a
547
return self.get_config().get_user_option('submit_branch')
549
def set_submit_branch(self, location):
550
"""Return the submit location of the branch.
552
This is the default location for bundle. The usual
553
pattern is that the user can override it by specifying a
556
self.get_config().set_user_option('submit_branch', location)
558
def get_public_branch(self):
559
"""Return the public location of the branch.
561
This is is used by merge directives.
563
return self._get_config_location('public_branch')
565
def set_public_branch(self, location):
566
"""Return the submit location of the branch.
568
This is the default location for bundle. The usual
569
pattern is that the user can override it by specifying a
572
self._set_config_location('public_branch', location)
574
def get_push_location(self):
575
"""Return the None or the location to push this branch to."""
576
push_loc = self.get_config().get_user_option('push_location')
579
def set_push_location(self, location):
580
"""Set a new push location for this branch."""
581
raise NotImplementedError(self.set_push_location)
583
def set_parent(self, url):
584
raise NotImplementedError(self.set_parent)
588
"""Synchronise this branch with the master branch if any.
590
:return: None or the last_revision pivoted out during the update.
594
def check_revno(self, revno):
596
Check whether a revno corresponds to any revision.
597
Zero (the NULL revision) is considered valid.
600
self.check_real_revno(revno)
602
def check_real_revno(self, revno):
604
Check whether a revno corresponds to a real revision.
605
Zero (the NULL revision) is considered invalid
607
if revno < 1 or revno > self.revno():
608
raise InvalidRevisionNumber(revno)
611
def clone(self, to_bzrdir, revision_id=None):
612
"""Clone this branch into to_bzrdir preserving all semantic values.
614
revision_id: if not None, the revision history in the new branch will
615
be truncated to end with revision_id.
617
result = self._format.initialize(to_bzrdir)
618
self.copy_content_into(result, revision_id=revision_id)
622
def sprout(self, to_bzrdir, revision_id=None):
623
"""Create a new line of development from the branch, into to_bzrdir.
625
revision_id: if not None, the revision history in the new branch will
626
be truncated to end with revision_id.
628
result = self._format.initialize(to_bzrdir)
629
self.copy_content_into(result, revision_id=revision_id)
630
result.set_parent(self.bzrdir.root_transport.base)
633
def _synchronize_history(self, destination, revision_id):
634
"""Synchronize last revision and revision history between branches.
636
This version is most efficient when the destination is also a
637
BzrBranch5, but works for BzrBranch6 as long as the revision
638
history is the true lefthand parent history, and all of the revisions
639
are in the destination's repository. If not, set_revision_history
642
:param destination: The branch to copy the history into
643
:param revision_id: The revision-id to truncate history at. May
644
be None to copy complete history.
646
new_history = self.revision_history()
647
if revision_id is not None:
648
revision_id = osutils.safe_revision_id(revision_id)
650
new_history = new_history[:new_history.index(revision_id) + 1]
652
rev = self.repository.get_revision(revision_id)
653
new_history = rev.get_history(self.repository)[1:]
654
destination.set_revision_history(new_history)
657
def copy_content_into(self, destination, revision_id=None):
658
"""Copy the content of self into destination.
660
revision_id: if not None, the revision history in the new branch will
661
be truncated to end with revision_id.
663
self._synchronize_history(destination, revision_id)
665
parent = self.get_parent()
666
except errors.InaccessibleParent, e:
667
mutter('parent was not accessible to copy: %s', e)
670
destination.set_parent(parent)
671
self.tags.merge_to(destination.tags)
675
"""Check consistency of the branch.
677
In particular this checks that revisions given in the revision-history
678
do actually match up in the revision graph, and that they're all
679
present in the repository.
681
Callers will typically also want to check the repository.
683
:return: A BranchCheckResult.
685
mainline_parent_id = None
686
for revision_id in self.revision_history():
688
revision = self.repository.get_revision(revision_id)
689
except errors.NoSuchRevision, e:
690
raise errors.BzrCheckError("mainline revision {%s} not in repository"
692
# In general the first entry on the revision history has no parents.
693
# But it's not illegal for it to have parents listed; this can happen
694
# in imports from Arch when the parents weren't reachable.
695
if mainline_parent_id is not None:
696
if mainline_parent_id not in revision.parent_ids:
697
raise errors.BzrCheckError("previous revision {%s} not listed among "
699
% (mainline_parent_id, revision_id))
700
mainline_parent_id = revision_id
701
return BranchCheckResult(self)
703
def _get_checkout_format(self):
704
"""Return the most suitable metadir for a checkout of this branch.
705
Weaves are used if this branch's repository uses weaves.
707
from bzrlib.remote import RemoteBzrDir
708
if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
709
from bzrlib.repofmt import weaverepo
710
format = bzrdir.BzrDirMetaFormat1()
711
format.repository_format = weaverepo.RepositoryFormat7()
712
elif isinstance(self.bzrdir, RemoteBzrDir):
713
format = bzrdir.BzrDirMetaFormat1()
715
format = self.repository.bzrdir.checkout_metadir()
716
format.set_branch_format(self._format)
719
def create_checkout(self, to_location, revision_id=None,
721
"""Create a checkout of a branch.
723
:param to_location: The url to produce the checkout at
724
:param revision_id: The revision to check out
725
:param lightweight: If True, produce a lightweight checkout, otherwise,
726
produce a bound branch (heavyweight checkout)
727
:return: The tree of the created checkout
729
t = transport.get_transport(to_location)
732
except errors.FileExists:
735
format = self._get_checkout_format()
736
checkout = format.initialize_on_transport(t)
737
BranchReferenceFormat().initialize(checkout, self)
739
format = self._get_checkout_format()
740
checkout_branch = bzrdir.BzrDir.create_branch_convenience(
741
to_location, force_new_tree=False, format=format)
742
checkout = checkout_branch.bzrdir
743
checkout_branch.bind(self)
744
# pull up to the specified revision_id to set the initial
745
# branch tip correctly, and seed it with history.
746
checkout_branch.pull(self, stop_revision=revision_id)
747
tree = checkout.create_workingtree(revision_id)
748
basis_tree = tree.basis_tree()
749
basis_tree.lock_read()
751
for path, file_id in basis_tree.iter_references():
752
reference_parent = self.reference_parent(file_id, path)
753
reference_parent.create_checkout(tree.abspath(path),
754
basis_tree.get_reference_revision(file_id, path),
760
def reference_parent(self, file_id, path):
761
"""Return the parent branch for a tree-reference file_id
762
:param file_id: The file_id of the tree reference
763
:param path: The path of the file_id in the tree
764
:return: A branch associated with the file_id
766
# FIXME should provide multiple branches, based on config
767
return Branch.open(self.bzrdir.root_transport.clone(path).base)
769
def supports_tags(self):
770
return self._format.supports_tags()
773
class BranchFormat(object):
774
"""An encapsulation of the initialization and open routines for a format.
776
Formats provide three things:
777
* An initialization routine,
781
Formats are placed in an dict by their format string for reference
782
during branch opening. Its not required that these be instances, they
783
can be classes themselves with class methods - it simply depends on
784
whether state is needed for a given format or not.
786
Once a format is deprecated, just deprecate the initialize and open
787
methods on the format class. Do not deprecate the object, as the
788
object will be created every time regardless.
791
_default_format = None
792
"""The default format used for new branches."""
795
"""The known formats."""
798
def find_format(klass, a_bzrdir):
799
"""Return the format for the branch object in a_bzrdir."""
801
transport = a_bzrdir.get_branch_transport(None)
802
format_string = transport.get("format").read()
803
return klass._formats[format_string]
805
raise NotBranchError(path=transport.base)
807
raise errors.UnknownFormatError(format=format_string)
810
def get_default_format(klass):
811
"""Return the current default format."""
812
return klass._default_format
814
def get_format_string(self):
815
"""Return the ASCII format string that identifies this format."""
816
raise NotImplementedError(self.get_format_string)
818
def get_format_description(self):
819
"""Return the short format description for this format."""
820
raise NotImplementedError(self.get_format_description)
822
def get_reference(self, a_bzrdir):
823
"""Get the target reference of the branch in a_bzrdir.
825
format probing must have been completed before calling
826
this method - it is assumed that the format of the branch
827
in a_bzrdir is correct.
829
:param a_bzrdir: The bzrdir to get the branch data from.
830
:return: None if the branch is not a reference branch.
834
def _initialize_helper(self, a_bzrdir, utf8_files, lock_type='metadir',
836
"""Initialize a branch in a bzrdir, with specified files
838
:param a_bzrdir: The bzrdir to initialize the branch in
839
:param utf8_files: The files to create as a list of
840
(filename, content) tuples
841
:param set_format: If True, set the format with
842
self.get_format_string. (BzrBranch4 has its format set
844
:return: a branch in this format
846
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
847
branch_transport = a_bzrdir.get_branch_transport(self)
849
'metadir': ('lock', lockdir.LockDir),
850
'branch4': ('branch-lock', lockable_files.TransportLock),
852
lock_name, lock_class = lock_map[lock_type]
853
control_files = lockable_files.LockableFiles(branch_transport,
854
lock_name, lock_class)
855
control_files.create_lock()
856
control_files.lock_write()
858
control_files.put_utf8('format', self.get_format_string())
860
for file, content in utf8_files:
861
control_files.put_utf8(file, content)
863
control_files.unlock()
864
return self.open(a_bzrdir, _found=True)
866
def initialize(self, a_bzrdir):
867
"""Create a branch of this format in a_bzrdir."""
868
raise NotImplementedError(self.initialize)
870
def is_supported(self):
871
"""Is this format supported?
873
Supported formats can be initialized and opened.
874
Unsupported formats may not support initialization or committing or
875
some other features depending on the reason for not being supported.
879
def open(self, a_bzrdir, _found=False):
880
"""Return the branch object for a_bzrdir
882
_found is a private parameter, do not use it. It is used to indicate
883
if format probing has already be done.
885
raise NotImplementedError(self.open)
888
def register_format(klass, format):
889
klass._formats[format.get_format_string()] = format
892
def set_default_format(klass, format):
893
klass._default_format = format
896
def unregister_format(klass, format):
897
assert klass._formats[format.get_format_string()] is format
898
del klass._formats[format.get_format_string()]
901
return self.get_format_string().rstrip()
903
def supports_tags(self):
904
"""True if this format supports tags stored in the branch"""
905
return False # by default
907
# XXX: Probably doesn't really belong here -- mbp 20070212
908
def _initialize_control_files(self, a_bzrdir, utf8_files, lock_filename,
910
branch_transport = a_bzrdir.get_branch_transport(self)
911
control_files = lockable_files.LockableFiles(branch_transport,
912
lock_filename, lock_class)
913
control_files.create_lock()
914
control_files.lock_write()
916
for filename, content in utf8_files:
917
control_files.put_utf8(filename, content)
919
control_files.unlock()
922
class BranchHooks(dict):
923
"""A dictionary mapping hook name to a list of callables for branch hooks.
925
e.g. ['set_rh'] Is the list of items to be called when the
926
set_revision_history function is invoked.
930
"""Create the default hooks.
932
These are all empty initially, because by default nothing should get
936
# Introduced in 0.15:
937
# invoked whenever the revision history has been set
938
# with set_revision_history. The api signature is
939
# (branch, revision_history), and the branch will
942
# invoked after a push operation completes.
943
# the api signature is
945
# containing the members
946
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
947
# where local is the local branch or None, master is the target
948
# master branch, and the rest should be self explanatory. The source
949
# is read locked and the target branches write locked. Source will
950
# be the local low-latency branch.
951
self['post_push'] = []
952
# invoked after a pull operation completes.
953
# the api signature is
955
# containing the members
956
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
957
# where local is the local branch or None, master is the target
958
# master branch, and the rest should be self explanatory. The source
959
# is read locked and the target branches write locked. The local
960
# branch is the low-latency branch.
961
self['post_pull'] = []
962
# invoked after a commit operation completes.
963
# the api signature is
964
# (local, master, old_revno, old_revid, new_revno, new_revid)
965
# old_revid is NULL_REVISION for the first commit to a branch.
966
self['post_commit'] = []
967
# invoked after a uncommit operation completes.
968
# the api signature is
969
# (local, master, old_revno, old_revid, new_revno, new_revid) where
970
# local is the local branch or None, master is the target branch,
971
# and an empty branch recieves new_revno of 0, new_revid of None.
972
self['post_uncommit'] = []
974
def install_hook(self, hook_name, a_callable):
975
"""Install a_callable in to the hook hook_name.
977
:param hook_name: A hook name. See the __init__ method of BranchHooks
978
for the complete list of hooks.
979
:param a_callable: The callable to be invoked when the hook triggers.
980
The exact signature will depend on the hook - see the __init__
981
method of BranchHooks for details on each hook.
984
self[hook_name].append(a_callable)
986
raise errors.UnknownHook('branch', hook_name)
989
# install the default hooks into the Branch class.
990
Branch.hooks = BranchHooks()
993
class BzrBranchFormat4(BranchFormat):
994
"""Bzr branch format 4.
997
- a revision-history file.
998
- a branch-lock lock file [ to be shared with the bzrdir ]
1001
def get_format_description(self):
1002
"""See BranchFormat.get_format_description()."""
1003
return "Branch format 4"
1005
def initialize(self, a_bzrdir):
1006
"""Create a branch of this format in a_bzrdir."""
1007
utf8_files = [('revision-history', ''),
1008
('branch-name', ''),
1010
return self._initialize_helper(a_bzrdir, utf8_files,
1011
lock_type='branch4', set_format=False)
1014
super(BzrBranchFormat4, self).__init__()
1015
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1017
def open(self, a_bzrdir, _found=False):
1018
"""Return the branch object for a_bzrdir
1020
_found is a private parameter, do not use it. It is used to indicate
1021
if format probing has already be done.
1024
# we are being called directly and must probe.
1025
raise NotImplementedError
1026
return BzrBranch(_format=self,
1027
_control_files=a_bzrdir._control_files,
1029
_repository=a_bzrdir.open_repository())
1032
return "Bazaar-NG branch format 4"
1035
class BzrBranchFormat5(BranchFormat):
1036
"""Bzr branch format 5.
1039
- a revision-history file.
1041
- a lock dir guarding the branch itself
1042
- all of this stored in a branch/ subdirectory
1043
- works with shared repositories.
1045
This format is new in bzr 0.8.
1048
def get_format_string(self):
1049
"""See BranchFormat.get_format_string()."""
1050
return "Bazaar-NG branch format 5\n"
1052
def get_format_description(self):
1053
"""See BranchFormat.get_format_description()."""
1054
return "Branch format 5"
1056
def initialize(self, a_bzrdir):
1057
"""Create a branch of this format in a_bzrdir."""
1058
utf8_files = [('revision-history', ''),
1059
('branch-name', ''),
1061
return self._initialize_helper(a_bzrdir, utf8_files)
1064
super(BzrBranchFormat5, self).__init__()
1065
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1067
def open(self, a_bzrdir, _found=False):
1068
"""Return the branch object for a_bzrdir
1070
_found is a private parameter, do not use it. It is used to indicate
1071
if format probing has already be done.
1074
format = BranchFormat.find_format(a_bzrdir)
1075
assert format.__class__ == self.__class__
1077
transport = a_bzrdir.get_branch_transport(None)
1078
control_files = lockable_files.LockableFiles(transport, 'lock',
1080
return BzrBranch5(_format=self,
1081
_control_files=control_files,
1083
_repository=a_bzrdir.find_repository())
1085
raise NotBranchError(path=transport.base)
1088
class BzrBranchFormat6(BzrBranchFormat5):
1089
"""Branch format with last-revision
1091
Unlike previous formats, this has no explicit revision history. Instead,
1092
this just stores the last-revision, and the left-hand history leading
1093
up to there is the history.
1095
This format was introduced in bzr 0.15
1098
def get_format_string(self):
1099
"""See BranchFormat.get_format_string()."""
1100
return "Bazaar Branch Format 6 (bzr 0.15)\n"
1102
def get_format_description(self):
1103
"""See BranchFormat.get_format_description()."""
1104
return "Branch format 6"
1106
def initialize(self, a_bzrdir):
1107
"""Create a branch of this format in a_bzrdir."""
1108
utf8_files = [('last-revision', '0 null:\n'),
1109
('branch-name', ''),
1110
('branch.conf', ''),
1113
return self._initialize_helper(a_bzrdir, utf8_files)
1115
def open(self, a_bzrdir, _found=False):
1116
"""Return the branch object for a_bzrdir
1118
_found is a private parameter, do not use it. It is used to indicate
1119
if format probing has already be done.
1122
format = BranchFormat.find_format(a_bzrdir)
1123
assert format.__class__ == self.__class__
1124
transport = a_bzrdir.get_branch_transport(None)
1125
control_files = lockable_files.LockableFiles(transport, 'lock',
1127
return BzrBranch6(_format=self,
1128
_control_files=control_files,
1130
_repository=a_bzrdir.find_repository())
1132
def supports_tags(self):
1136
class BranchReferenceFormat(BranchFormat):
1137
"""Bzr branch reference format.
1139
Branch references are used in implementing checkouts, they
1140
act as an alias to the real branch which is at some other url.
1147
def get_format_string(self):
1148
"""See BranchFormat.get_format_string()."""
1149
return "Bazaar-NG Branch Reference Format 1\n"
1151
def get_format_description(self):
1152
"""See BranchFormat.get_format_description()."""
1153
return "Checkout reference format 1"
1155
def get_reference(self, a_bzrdir):
1156
"""See BranchFormat.get_reference()."""
1157
transport = a_bzrdir.get_branch_transport(None)
1158
return transport.get('location').read()
1160
def initialize(self, a_bzrdir, target_branch=None):
1161
"""Create a branch of this format in a_bzrdir."""
1162
if target_branch is None:
1163
# this format does not implement branch itself, thus the implicit
1164
# creation contract must see it as uninitializable
1165
raise errors.UninitializableFormat(self)
1166
mutter('creating branch reference in %s', a_bzrdir.transport.base)
1167
branch_transport = a_bzrdir.get_branch_transport(self)
1168
branch_transport.put_bytes('location',
1169
target_branch.bzrdir.root_transport.base)
1170
branch_transport.put_bytes('format', self.get_format_string())
1171
return self.open(a_bzrdir, _found=True)
1174
super(BranchReferenceFormat, self).__init__()
1175
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1177
def _make_reference_clone_function(format, a_branch):
1178
"""Create a clone() routine for a branch dynamically."""
1179
def clone(to_bzrdir, revision_id=None):
1180
"""See Branch.clone()."""
1181
return format.initialize(to_bzrdir, a_branch)
1182
# cannot obey revision_id limits when cloning a reference ...
1183
# FIXME RBC 20060210 either nuke revision_id for clone, or
1184
# emit some sort of warning/error to the caller ?!
1187
def open(self, a_bzrdir, _found=False, location=None):
1188
"""Return the branch that the branch reference in a_bzrdir points at.
1190
_found is a private parameter, do not use it. It is used to indicate
1191
if format probing has already be done.
1194
format = BranchFormat.find_format(a_bzrdir)
1195
assert format.__class__ == self.__class__
1196
if location is None:
1197
location = self.get_reference(a_bzrdir)
1198
real_bzrdir = bzrdir.BzrDir.open(location)
1199
result = real_bzrdir.open_branch()
1200
# this changes the behaviour of result.clone to create a new reference
1201
# rather than a copy of the content of the branch.
1202
# I did not use a proxy object because that needs much more extensive
1203
# testing, and we are only changing one behaviour at the moment.
1204
# If we decide to alter more behaviours - i.e. the implicit nickname
1205
# then this should be refactored to introduce a tested proxy branch
1206
# and a subclass of that for use in overriding clone() and ....
1208
result.clone = self._make_reference_clone_function(result)
1212
# formats which have no format string are not discoverable
1213
# and not independently creatable, so are not registered.
1214
__default_format = BzrBranchFormat5()
1215
BranchFormat.register_format(__default_format)
1216
BranchFormat.register_format(BranchReferenceFormat())
1217
BranchFormat.register_format(BzrBranchFormat6())
1218
BranchFormat.set_default_format(__default_format)
1219
_legacy_formats = [BzrBranchFormat4(),
1222
class BzrBranch(Branch):
1223
"""A branch stored in the actual filesystem.
1225
Note that it's "local" in the context of the filesystem; it doesn't
1226
really matter if it's on an nfs/smb/afs/coda/... share, as long as
1227
it's writable, and can be accessed via the normal filesystem API.
1230
def __init__(self, _format=None,
1231
_control_files=None, a_bzrdir=None, _repository=None):
1232
"""Create new branch object at a particular location."""
1233
Branch.__init__(self)
1234
if a_bzrdir is None:
1235
raise ValueError('a_bzrdir must be supplied')
1237
self.bzrdir = a_bzrdir
1238
# self._transport used to point to the directory containing the
1239
# control directory, but was not used - now it's just the transport
1240
# for the branch control files. mbp 20070212
1241
self._base = self.bzrdir.transport.clone('..').base
1242
self._format = _format
1243
if _control_files is None:
1244
raise ValueError('BzrBranch _control_files is None')
1245
self.control_files = _control_files
1246
self._transport = _control_files._transport
1247
self.repository = _repository
1250
return '%s(%r)' % (self.__class__.__name__, self.base)
1254
def _get_base(self):
1255
"""Returns the directory containing the control directory."""
1258
base = property(_get_base, doc="The URL for the root of this branch.")
1260
def abspath(self, name):
1261
"""See Branch.abspath."""
1262
return self.control_files._transport.abspath(name)
1265
def get_root_id(self):
1266
"""See Branch.get_root_id."""
1267
tree = self.repository.revision_tree(self.last_revision())
1268
return tree.inventory.root.file_id
1270
def is_locked(self):
1271
return self.control_files.is_locked()
1273
def lock_write(self, tokens=None):
1274
if tokens is not None:
1275
branch_token, repo_token = tokens
1277
branch_token = repo_token = None
1278
repo_token = self.repository.lock_write(token=repo_token)
1280
branch_token = self.control_files.lock_write(token=branch_token)
1282
self.repository.unlock()
1285
tokens = (branch_token, repo_token)
1286
assert tokens == (None, None) or None not in tokens, (
1287
'Both branch and repository locks must return tokens, or else '
1288
'neither must return tokens. Got %r.' % (tokens,))
1289
if tokens == (None, None):
1294
def lock_read(self):
1295
self.repository.lock_read()
1297
self.control_files.lock_read()
1299
self.repository.unlock()
1303
# TODO: test for failed two phase locks. This is known broken.
1305
self.control_files.unlock()
1307
self.repository.unlock()
1308
if not self.control_files.is_locked():
1309
# we just released the lock
1310
self._clear_cached_state()
1312
def peek_lock_mode(self):
1313
if self.control_files._lock_count == 0:
1316
return self.control_files._lock_mode
1318
def get_physical_lock_status(self):
1319
return self.control_files.get_physical_lock_status()
1322
def print_file(self, file, revision_id):
1323
"""See Branch.print_file."""
1324
return self.repository.print_file(file, revision_id)
1327
def append_revision(self, *revision_ids):
1328
"""See Branch.append_revision."""
1329
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1330
for revision_id in revision_ids:
1331
_mod_revision.check_not_reserved_id(revision_id)
1332
mutter("add {%s} to revision-history" % revision_id)
1333
rev_history = self.revision_history()
1334
rev_history.extend(revision_ids)
1335
self.set_revision_history(rev_history)
1337
def _write_revision_history(self, history):
1338
"""Factored out of set_revision_history.
1340
This performs the actual writing to disk.
1341
It is intended to be called by BzrBranch5.set_revision_history."""
1342
self.control_files.put_bytes(
1343
'revision-history', '\n'.join(history))
1346
def set_revision_history(self, rev_history):
1347
"""See Branch.set_revision_history."""
1348
rev_history = [osutils.safe_revision_id(r) for r in rev_history]
1349
self._write_revision_history(rev_history)
1350
self._cache_revision_history(rev_history)
1351
for hook in Branch.hooks['set_rh']:
1352
hook(self, rev_history)
1355
def set_last_revision_info(self, revno, revision_id):
1356
revision_id = osutils.safe_revision_id(revision_id)
1357
history = self._lefthand_history(revision_id)
1358
assert len(history) == revno, '%d != %d' % (len(history), revno)
1359
self.set_revision_history(history)
1361
def _gen_revision_history(self):
1362
history = self.control_files.get('revision-history').read().split('\n')
1363
if history[-1:] == ['']:
1364
# There shouldn't be a trailing newline, but just in case.
1368
def _lefthand_history(self, revision_id, last_rev=None,
1370
# stop_revision must be a descendant of last_revision
1371
stop_graph = self.repository.get_revision_graph(revision_id)
1372
if last_rev is not None and last_rev not in stop_graph:
1373
# our previous tip is not merged into stop_revision
1374
raise errors.DivergedBranches(self, other_branch)
1375
# make a new revision history from the graph
1376
current_rev_id = revision_id
1378
while current_rev_id not in (None, _mod_revision.NULL_REVISION):
1379
new_history.append(current_rev_id)
1380
current_rev_id_parents = stop_graph[current_rev_id]
1382
current_rev_id = current_rev_id_parents[0]
1384
current_rev_id = None
1385
new_history.reverse()
1389
def generate_revision_history(self, revision_id, last_rev=None,
1391
"""Create a new revision history that will finish with revision_id.
1393
:param revision_id: the new tip to use.
1394
:param last_rev: The previous last_revision. If not None, then this
1395
must be a ancestory of revision_id, or DivergedBranches is raised.
1396
:param other_branch: The other branch that DivergedBranches should
1397
raise with respect to.
1399
revision_id = osutils.safe_revision_id(revision_id)
1400
self.set_revision_history(self._lefthand_history(revision_id,
1401
last_rev, other_branch))
1404
def update_revisions(self, other, stop_revision=None):
1405
"""See Branch.update_revisions."""
1408
if stop_revision is None:
1409
stop_revision = other.last_revision()
1410
if stop_revision is None:
1411
# if there are no commits, we're done.
1414
stop_revision = osutils.safe_revision_id(stop_revision)
1415
# whats the current last revision, before we fetch [and change it
1417
last_rev = self.last_revision()
1418
# we fetch here regardless of whether we need to so that we pickup
1420
self.fetch(other, stop_revision)
1421
my_ancestry = self.repository.get_ancestry(last_rev)
1422
if stop_revision in my_ancestry:
1423
# last_revision is a descendant of stop_revision
1425
self.generate_revision_history(stop_revision, last_rev=last_rev,
1430
def basis_tree(self):
1431
"""See Branch.basis_tree."""
1432
return self.repository.revision_tree(self.last_revision())
1434
@deprecated_method(zero_eight)
1435
def working_tree(self):
1436
"""Create a Working tree object for this branch."""
1438
from bzrlib.transport.local import LocalTransport
1439
if (self.base.find('://') != -1 or
1440
not isinstance(self._transport, LocalTransport)):
1441
raise NoWorkingTree(self.base)
1442
return self.bzrdir.open_workingtree()
1445
def pull(self, source, overwrite=False, stop_revision=None,
1446
_hook_master=None, _run_hooks=True):
1449
:param _hook_master: Private parameter - set the branch to
1450
be supplied as the master to push hooks.
1451
:param _run_hooks: Private parameter - allow disabling of
1452
hooks, used when pushing to a master branch.
1454
result = PullResult()
1455
result.source_branch = source
1456
result.target_branch = self
1459
result.old_revno, result.old_revid = self.last_revision_info()
1461
self.update_revisions(source, stop_revision)
1462
except DivergedBranches:
1466
if stop_revision is None:
1467
stop_revision = source.last_revision()
1468
self.generate_revision_history(stop_revision)
1469
result.tag_conflicts = source.tags.merge_to(self.tags)
1470
result.new_revno, result.new_revid = self.last_revision_info()
1472
result.master_branch = _hook_master
1473
result.local_branch = self
1475
result.master_branch = self
1476
result.local_branch = None
1478
for hook in Branch.hooks['post_pull']:
1484
def _get_parent_location(self):
1485
_locs = ['parent', 'pull', 'x-pull']
1488
return self.control_files.get(l).read().strip('\n')
1494
def push(self, target, overwrite=False, stop_revision=None,
1495
_hook_master=None, _run_hooks=True):
1498
:param _hook_master: Private parameter - set the branch to
1499
be supplied as the master to push hooks.
1500
:param _run_hooks: Private parameter - allow disabling of
1501
hooks, used when pushing to a master branch.
1503
result = PushResult()
1504
result.source_branch = self
1505
result.target_branch = target
1508
result.old_revno, result.old_revid = target.last_revision_info()
1510
target.update_revisions(self, stop_revision)
1511
except DivergedBranches:
1515
target.set_revision_history(self.revision_history())
1516
result.tag_conflicts = self.tags.merge_to(target.tags)
1517
result.new_revno, result.new_revid = target.last_revision_info()
1519
result.master_branch = _hook_master
1520
result.local_branch = target
1522
result.master_branch = target
1523
result.local_branch = None
1525
for hook in Branch.hooks['post_push']:
1531
def get_parent(self):
1532
"""See Branch.get_parent."""
1534
assert self.base[-1] == '/'
1535
parent = self._get_parent_location()
1538
# This is an old-format absolute path to a local branch
1539
# turn it into a url
1540
if parent.startswith('/'):
1541
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1543
return urlutils.join(self.base[:-1], parent)
1544
except errors.InvalidURLJoin, e:
1545
raise errors.InaccessibleParent(parent, self.base)
1547
def set_push_location(self, location):
1548
"""See Branch.set_push_location."""
1549
self.get_config().set_user_option(
1550
'push_location', location,
1551
store=_mod_config.STORE_LOCATION_NORECURSE)
1554
def set_parent(self, url):
1555
"""See Branch.set_parent."""
1556
# TODO: Maybe delete old location files?
1557
# URLs should never be unicode, even on the local fs,
1558
# FIXUP this and get_parent in a future branch format bump:
1559
# read and rewrite the file, and have the new format code read
1560
# using .get not .get_utf8. RBC 20060125
1562
if isinstance(url, unicode):
1564
url = url.encode('ascii')
1565
except UnicodeEncodeError:
1566
raise bzrlib.errors.InvalidURL(url,
1567
"Urls must be 7-bit ascii, "
1568
"use bzrlib.urlutils.escape")
1569
url = urlutils.relative_url(self.base, url)
1570
self._set_parent_location(url)
1572
def _set_parent_location(self, url):
1574
self.control_files._transport.delete('parent')
1576
assert isinstance(url, str)
1577
self.control_files.put_bytes('parent', url + '\n')
1579
@deprecated_function(zero_nine)
1580
def tree_config(self):
1581
"""DEPRECATED; call get_config instead.
1582
TreeConfig has become part of BranchConfig."""
1583
return TreeConfig(self)
1586
class BzrBranch5(BzrBranch):
1587
"""A format 5 branch. This supports new features over plan branches.
1589
It has support for a master_branch which is the data for bound branches.
1597
super(BzrBranch5, self).__init__(_format=_format,
1598
_control_files=_control_files,
1600
_repository=_repository)
1603
def pull(self, source, overwrite=False, stop_revision=None,
1605
"""Extends branch.pull to be bound branch aware.
1607
:param _run_hooks: Private parameter used to force hook running
1608
off during bound branch double-pushing.
1610
bound_location = self.get_bound_location()
1611
master_branch = None
1612
if bound_location and source.base != bound_location:
1613
# not pulling from master, so we need to update master.
1614
master_branch = self.get_master_branch()
1615
master_branch.lock_write()
1618
# pull from source into master.
1619
master_branch.pull(source, overwrite, stop_revision,
1621
return super(BzrBranch5, self).pull(source, overwrite,
1622
stop_revision, _hook_master=master_branch,
1623
_run_hooks=_run_hooks)
1626
master_branch.unlock()
1629
def push(self, target, overwrite=False, stop_revision=None):
1630
"""Updates branch.push to be bound branch aware."""
1631
bound_location = target.get_bound_location()
1632
master_branch = None
1633
if bound_location and target.base != bound_location:
1634
# not pushing to master, so we need to update master.
1635
master_branch = target.get_master_branch()
1636
master_branch.lock_write()
1639
# push into the master from this branch.
1640
super(BzrBranch5, self).push(master_branch, overwrite,
1641
stop_revision, _run_hooks=False)
1642
# and push into the target branch from this. Note that we push from
1643
# this branch again, because its considered the highest bandwidth
1645
return super(BzrBranch5, self).push(target, overwrite,
1646
stop_revision, _hook_master=master_branch)
1649
master_branch.unlock()
1651
def get_bound_location(self):
1653
return self.control_files.get_utf8('bound').read()[:-1]
1654
except errors.NoSuchFile:
1658
def get_master_branch(self):
1659
"""Return the branch we are bound to.
1661
:return: Either a Branch, or None
1663
This could memoise the branch, but if thats done
1664
it must be revalidated on each new lock.
1665
So for now we just don't memoise it.
1666
# RBC 20060304 review this decision.
1668
bound_loc = self.get_bound_location()
1672
return Branch.open(bound_loc)
1673
except (errors.NotBranchError, errors.ConnectionError), e:
1674
raise errors.BoundBranchConnectionFailure(
1678
def set_bound_location(self, location):
1679
"""Set the target where this branch is bound to.
1681
:param location: URL to the target branch
1684
self.control_files.put_utf8('bound', location+'\n')
1687
self.control_files._transport.delete('bound')
1693
def bind(self, other):
1694
"""Bind this branch to the branch other.
1696
This does not push or pull data between the branches, though it does
1697
check for divergence to raise an error when the branches are not
1698
either the same, or one a prefix of the other. That behaviour may not
1699
be useful, so that check may be removed in future.
1701
:param other: The branch to bind to
1704
# TODO: jam 20051230 Consider checking if the target is bound
1705
# It is debatable whether you should be able to bind to
1706
# a branch which is itself bound.
1707
# Committing is obviously forbidden,
1708
# but binding itself may not be.
1709
# Since we *have* to check at commit time, we don't
1710
# *need* to check here
1712
# we want to raise diverged if:
1713
# last_rev is not in the other_last_rev history, AND
1714
# other_last_rev is not in our history, and do it without pulling
1716
last_rev = self.last_revision()
1717
if last_rev is not None:
1720
other_last_rev = other.last_revision()
1721
if other_last_rev is not None:
1722
# neither branch is new, we have to do some work to
1723
# ascertain diversion.
1724
remote_graph = other.repository.get_revision_graph(
1726
local_graph = self.repository.get_revision_graph(last_rev)
1727
if (last_rev not in remote_graph and
1728
other_last_rev not in local_graph):
1729
raise errors.DivergedBranches(self, other)
1732
self.set_bound_location(other.base)
1736
"""If bound, unbind"""
1737
return self.set_bound_location(None)
1741
"""Synchronise this branch with the master branch if any.
1743
:return: None or the last_revision that was pivoted out during the
1746
master = self.get_master_branch()
1747
if master is not None:
1748
old_tip = self.last_revision()
1749
self.pull(master, overwrite=True)
1750
if old_tip in self.repository.get_ancestry(self.last_revision()):
1756
class BzrBranchExperimental(BzrBranch5):
1757
"""Bzr experimental branch format
1760
- a revision-history file.
1762
- a lock dir guarding the branch itself
1763
- all of this stored in a branch/ subdirectory
1764
- works with shared repositories.
1765
- a tag dictionary in the branch
1767
This format is new in bzr 0.15, but shouldn't be used for real data,
1770
This class acts as it's own BranchFormat.
1773
_matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1776
def get_format_string(cls):
1777
"""See BranchFormat.get_format_string()."""
1778
return "Bazaar-NG branch format experimental\n"
1781
def get_format_description(cls):
1782
"""See BranchFormat.get_format_description()."""
1783
return "Experimental branch format"
1786
def _initialize_control_files(cls, a_bzrdir, utf8_files, lock_filename,
1788
branch_transport = a_bzrdir.get_branch_transport(cls)
1789
control_files = lockable_files.LockableFiles(branch_transport,
1790
lock_filename, lock_class)
1791
control_files.create_lock()
1792
control_files.lock_write()
1794
for filename, content in utf8_files:
1795
control_files.put_utf8(filename, content)
1797
control_files.unlock()
1800
def initialize(cls, a_bzrdir):
1801
"""Create a branch of this format in a_bzrdir."""
1802
utf8_files = [('format', cls.get_format_string()),
1803
('revision-history', ''),
1804
('branch-name', ''),
1807
cls._initialize_control_files(a_bzrdir, utf8_files,
1808
'lock', lockdir.LockDir)
1809
return cls.open(a_bzrdir, _found=True)
1812
def open(cls, a_bzrdir, _found=False):
1813
"""Return the branch object for a_bzrdir
1815
_found is a private parameter, do not use it. It is used to indicate
1816
if format probing has already be done.
1819
format = BranchFormat.find_format(a_bzrdir)
1820
assert format.__class__ == cls
1821
transport = a_bzrdir.get_branch_transport(None)
1822
control_files = lockable_files.LockableFiles(transport, 'lock',
1824
return cls(_format=cls,
1825
_control_files=control_files,
1827
_repository=a_bzrdir.find_repository())
1830
def is_supported(cls):
1833
def _make_tags(self):
1834
return BasicTags(self)
1837
def supports_tags(cls):
1841
BranchFormat.register_format(BzrBranchExperimental)
1844
class BzrBranch6(BzrBranch5):
1847
def last_revision_info(self):
1848
revision_string = self.control_files.get('last-revision').read()
1849
revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
1850
revision_id = cache_utf8.get_cached_utf8(revision_id)
1852
return revno, revision_id
1854
def last_revision(self):
1855
"""Return last revision id, or None"""
1856
revision_id = self.last_revision_info()[1]
1857
if revision_id == _mod_revision.NULL_REVISION:
1861
def _write_last_revision_info(self, revno, revision_id):
1862
"""Simply write out the revision id, with no checks.
1864
Use set_last_revision_info to perform this safely.
1866
Does not update the revision_history cache.
1867
Intended to be called by set_last_revision_info and
1868
_write_revision_history.
1870
if revision_id is None:
1871
revision_id = 'null:'
1872
out_string = '%d %s\n' % (revno, revision_id)
1873
self.control_files.put_bytes('last-revision', out_string)
1876
def set_last_revision_info(self, revno, revision_id):
1877
revision_id = osutils.safe_revision_id(revision_id)
1878
if self._get_append_revisions_only():
1879
self._check_history_violation(revision_id)
1880
self._write_last_revision_info(revno, revision_id)
1881
self._clear_cached_state()
1883
def _check_history_violation(self, revision_id):
1884
last_revision = self.last_revision()
1885
if last_revision is None:
1887
if last_revision not in self._lefthand_history(revision_id):
1888
raise errors.AppendRevisionsOnlyViolation(self.base)
1890
def _gen_revision_history(self):
1891
"""Generate the revision history from last revision
1893
history = list(self.repository.iter_reverse_revision_history(
1894
self.last_revision()))
1898
def _write_revision_history(self, history):
1899
"""Factored out of set_revision_history.
1901
This performs the actual writing to disk, with format-specific checks.
1902
It is intended to be called by BzrBranch5.set_revision_history.
1904
if len(history) == 0:
1905
last_revision = 'null:'
1907
if history != self._lefthand_history(history[-1]):
1908
raise errors.NotLefthandHistory(history)
1909
last_revision = history[-1]
1910
if self._get_append_revisions_only():
1911
self._check_history_violation(last_revision)
1912
self._write_last_revision_info(len(history), last_revision)
1915
def append_revision(self, *revision_ids):
1916
revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1917
if len(revision_ids) == 0:
1919
prev_revno, prev_revision = self.last_revision_info()
1920
for revision in self.repository.get_revisions(revision_ids):
1921
if prev_revision == _mod_revision.NULL_REVISION:
1922
if revision.parent_ids != []:
1923
raise errors.NotLeftParentDescendant(self, prev_revision,
1924
revision.revision_id)
1926
if revision.parent_ids[0] != prev_revision:
1927
raise errors.NotLeftParentDescendant(self, prev_revision,
1928
revision.revision_id)
1929
prev_revision = revision.revision_id
1930
self.set_last_revision_info(prev_revno + len(revision_ids),
1934
def _set_parent_location(self, url):
1935
"""Set the parent branch"""
1936
self._set_config_location('parent_location', url, make_relative=True)
1939
def _get_parent_location(self):
1940
"""Set the parent branch"""
1941
return self._get_config_location('parent_location')
1943
def set_push_location(self, location):
1944
"""See Branch.set_push_location."""
1945
self._set_config_location('push_location', location)
1947
def set_bound_location(self, location):
1948
"""See Branch.set_push_location."""
1950
config = self.get_config()
1951
if location is None:
1952
if config.get_user_option('bound') != 'True':
1955
config.set_user_option('bound', 'False')
1958
self._set_config_location('bound_location', location,
1960
config.set_user_option('bound', 'True')
1963
def _get_bound_location(self, bound):
1964
"""Return the bound location in the config file.
1966
Return None if the bound parameter does not match"""
1967
config = self.get_config()
1968
config_bound = (config.get_user_option('bound') == 'True')
1969
if config_bound != bound:
1971
return self._get_config_location('bound_location', config=config)
1973
def get_bound_location(self):
1974
"""See Branch.set_push_location."""
1975
return self._get_bound_location(True)
1977
def get_old_bound_location(self):
1978
"""See Branch.get_old_bound_location"""
1979
return self._get_bound_location(False)
1981
def set_append_revisions_only(self, enabled):
1986
self.get_config().set_user_option('append_revisions_only', value)
1988
def _get_append_revisions_only(self):
1989
value = self.get_config().get_user_option('append_revisions_only')
1990
return value == 'True'
1992
def _synchronize_history(self, destination, revision_id):
1993
"""Synchronize last revision and revision history between branches.
1995
This version is most efficient when the destination is also a
1996
BzrBranch6, but works for BzrBranch5, as long as the destination's
1997
repository contains all the lefthand ancestors of the intended
1998
last_revision. If not, set_last_revision_info will fail.
2000
:param destination: The branch to copy the history into
2001
:param revision_id: The revision-id to truncate history at. May
2002
be None to copy complete history.
2004
if revision_id is None:
2005
revno, revision_id = self.last_revision_info()
2007
revno = self.revision_id_to_revno(revision_id)
2008
destination.set_last_revision_info(revno, revision_id)
2010
def _make_tags(self):
2011
return BasicTags(self)
2014
class BranchTestProviderAdapter(object):
2015
"""A tool to generate a suite testing multiple branch formats at once.
2017
This is done by copying the test once for each transport and injecting
2018
the transport_server, transport_readonly_server, and branch_format
2019
classes into each copy. Each copy is also given a new id() to make it
2023
def __init__(self, transport_server, transport_readonly_server, formats,
2024
vfs_transport_factory=None):
2025
self._transport_server = transport_server
2026
self._transport_readonly_server = transport_readonly_server
2027
self._formats = formats
2028
self._vfs_transport_factory = vfs_transport_factory
2030
def adapt(self, test):
2031
result = TestSuite()
2032
for branch_format, bzrdir_format in self._formats:
2033
new_test = deepcopy(test)
2034
new_test.transport_server = self._transport_server
2035
new_test.transport_readonly_server = self._transport_readonly_server
2036
if self._vfs_transport_factory:
2037
new_test.vfs_transport_factory = self._vfs_transport_factory
2038
new_test.bzrdir_format = bzrdir_format
2039
new_test.branch_format = branch_format
2040
def make_new_test_id():
2041
# the format can be either a class or an instance
2042
name = getattr(branch_format, '__name__',
2043
branch_format.__class__.__name__)
2044
new_id = "%s(%s)" % (new_test.id(), name)
2045
return lambda: new_id
2046
new_test.id = make_new_test_id()
2047
result.addTest(new_test)
2051
######################################################################
2052
# results of operations
2055
class _Result(object):
2057
def _show_tag_conficts(self, to_file):
2058
if not getattr(self, 'tag_conflicts', None):
2060
to_file.write('Conflicting tags:\n')
2061
for name, value1, value2 in self.tag_conflicts:
2062
to_file.write(' %s\n' % (name, ))
2065
class PullResult(_Result):
2066
"""Result of a Branch.pull operation.
2068
:ivar old_revno: Revision number before pull.
2069
:ivar new_revno: Revision number after pull.
2070
:ivar old_revid: Tip revision id before pull.
2071
:ivar new_revid: Tip revision id after pull.
2072
:ivar source_branch: Source (local) branch object.
2073
:ivar master_branch: Master branch of the target, or None.
2074
:ivar target_branch: Target/destination branch object.
2078
# DEPRECATED: pull used to return the change in revno
2079
return self.new_revno - self.old_revno
2081
def report(self, to_file):
2082
if self.old_revid == self.new_revid:
2083
to_file.write('No revisions to pull.\n')
2085
to_file.write('Now on revision %d.\n' % self.new_revno)
2086
self._show_tag_conficts(to_file)
2089
class PushResult(_Result):
2090
"""Result of a Branch.push operation.
2092
:ivar old_revno: Revision number before push.
2093
:ivar new_revno: Revision number after push.
2094
:ivar old_revid: Tip revision id before push.
2095
:ivar new_revid: Tip revision id after push.
2096
:ivar source_branch: Source branch object.
2097
:ivar master_branch: Master branch of the target, or None.
2098
:ivar target_branch: Target/destination branch object.
2102
# DEPRECATED: push used to return the change in revno
2103
return self.new_revno - self.old_revno
2105
def report(self, to_file):
2106
"""Write a human-readable description of the result."""
2107
if self.old_revid == self.new_revid:
2108
to_file.write('No new revisions to push.\n')
2110
to_file.write('Pushed up to revision %d.\n' % self.new_revno)
2111
self._show_tag_conficts(to_file)
2114
class BranchCheckResult(object):
2115
"""Results of checking branch consistency.
2120
def __init__(self, branch):
2121
self.branch = branch
2123
def report_results(self, verbose):
2124
"""Report the check results via trace.note.
2126
:param verbose: Requests more detailed display of what was checked,
2129
note('checked branch %s format %s',
2131
self.branch._format)
2134
class Converter5to6(object):
2135
"""Perform an in-place upgrade of format 5 to format 6"""
2137
def convert(self, branch):
2138
# Data for 5 and 6 can peacefully coexist.
2139
format = BzrBranchFormat6()
2140
new_branch = format.open(branch.bzrdir, _found=True)
2142
# Copy source data into target
2143
new_branch.set_last_revision_info(*branch.last_revision_info())
2144
new_branch.set_parent(branch.get_parent())
2145
new_branch.set_bound_location(branch.get_bound_location())
2146
new_branch.set_push_location(branch.get_push_location())
2148
# New branch has no tags by default
2149
new_branch.tags._set_tag_dict({})
2151
# Copying done; now update target format
2152
new_branch.control_files.put_utf8('format',
2153
format.get_format_string())
2155
# Clean up old files
2156
new_branch.control_files._transport.delete('revision-history')
2158
branch.set_parent(None)
2161
branch.set_bound_location(None)