1
# Copyright (C) 2005, 2006, 2007, 2008 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
20
from bzrlib.lazy_import import lazy_import
21
lazy_import(globals(), """
22
from itertools import chain
26
config as _mod_config,
32
revision as _mod_revision,
38
from bzrlib.config import BranchConfig
39
from bzrlib.repofmt.pack_repo import RepositoryFormatKnitPack5RichRoot
40
from bzrlib.tag import (
46
from bzrlib.decorators import needs_read_lock, needs_write_lock
47
from bzrlib.hooks import Hooks
48
from bzrlib.symbol_versioning import (
52
from bzrlib.trace import mutter, mutter_callsite, note, is_quiet
55
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
56
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
57
BZR_BRANCH_FORMAT_6 = "Bazaar Branch Format 6 (bzr 0.15)\n"
60
# TODO: Maybe include checks for common corruption of newlines, etc?
62
# TODO: Some operations like log might retrieve the same revisions
63
# repeatedly to calculate deltas. We could perhaps have a weakref
64
# cache in memory to make this faster. In general anything can be
65
# cached in memory between lock and unlock operations. .. nb thats
66
# what the transaction identity map provides
69
######################################################################
73
"""Branch holding a history of revisions.
76
Base directory/url of the branch.
78
hooks: An instance of BranchHooks.
80
# this is really an instance variable - FIXME move it there
84
# override this to set the strategy for storing tags
86
return DisabledTags(self)
88
def __init__(self, *ignored, **ignored_too):
89
self.tags = self._make_tags()
90
self._revision_history_cache = None
91
self._revision_id_to_revno_cache = None
92
self._last_revision_info_cache = None
94
hooks = Branch.hooks['open']
99
"""Called by init to allow simpler extension of the base class."""
101
def break_lock(self):
102
"""Break a lock if one is present from another instance.
104
Uses the ui factory to ask for confirmation if the lock may be from
107
This will probe the repository for its lock as well.
109
self.control_files.break_lock()
110
self.repository.break_lock()
111
master = self.get_master_branch()
112
if master is not None:
116
def open(base, _unsupported=False, possible_transports=None):
117
"""Open the branch rooted at base.
119
For instance, if the branch is at URL/.bzr/branch,
120
Branch.open(URL) -> a Branch instance.
122
control = bzrdir.BzrDir.open(base, _unsupported,
123
possible_transports=possible_transports)
124
return control.open_branch(_unsupported)
127
def open_from_transport(transport, _unsupported=False):
128
"""Open the branch rooted at transport"""
129
control = bzrdir.BzrDir.open_from_transport(transport, _unsupported)
130
return control.open_branch(_unsupported)
133
def open_containing(url, possible_transports=None):
134
"""Open an existing branch which contains url.
136
This probes for a branch at url, and searches upwards from there.
138
Basically we keep looking up until we find the control directory or
139
run into the root. If there isn't one, raises NotBranchError.
140
If there is one and it is either an unrecognised format or an unsupported
141
format, UnknownFormatError or UnsupportedFormatError are raised.
142
If there is one, it is returned, along with the unused portion of url.
144
control, relpath = bzrdir.BzrDir.open_containing(url,
146
return control.open_branch(), relpath
148
def get_config(self):
149
return BranchConfig(self)
152
config = self.get_config()
153
if not config.has_explicit_nickname(): # explicit overrides master
154
master = self.get_master_branch()
155
if master is not None:
156
# return the master branch value
157
config = master.get_config()
158
return config.get_nickname()
160
def _set_nick(self, nick):
161
self.get_config().set_user_option('nickname', nick, warn_masked=True)
163
nick = property(_get_nick, _set_nick)
166
raise NotImplementedError(self.is_locked)
168
def lock_write(self):
169
raise NotImplementedError(self.lock_write)
172
raise NotImplementedError(self.lock_read)
175
raise NotImplementedError(self.unlock)
177
def peek_lock_mode(self):
178
"""Return lock mode for the Branch: 'r', 'w' or None"""
179
raise NotImplementedError(self.peek_lock_mode)
181
def get_physical_lock_status(self):
182
raise NotImplementedError(self.get_physical_lock_status)
185
def get_revision_id_to_revno_map(self):
186
"""Return the revision_id => dotted revno map.
188
This will be regenerated on demand, but will be cached.
190
:return: A dictionary mapping revision_id => dotted revno.
191
This dictionary should not be modified by the caller.
193
if self._revision_id_to_revno_cache is not None:
194
mapping = self._revision_id_to_revno_cache
196
mapping = self._gen_revno_map()
197
self._cache_revision_id_to_revno(mapping)
198
# TODO: jam 20070417 Since this is being cached, should we be returning
200
# I would rather not, and instead just declare that users should not
201
# modify the return value.
204
def _gen_revno_map(self):
205
"""Create a new mapping from revision ids to dotted revnos.
207
Dotted revnos are generated based on the current tip in the revision
209
This is the worker function for get_revision_id_to_revno_map, which
210
just caches the return value.
212
:return: A dictionary mapping revision_id => dotted revno.
214
last_revision = self.last_revision()
215
revision_graph = repository._old_get_graph(self.repository,
217
merge_sorted_revisions = tsort.merge_sort(
222
revision_id_to_revno = dict((rev_id, revno)
223
for seq_num, rev_id, depth, revno, end_of_merge
224
in merge_sorted_revisions)
225
return revision_id_to_revno
227
def leave_lock_in_place(self):
228
"""Tell this branch object not to release the physical lock when this
231
If lock_write doesn't return a token, then this method is not supported.
233
self.control_files.leave_in_place()
235
def dont_leave_lock_in_place(self):
236
"""Tell this branch object to release the physical lock when this
237
object is unlocked, even if it didn't originally acquire it.
239
If lock_write doesn't return a token, then this method is not supported.
241
self.control_files.dont_leave_in_place()
243
def bind(self, other):
244
"""Bind the local branch the other branch.
246
:param other: The branch to bind to
249
raise errors.UpgradeRequired(self.base)
252
def fetch(self, from_branch, last_revision=None, pb=None):
253
"""Copy revisions from from_branch into this branch.
255
:param from_branch: Where to copy from.
256
:param last_revision: What revision to stop at (None for at the end
258
:param pb: An optional progress bar to use.
260
Returns the copied revision count and the failed revisions in a tuple:
263
if self.base == from_branch.base:
266
nested_pb = ui.ui_factory.nested_progress_bar()
271
from_branch.lock_read()
273
if last_revision is None:
274
pb.update('get source history')
275
last_revision = from_branch.last_revision()
276
last_revision = _mod_revision.ensure_null(last_revision)
277
return self.repository.fetch(from_branch.repository,
278
revision_id=last_revision,
281
if nested_pb is not None:
285
def get_bound_location(self):
286
"""Return the URL of the branch we are bound to.
288
Older format branches cannot bind, please be sure to use a metadir
293
def get_old_bound_location(self):
294
"""Return the URL of the branch we used to be bound to
296
raise errors.UpgradeRequired(self.base)
298
def get_commit_builder(self, parents, config=None, timestamp=None,
299
timezone=None, committer=None, revprops=None,
301
"""Obtain a CommitBuilder for this branch.
303
:param parents: Revision ids of the parents of the new revision.
304
:param config: Optional configuration to use.
305
:param timestamp: Optional timestamp recorded for commit.
306
:param timezone: Optional timezone for timestamp.
307
:param committer: Optional committer to set for commit.
308
:param revprops: Optional dictionary of revision properties.
309
:param revision_id: Optional revision id.
313
config = self.get_config()
315
return self.repository.get_commit_builder(self, parents, config,
316
timestamp, timezone, committer, revprops, revision_id)
318
def get_master_branch(self, possible_transports=None):
319
"""Return the branch we are bound to.
321
:return: Either a Branch, or None
325
def get_revision_delta(self, revno):
326
"""Return the delta for one revision.
328
The delta is relative to its mainline predecessor, or the
329
empty tree for revision 1.
331
rh = self.revision_history()
332
if not (1 <= revno <= len(rh)):
333
raise errors.InvalidRevisionNumber(revno)
334
return self.repository.get_revision_delta(rh[revno-1])
336
def get_stacked_on_url(self):
337
"""Get the URL this branch is stacked against.
339
:raises NotStacked: If the branch is not stacked.
340
:raises UnstackableBranchFormat: If the branch does not support
343
raise NotImplementedError(self.get_stacked_on_url)
345
def print_file(self, file, revision_id):
346
"""Print `file` to stdout."""
347
raise NotImplementedError(self.print_file)
349
def set_revision_history(self, rev_history):
350
raise NotImplementedError(self.set_revision_history)
352
def set_stacked_on_url(self, url):
353
"""Set the URL this branch is stacked against.
355
:raises UnstackableBranchFormat: If the branch does not support
357
:raises UnstackableRepositoryFormat: If the repository does not support
360
raise NotImplementedError(self.set_stacked_on_url)
362
def _cache_revision_history(self, rev_history):
363
"""Set the cached revision history to rev_history.
365
The revision_history method will use this cache to avoid regenerating
366
the revision history.
368
This API is semi-public; it only for use by subclasses, all other code
369
should consider it to be private.
371
self._revision_history_cache = rev_history
373
def _cache_revision_id_to_revno(self, revision_id_to_revno):
374
"""Set the cached revision_id => revno map to revision_id_to_revno.
376
This API is semi-public; it only for use by subclasses, all other code
377
should consider it to be private.
379
self._revision_id_to_revno_cache = revision_id_to_revno
381
def _clear_cached_state(self):
382
"""Clear any cached data on this branch, e.g. cached revision history.
384
This means the next call to revision_history will need to call
385
_gen_revision_history.
387
This API is semi-public; it only for use by subclasses, all other code
388
should consider it to be private.
390
self._revision_history_cache = None
391
self._revision_id_to_revno_cache = None
392
self._last_revision_info_cache = None
394
def _gen_revision_history(self):
395
"""Return sequence of revision hashes on to this branch.
397
Unlike revision_history, this method always regenerates or rereads the
398
revision history, i.e. it does not cache the result, so repeated calls
401
Concrete subclasses should override this instead of revision_history so
402
that subclasses do not need to deal with caching logic.
404
This API is semi-public; it only for use by subclasses, all other code
405
should consider it to be private.
407
raise NotImplementedError(self._gen_revision_history)
410
def revision_history(self):
411
"""Return sequence of revision ids on this branch.
413
This method will cache the revision history for as long as it is safe to
416
if 'evil' in debug.debug_flags:
417
mutter_callsite(3, "revision_history scales with history.")
418
if self._revision_history_cache is not None:
419
history = self._revision_history_cache
421
history = self._gen_revision_history()
422
self._cache_revision_history(history)
426
"""Return current revision number for this branch.
428
That is equivalent to the number of revisions committed to
431
return self.last_revision_info()[0]
434
"""Older format branches cannot bind or unbind."""
435
raise errors.UpgradeRequired(self.base)
437
def set_append_revisions_only(self, enabled):
438
"""Older format branches are never restricted to append-only"""
439
raise errors.UpgradeRequired(self.base)
441
def last_revision(self):
442
"""Return last revision id, or NULL_REVISION."""
443
return self.last_revision_info()[1]
446
def last_revision_info(self):
447
"""Return information about the last revision.
449
:return: A tuple (revno, revision_id).
451
if self._last_revision_info_cache is None:
452
self._last_revision_info_cache = self._last_revision_info()
453
return self._last_revision_info_cache
455
def _last_revision_info(self):
456
rh = self.revision_history()
459
return (revno, rh[-1])
461
return (0, _mod_revision.NULL_REVISION)
463
@deprecated_method(deprecated_in((1, 6, 0)))
464
def missing_revisions(self, other, stop_revision=None):
465
"""Return a list of new revisions that would perfectly fit.
467
If self and other have not diverged, return a list of the revisions
468
present in other, but missing from self.
470
self_history = self.revision_history()
471
self_len = len(self_history)
472
other_history = other.revision_history()
473
other_len = len(other_history)
474
common_index = min(self_len, other_len) -1
475
if common_index >= 0 and \
476
self_history[common_index] != other_history[common_index]:
477
raise errors.DivergedBranches(self, other)
479
if stop_revision is None:
480
stop_revision = other_len
482
if stop_revision > other_len:
483
raise errors.NoSuchRevision(self, stop_revision)
484
return other_history[self_len:stop_revision]
487
def update_revisions(self, other, stop_revision=None, overwrite=False,
489
"""Pull in new perfect-fit revisions.
491
:param other: Another Branch to pull from
492
:param stop_revision: Updated until the given revision
493
:param overwrite: Always set the branch pointer, rather than checking
494
to see if it is a proper descendant.
495
:param graph: A Graph object that can be used to query history
496
information. This can be None.
501
other_revno, other_last_revision = other.last_revision_info()
502
stop_revno = None # unknown
503
if stop_revision is None:
504
stop_revision = other_last_revision
505
if _mod_revision.is_null(stop_revision):
506
# if there are no commits, we're done.
508
stop_revno = other_revno
510
# what's the current last revision, before we fetch [and change it
512
last_rev = _mod_revision.ensure_null(self.last_revision())
513
# we fetch here so that we don't process data twice in the common
514
# case of having something to pull, and so that the check for
515
# already merged can operate on the just fetched graph, which will
516
# be cached in memory.
517
self.fetch(other, stop_revision)
518
# Check to see if one is an ancestor of the other
521
graph = self.repository.get_graph()
522
if self._check_if_descendant_or_diverged(
523
stop_revision, last_rev, graph, other):
524
# stop_revision is a descendant of last_rev, but we aren't
525
# overwriting, so we're done.
527
if stop_revno is None:
529
graph = self.repository.get_graph()
530
this_revno, this_last_revision = self.last_revision_info()
531
stop_revno = graph.find_distance_to_null(stop_revision,
532
[(other_last_revision, other_revno),
533
(this_last_revision, this_revno)])
534
self.set_last_revision_info(stop_revno, stop_revision)
538
def revision_id_to_revno(self, revision_id):
539
"""Given a revision id, return its revno"""
540
if _mod_revision.is_null(revision_id):
542
history = self.revision_history()
544
return history.index(revision_id) + 1
546
raise errors.NoSuchRevision(self, revision_id)
548
def get_rev_id(self, revno, history=None):
549
"""Find the revision id of the specified revno."""
551
return _mod_revision.NULL_REVISION
553
history = self.revision_history()
554
if revno <= 0 or revno > len(history):
555
raise errors.NoSuchRevision(self, revno)
556
return history[revno - 1]
558
def pull(self, source, overwrite=False, stop_revision=None,
559
possible_transports=None, _override_hook_target=None):
560
"""Mirror source into this branch.
562
This branch is considered to be 'local', having low latency.
564
:returns: PullResult instance
566
raise NotImplementedError(self.pull)
568
def push(self, target, overwrite=False, stop_revision=None):
569
"""Mirror this branch into target.
571
This branch is considered to be 'local', having low latency.
573
raise NotImplementedError(self.push)
575
def basis_tree(self):
576
"""Return `Tree` object for last revision."""
577
return self.repository.revision_tree(self.last_revision())
579
def get_parent(self):
580
"""Return the parent location of the branch.
582
This is the default location for push/pull/missing. The usual
583
pattern is that the user can override it by specifying a
586
raise NotImplementedError(self.get_parent)
588
def _set_config_location(self, name, url, config=None,
589
make_relative=False):
591
config = self.get_config()
595
url = urlutils.relative_url(self.base, url)
596
config.set_user_option(name, url, warn_masked=True)
598
def _get_config_location(self, name, config=None):
600
config = self.get_config()
601
location = config.get_user_option(name)
606
def get_submit_branch(self):
607
"""Return the submit location of the branch.
609
This is the default location for bundle. The usual
610
pattern is that the user can override it by specifying a
613
return self.get_config().get_user_option('submit_branch')
615
def set_submit_branch(self, location):
616
"""Return the submit location of the branch.
618
This is the default location for bundle. The usual
619
pattern is that the user can override it by specifying a
622
self.get_config().set_user_option('submit_branch', location,
625
def get_public_branch(self):
626
"""Return the public location of the branch.
628
This is is used by merge directives.
630
return self._get_config_location('public_branch')
632
def set_public_branch(self, location):
633
"""Return the submit location of the branch.
635
This is the default location for bundle. The usual
636
pattern is that the user can override it by specifying a
639
self._set_config_location('public_branch', location)
641
def get_push_location(self):
642
"""Return the None or the location to push this branch to."""
643
push_loc = self.get_config().get_user_option('push_location')
646
def set_push_location(self, location):
647
"""Set a new push location for this branch."""
648
raise NotImplementedError(self.set_push_location)
650
def set_parent(self, url):
651
raise NotImplementedError(self.set_parent)
655
"""Synchronise this branch with the master branch if any.
657
:return: None or the last_revision pivoted out during the update.
661
def check_revno(self, revno):
663
Check whether a revno corresponds to any revision.
664
Zero (the NULL revision) is considered valid.
667
self.check_real_revno(revno)
669
def check_real_revno(self, revno):
671
Check whether a revno corresponds to a real revision.
672
Zero (the NULL revision) is considered invalid
674
if revno < 1 or revno > self.revno():
675
raise errors.InvalidRevisionNumber(revno)
678
def clone(self, to_bzrdir, revision_id=None):
679
"""Clone this branch into to_bzrdir preserving all semantic values.
681
revision_id: if not None, the revision history in the new branch will
682
be truncated to end with revision_id.
684
result = to_bzrdir.create_branch()
685
self.copy_content_into(result, revision_id=revision_id)
689
def sprout(self, to_bzrdir, revision_id=None):
690
"""Create a new line of development from the branch, into to_bzrdir.
692
to_bzrdir controls the branch format.
694
revision_id: if not None, the revision history in the new branch will
695
be truncated to end with revision_id.
697
result = to_bzrdir.create_branch()
698
self.copy_content_into(result, revision_id=revision_id)
699
result.set_parent(self.bzrdir.root_transport.base)
702
def _synchronize_history(self, destination, revision_id):
703
"""Synchronize last revision and revision history between branches.
705
This version is most efficient when the destination is also a
706
BzrBranch5, but works for BzrBranch6 as long as the revision
707
history is the true lefthand parent history, and all of the revisions
708
are in the destination's repository. If not, set_revision_history
711
:param destination: The branch to copy the history into
712
:param revision_id: The revision-id to truncate history at. May
713
be None to copy complete history.
715
if revision_id == _mod_revision.NULL_REVISION:
718
new_history = self.revision_history()
719
if revision_id is not None and new_history != []:
721
new_history = new_history[:new_history.index(revision_id) + 1]
723
rev = self.repository.get_revision(revision_id)
724
new_history = rev.get_history(self.repository)[1:]
725
destination.set_revision_history(new_history)
728
def copy_content_into(self, destination, revision_id=None):
729
"""Copy the content of self into destination.
731
revision_id: if not None, the revision history in the new branch will
732
be truncated to end with revision_id.
734
self._synchronize_history(destination, revision_id)
736
parent = self.get_parent()
737
except errors.InaccessibleParent, e:
738
mutter('parent was not accessible to copy: %s', e)
741
destination.set_parent(parent)
742
self.tags.merge_to(destination.tags)
746
"""Check consistency of the branch.
748
In particular this checks that revisions given in the revision-history
749
do actually match up in the revision graph, and that they're all
750
present in the repository.
752
Callers will typically also want to check the repository.
754
:return: A BranchCheckResult.
756
mainline_parent_id = None
757
last_revno, last_revision_id = self.last_revision_info()
758
real_rev_history = list(self.repository.iter_reverse_revision_history(
760
real_rev_history.reverse()
761
if len(real_rev_history) != last_revno:
762
raise errors.BzrCheckError('revno does not match len(mainline)'
763
' %s != %s' % (last_revno, len(real_rev_history)))
764
# TODO: We should probably also check that real_rev_history actually
765
# matches self.revision_history()
766
for revision_id in real_rev_history:
768
revision = self.repository.get_revision(revision_id)
769
except errors.NoSuchRevision, e:
770
raise errors.BzrCheckError("mainline revision {%s} not in repository"
772
# In general the first entry on the revision history has no parents.
773
# But it's not illegal for it to have parents listed; this can happen
774
# in imports from Arch when the parents weren't reachable.
775
if mainline_parent_id is not None:
776
if mainline_parent_id not in revision.parent_ids:
777
raise errors.BzrCheckError("previous revision {%s} not listed among "
779
% (mainline_parent_id, revision_id))
780
mainline_parent_id = revision_id
781
return BranchCheckResult(self)
783
def _get_checkout_format(self):
784
"""Return the most suitable metadir for a checkout of this branch.
785
Weaves are used if this branch's repository uses weaves.
787
if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
788
from bzrlib.repofmt import weaverepo
789
format = bzrdir.BzrDirMetaFormat1()
790
format.repository_format = weaverepo.RepositoryFormat7()
792
format = self.repository.bzrdir.checkout_metadir()
793
format.set_branch_format(self._format)
796
def create_checkout(self, to_location, revision_id=None,
797
lightweight=False, accelerator_tree=None,
799
"""Create a checkout of a branch.
801
:param to_location: The url to produce the checkout at
802
:param revision_id: The revision to check out
803
:param lightweight: If True, produce a lightweight checkout, otherwise,
804
produce a bound branch (heavyweight checkout)
805
:param accelerator_tree: A tree which can be used for retrieving file
806
contents more quickly than the revision tree, i.e. a workingtree.
807
The revision tree will be used for cases where accelerator_tree's
808
content is different.
809
:param hardlink: If true, hard-link files from accelerator_tree,
811
:return: The tree of the created checkout
813
t = transport.get_transport(to_location)
816
format = self._get_checkout_format()
817
checkout = format.initialize_on_transport(t)
818
from_branch = BranchReferenceFormat().initialize(checkout, self)
820
format = self._get_checkout_format()
821
checkout_branch = bzrdir.BzrDir.create_branch_convenience(
822
to_location, force_new_tree=False, format=format)
823
checkout = checkout_branch.bzrdir
824
checkout_branch.bind(self)
825
# pull up to the specified revision_id to set the initial
826
# branch tip correctly, and seed it with history.
827
checkout_branch.pull(self, stop_revision=revision_id)
829
tree = checkout.create_workingtree(revision_id,
830
from_branch=from_branch,
831
accelerator_tree=accelerator_tree,
833
basis_tree = tree.basis_tree()
834
basis_tree.lock_read()
836
for path, file_id in basis_tree.iter_references():
837
reference_parent = self.reference_parent(file_id, path)
838
reference_parent.create_checkout(tree.abspath(path),
839
basis_tree.get_reference_revision(file_id, path),
846
def reconcile(self, thorough=True):
847
"""Make sure the data stored in this branch is consistent."""
848
from bzrlib.reconcile import BranchReconciler
849
reconciler = BranchReconciler(self, thorough=thorough)
850
reconciler.reconcile()
853
def reference_parent(self, file_id, path):
854
"""Return the parent branch for a tree-reference file_id
855
:param file_id: The file_id of the tree reference
856
:param path: The path of the file_id in the tree
857
:return: A branch associated with the file_id
859
# FIXME should provide multiple branches, based on config
860
return Branch.open(self.bzrdir.root_transport.clone(path).base)
862
def supports_tags(self):
863
return self._format.supports_tags()
865
def _check_if_descendant_or_diverged(self, revision_a, revision_b, graph,
867
"""Ensure that revision_b is a descendant of revision_a.
869
This is a helper function for update_revisions.
871
:raises: DivergedBranches if revision_b has diverged from revision_a.
872
:returns: True if revision_b is a descendant of revision_a.
874
relation = self._revision_relations(revision_a, revision_b, graph)
875
if relation == 'b_descends_from_a':
877
elif relation == 'diverged':
878
raise errors.DivergedBranches(self, other_branch)
879
elif relation == 'a_descends_from_b':
882
raise AssertionError("invalid relation: %r" % (relation,))
884
def _revision_relations(self, revision_a, revision_b, graph):
885
"""Determine the relationship between two revisions.
887
:returns: One of: 'a_descends_from_b', 'b_descends_from_a', 'diverged'
889
heads = graph.heads([revision_a, revision_b])
890
if heads == set([revision_b]):
891
return 'b_descends_from_a'
892
elif heads == set([revision_a, revision_b]):
893
# These branches have diverged
895
elif heads == set([revision_a]):
896
return 'a_descends_from_b'
898
raise AssertionError("invalid heads: %r" % (heads,))
901
class BranchFormat(object):
902
"""An encapsulation of the initialization and open routines for a format.
904
Formats provide three things:
905
* An initialization routine,
909
Formats are placed in an dict by their format string for reference
910
during branch opening. Its not required that these be instances, they
911
can be classes themselves with class methods - it simply depends on
912
whether state is needed for a given format or not.
914
Once a format is deprecated, just deprecate the initialize and open
915
methods on the format class. Do not deprecate the object, as the
916
object will be created every time regardless.
919
_default_format = None
920
"""The default format used for new branches."""
923
"""The known formats."""
925
def __eq__(self, other):
926
return self.__class__ is other.__class__
928
def __ne__(self, other):
929
return not (self == other)
932
def find_format(klass, a_bzrdir):
933
"""Return the format for the branch object in a_bzrdir."""
935
transport = a_bzrdir.get_branch_transport(None)
936
format_string = transport.get("format").read()
937
return klass._formats[format_string]
938
except errors.NoSuchFile:
939
raise errors.NotBranchError(path=transport.base)
941
raise errors.UnknownFormatError(format=format_string, kind='branch')
944
def get_default_format(klass):
945
"""Return the current default format."""
946
return klass._default_format
948
def get_reference(self, a_bzrdir):
949
"""Get the target reference of the branch in a_bzrdir.
951
format probing must have been completed before calling
952
this method - it is assumed that the format of the branch
953
in a_bzrdir is correct.
955
:param a_bzrdir: The bzrdir to get the branch data from.
956
:return: None if the branch is not a reference branch.
961
def set_reference(self, a_bzrdir, to_branch):
962
"""Set the target reference of the branch in a_bzrdir.
964
format probing must have been completed before calling
965
this method - it is assumed that the format of the branch
966
in a_bzrdir is correct.
968
:param a_bzrdir: The bzrdir to set the branch reference for.
969
:param to_branch: branch that the checkout is to reference
971
raise NotImplementedError(self.set_reference)
973
def get_format_string(self):
974
"""Return the ASCII format string that identifies this format."""
975
raise NotImplementedError(self.get_format_string)
977
def get_format_description(self):
978
"""Return the short format description for this format."""
979
raise NotImplementedError(self.get_format_description)
981
def _initialize_helper(self, a_bzrdir, utf8_files, lock_type='metadir',
983
"""Initialize a branch in a bzrdir, with specified files
985
:param a_bzrdir: The bzrdir to initialize the branch in
986
:param utf8_files: The files to create as a list of
987
(filename, content) tuples
988
:param set_format: If True, set the format with
989
self.get_format_string. (BzrBranch4 has its format set
991
:return: a branch in this format
993
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
994
branch_transport = a_bzrdir.get_branch_transport(self)
996
'metadir': ('lock', lockdir.LockDir),
997
'branch4': ('branch-lock', lockable_files.TransportLock),
999
lock_name, lock_class = lock_map[lock_type]
1000
control_files = lockable_files.LockableFiles(branch_transport,
1001
lock_name, lock_class)
1002
control_files.create_lock()
1003
control_files.lock_write()
1005
utf8_files += [('format', self.get_format_string())]
1007
for (filename, content) in utf8_files:
1008
branch_transport.put_bytes(
1010
mode=a_bzrdir._get_file_mode())
1012
control_files.unlock()
1013
return self.open(a_bzrdir, _found=True)
1015
def initialize(self, a_bzrdir):
1016
"""Create a branch of this format in a_bzrdir."""
1017
raise NotImplementedError(self.initialize)
1019
def is_supported(self):
1020
"""Is this format supported?
1022
Supported formats can be initialized and opened.
1023
Unsupported formats may not support initialization or committing or
1024
some other features depending on the reason for not being supported.
1028
def open(self, a_bzrdir, _found=False):
1029
"""Return the branch object for a_bzrdir
1031
_found is a private parameter, do not use it. It is used to indicate
1032
if format probing has already be done.
1034
raise NotImplementedError(self.open)
1037
def register_format(klass, format):
1038
klass._formats[format.get_format_string()] = format
1041
def set_default_format(klass, format):
1042
klass._default_format = format
1044
def supports_stacking(self):
1045
"""True if this format records a stacked-on branch."""
1049
def unregister_format(klass, format):
1050
del klass._formats[format.get_format_string()]
1053
return self.get_format_string().rstrip()
1055
def supports_tags(self):
1056
"""True if this format supports tags stored in the branch"""
1057
return False # by default
1060
class BranchHooks(Hooks):
1061
"""A dictionary mapping hook name to a list of callables for branch hooks.
1063
e.g. ['set_rh'] Is the list of items to be called when the
1064
set_revision_history function is invoked.
1068
"""Create the default hooks.
1070
These are all empty initially, because by default nothing should get
1073
Hooks.__init__(self)
1074
# Introduced in 0.15:
1075
# invoked whenever the revision history has been set
1076
# with set_revision_history. The api signature is
1077
# (branch, revision_history), and the branch will
1080
# Invoked after a branch is opened. The api signature is (branch).
1082
# invoked after a push operation completes.
1083
# the api signature is
1085
# containing the members
1086
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
1087
# where local is the local target branch or None, master is the target
1088
# master branch, and the rest should be self explanatory. The source
1089
# is read locked and the target branches write locked. Source will
1090
# be the local low-latency branch.
1091
self['post_push'] = []
1092
# invoked after a pull operation completes.
1093
# the api signature is
1095
# containing the members
1096
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
1097
# where local is the local branch or None, master is the target
1098
# master branch, and the rest should be self explanatory. The source
1099
# is read locked and the target branches write locked. The local
1100
# branch is the low-latency branch.
1101
self['post_pull'] = []
1102
# invoked before a commit operation takes place.
1103
# the api signature is
1104
# (local, master, old_revno, old_revid, future_revno, future_revid,
1105
# tree_delta, future_tree).
1106
# old_revid is NULL_REVISION for the first commit to a branch
1107
# tree_delta is a TreeDelta object describing changes from the basis
1108
# revision, hooks MUST NOT modify this delta
1109
# future_tree is an in-memory tree obtained from
1110
# CommitBuilder.revision_tree() and hooks MUST NOT modify this tree
1111
self['pre_commit'] = []
1112
# invoked after a commit operation completes.
1113
# the api signature is
1114
# (local, master, old_revno, old_revid, new_revno, new_revid)
1115
# old_revid is NULL_REVISION for the first commit to a branch.
1116
self['post_commit'] = []
1117
# invoked after a uncommit operation completes.
1118
# the api signature is
1119
# (local, master, old_revno, old_revid, new_revno, new_revid) where
1120
# local is the local branch or None, master is the target branch,
1121
# and an empty branch recieves new_revno of 0, new_revid of None.
1122
self['post_uncommit'] = []
1124
# Invoked before the tip of a branch changes.
1125
# the api signature is
1126
# (params) where params is a ChangeBranchTipParams with the members
1127
# (branch, old_revno, new_revno, old_revid, new_revid)
1128
self['pre_change_branch_tip'] = []
1130
# Invoked after the tip of a branch changes.
1131
# the api signature is
1132
# (params) where params is a ChangeBranchTipParams with the members
1133
# (branch, old_revno, new_revno, old_revid, new_revid)
1134
self['post_change_branch_tip'] = []
1137
# install the default hooks into the Branch class.
1138
Branch.hooks = BranchHooks()
1141
class ChangeBranchTipParams(object):
1142
"""Object holding parameters passed to *_change_branch_tip hooks.
1144
There are 5 fields that hooks may wish to access:
1146
:ivar branch: the branch being changed
1147
:ivar old_revno: revision number before the change
1148
:ivar new_revno: revision number after the change
1149
:ivar old_revid: revision id before the change
1150
:ivar new_revid: revision id after the change
1152
The revid fields are strings. The revno fields are integers.
1155
def __init__(self, branch, old_revno, new_revno, old_revid, new_revid):
1156
"""Create a group of ChangeBranchTip parameters.
1158
:param branch: The branch being changed.
1159
:param old_revno: Revision number before the change.
1160
:param new_revno: Revision number after the change.
1161
:param old_revid: Tip revision id before the change.
1162
:param new_revid: Tip revision id after the change.
1164
self.branch = branch
1165
self.old_revno = old_revno
1166
self.new_revno = new_revno
1167
self.old_revid = old_revid
1168
self.new_revid = new_revid
1170
def __eq__(self, other):
1171
return self.__dict__ == other.__dict__
1174
return "<%s of %s from (%s, %s) to (%s, %s)>" % (
1175
self.__class__.__name__, self.branch,
1176
self.old_revno, self.old_revid, self.new_revno, self.new_revid)
1179
class BzrBranchFormat4(BranchFormat):
1180
"""Bzr branch format 4.
1183
- a revision-history file.
1184
- a branch-lock lock file [ to be shared with the bzrdir ]
1187
def get_format_description(self):
1188
"""See BranchFormat.get_format_description()."""
1189
return "Branch format 4"
1191
def initialize(self, a_bzrdir):
1192
"""Create a branch of this format in a_bzrdir."""
1193
utf8_files = [('revision-history', ''),
1194
('branch-name', ''),
1196
return self._initialize_helper(a_bzrdir, utf8_files,
1197
lock_type='branch4', set_format=False)
1200
super(BzrBranchFormat4, self).__init__()
1201
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1203
def open(self, a_bzrdir, _found=False):
1204
"""Return the branch object for a_bzrdir
1206
_found is a private parameter, do not use it. It is used to indicate
1207
if format probing has already be done.
1210
# we are being called directly and must probe.
1211
raise NotImplementedError
1212
return BzrBranch(_format=self,
1213
_control_files=a_bzrdir._control_files,
1215
_repository=a_bzrdir.open_repository())
1218
return "Bazaar-NG branch format 4"
1221
class BranchFormatMetadir(BranchFormat):
1222
"""Common logic for meta-dir based branch formats."""
1224
def _branch_class(self):
1225
"""What class to instantiate on open calls."""
1226
raise NotImplementedError(self._branch_class)
1228
def open(self, a_bzrdir, _found=False):
1229
"""Return the branch object for a_bzrdir.
1231
_found is a private parameter, do not use it. It is used to indicate
1232
if format probing has already be done.
1235
format = BranchFormat.find_format(a_bzrdir)
1236
if format.__class__ != self.__class__:
1237
raise AssertionError("wrong format %r found for %r" %
1240
transport = a_bzrdir.get_branch_transport(None)
1241
control_files = lockable_files.LockableFiles(transport, 'lock',
1243
return self._branch_class()(_format=self,
1244
_control_files=control_files,
1246
_repository=a_bzrdir.find_repository())
1247
except errors.NoSuchFile:
1248
raise errors.NotBranchError(path=transport.base)
1251
super(BranchFormatMetadir, self).__init__()
1252
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1254
def supports_tags(self):
1258
class BzrBranchFormat5(BranchFormatMetadir):
1259
"""Bzr branch format 5.
1262
- a revision-history file.
1264
- a lock dir guarding the branch itself
1265
- all of this stored in a branch/ subdirectory
1266
- works with shared repositories.
1268
This format is new in bzr 0.8.
1271
def _branch_class(self):
1274
def get_format_string(self):
1275
"""See BranchFormat.get_format_string()."""
1276
return "Bazaar-NG branch format 5\n"
1278
def get_format_description(self):
1279
"""See BranchFormat.get_format_description()."""
1280
return "Branch format 5"
1282
def initialize(self, a_bzrdir):
1283
"""Create a branch of this format in a_bzrdir."""
1284
utf8_files = [('revision-history', ''),
1285
('branch-name', ''),
1287
return self._initialize_helper(a_bzrdir, utf8_files)
1289
def supports_tags(self):
1293
class BzrBranchFormat6(BranchFormatMetadir):
1294
"""Branch format with last-revision and tags.
1296
Unlike previous formats, this has no explicit revision history. Instead,
1297
this just stores the last-revision, and the left-hand history leading
1298
up to there is the history.
1300
This format was introduced in bzr 0.15
1301
and became the default in 0.91.
1304
def _branch_class(self):
1307
def get_format_string(self):
1308
"""See BranchFormat.get_format_string()."""
1309
return "Bazaar Branch Format 6 (bzr 0.15)\n"
1311
def get_format_description(self):
1312
"""See BranchFormat.get_format_description()."""
1313
return "Branch format 6"
1315
def initialize(self, a_bzrdir):
1316
"""Create a branch of this format in a_bzrdir."""
1317
utf8_files = [('last-revision', '0 null:\n'),
1318
('branch.conf', ''),
1321
return self._initialize_helper(a_bzrdir, utf8_files)
1324
class BzrBranchFormat7(BranchFormatMetadir):
1325
"""Branch format with last-revision, tags, and a stacked location pointer.
1327
The stacked location pointer is passed down to the repository and requires
1328
a repository format with supports_external_lookups = True.
1330
This format was introduced in bzr 1.6.
1333
def _branch_class(self):
1336
def get_format_string(self):
1337
"""See BranchFormat.get_format_string()."""
1338
return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
1340
def get_format_description(self):
1341
"""See BranchFormat.get_format_description()."""
1342
return "Branch format 7"
1344
def initialize(self, a_bzrdir):
1345
"""Create a branch of this format in a_bzrdir."""
1346
utf8_files = [('last-revision', '0 null:\n'),
1347
('branch.conf', ''),
1350
return self._initialize_helper(a_bzrdir, utf8_files)
1353
super(BzrBranchFormat7, self).__init__()
1354
self._matchingbzrdir.repository_format = \
1355
RepositoryFormatKnitPack5RichRoot()
1357
def supports_stacking(self):
1361
class BranchReferenceFormat(BranchFormat):
1362
"""Bzr branch reference format.
1364
Branch references are used in implementing checkouts, they
1365
act as an alias to the real branch which is at some other url.
1372
def get_format_string(self):
1373
"""See BranchFormat.get_format_string()."""
1374
return "Bazaar-NG Branch Reference Format 1\n"
1376
def get_format_description(self):
1377
"""See BranchFormat.get_format_description()."""
1378
return "Checkout reference format 1"
1380
def get_reference(self, a_bzrdir):
1381
"""See BranchFormat.get_reference()."""
1382
transport = a_bzrdir.get_branch_transport(None)
1383
return transport.get('location').read()
1385
def set_reference(self, a_bzrdir, to_branch):
1386
"""See BranchFormat.set_reference()."""
1387
transport = a_bzrdir.get_branch_transport(None)
1388
location = transport.put_bytes('location', to_branch.base)
1390
def initialize(self, a_bzrdir, target_branch=None):
1391
"""Create a branch of this format in a_bzrdir."""
1392
if target_branch is None:
1393
# this format does not implement branch itself, thus the implicit
1394
# creation contract must see it as uninitializable
1395
raise errors.UninitializableFormat(self)
1396
mutter('creating branch reference in %s', a_bzrdir.transport.base)
1397
branch_transport = a_bzrdir.get_branch_transport(self)
1398
branch_transport.put_bytes('location',
1399
target_branch.bzrdir.root_transport.base)
1400
branch_transport.put_bytes('format', self.get_format_string())
1402
a_bzrdir, _found=True,
1403
possible_transports=[target_branch.bzrdir.root_transport])
1406
super(BranchReferenceFormat, self).__init__()
1407
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1409
def _make_reference_clone_function(format, a_branch):
1410
"""Create a clone() routine for a branch dynamically."""
1411
def clone(to_bzrdir, revision_id=None):
1412
"""See Branch.clone()."""
1413
return format.initialize(to_bzrdir, a_branch)
1414
# cannot obey revision_id limits when cloning a reference ...
1415
# FIXME RBC 20060210 either nuke revision_id for clone, or
1416
# emit some sort of warning/error to the caller ?!
1419
def open(self, a_bzrdir, _found=False, location=None,
1420
possible_transports=None):
1421
"""Return the branch that the branch reference in a_bzrdir points at.
1423
_found is a private parameter, do not use it. It is used to indicate
1424
if format probing has already be done.
1427
format = BranchFormat.find_format(a_bzrdir)
1428
if format.__class__ != self.__class__:
1429
raise AssertionError("wrong format %r found for %r" %
1431
if location is None:
1432
location = self.get_reference(a_bzrdir)
1433
real_bzrdir = bzrdir.BzrDir.open(
1434
location, possible_transports=possible_transports)
1435
result = real_bzrdir.open_branch()
1436
# this changes the behaviour of result.clone to create a new reference
1437
# rather than a copy of the content of the branch.
1438
# I did not use a proxy object because that needs much more extensive
1439
# testing, and we are only changing one behaviour at the moment.
1440
# If we decide to alter more behaviours - i.e. the implicit nickname
1441
# then this should be refactored to introduce a tested proxy branch
1442
# and a subclass of that for use in overriding clone() and ....
1444
result.clone = self._make_reference_clone_function(result)
1448
# formats which have no format string are not discoverable
1449
# and not independently creatable, so are not registered.
1450
__format5 = BzrBranchFormat5()
1451
__format6 = BzrBranchFormat6()
1452
__format7 = BzrBranchFormat7()
1453
BranchFormat.register_format(__format5)
1454
BranchFormat.register_format(BranchReferenceFormat())
1455
BranchFormat.register_format(__format6)
1456
BranchFormat.register_format(__format7)
1457
BranchFormat.set_default_format(__format6)
1458
_legacy_formats = [BzrBranchFormat4(),
1461
class BzrBranch(Branch):
1462
"""A branch stored in the actual filesystem.
1464
Note that it's "local" in the context of the filesystem; it doesn't
1465
really matter if it's on an nfs/smb/afs/coda/... share, as long as
1466
it's writable, and can be accessed via the normal filesystem API.
1468
:ivar _transport: Transport for file operations on this branch's
1469
control files, typically pointing to the .bzr/branch directory.
1470
:ivar repository: Repository for this branch.
1471
:ivar base: The url of the base directory for this branch; the one
1472
containing the .bzr directory.
1475
def __init__(self, _format=None,
1476
_control_files=None, a_bzrdir=None, _repository=None):
1477
"""Create new branch object at a particular location."""
1478
if a_bzrdir is None:
1479
raise ValueError('a_bzrdir must be supplied')
1481
self.bzrdir = a_bzrdir
1482
self._base = self.bzrdir.transport.clone('..').base
1483
# XXX: We should be able to just do
1484
# self.base = self.bzrdir.root_transport.base
1485
# but this does not quite work yet -- mbp 20080522
1486
self._format = _format
1487
if _control_files is None:
1488
raise ValueError('BzrBranch _control_files is None')
1489
self.control_files = _control_files
1490
self._transport = _control_files._transport
1491
self.repository = _repository
1492
Branch.__init__(self)
1495
return '%s(%r)' % (self.__class__.__name__, self.base)
1499
def _get_base(self):
1500
"""Returns the directory containing the control directory."""
1503
base = property(_get_base, doc="The URL for the root of this branch.")
1505
def is_locked(self):
1506
return self.control_files.is_locked()
1508
def lock_write(self, token=None):
1509
repo_token = self.repository.lock_write()
1511
token = self.control_files.lock_write(token=token)
1513
self.repository.unlock()
1517
def lock_read(self):
1518
self.repository.lock_read()
1520
self.control_files.lock_read()
1522
self.repository.unlock()
1526
# TODO: test for failed two phase locks. This is known broken.
1528
self.control_files.unlock()
1530
self.repository.unlock()
1531
if not self.control_files.is_locked():
1532
# we just released the lock
1533
self._clear_cached_state()
1535
def peek_lock_mode(self):
1536
if self.control_files._lock_count == 0:
1539
return self.control_files._lock_mode
1541
def get_physical_lock_status(self):
1542
return self.control_files.get_physical_lock_status()
1545
def print_file(self, file, revision_id):
1546
"""See Branch.print_file."""
1547
return self.repository.print_file(file, revision_id)
1549
def _write_revision_history(self, history):
1550
"""Factored out of set_revision_history.
1552
This performs the actual writing to disk.
1553
It is intended to be called by BzrBranch5.set_revision_history."""
1554
self._transport.put_bytes(
1555
'revision-history', '\n'.join(history),
1556
mode=self.bzrdir._get_file_mode())
1559
def set_revision_history(self, rev_history):
1560
"""See Branch.set_revision_history."""
1561
if 'evil' in debug.debug_flags:
1562
mutter_callsite(3, "set_revision_history scales with history.")
1563
check_not_reserved_id = _mod_revision.check_not_reserved_id
1564
for rev_id in rev_history:
1565
check_not_reserved_id(rev_id)
1566
if Branch.hooks['post_change_branch_tip']:
1567
# Don't calculate the last_revision_info() if there are no hooks
1569
old_revno, old_revid = self.last_revision_info()
1570
if len(rev_history) == 0:
1571
revid = _mod_revision.NULL_REVISION
1573
revid = rev_history[-1]
1574
self._run_pre_change_branch_tip_hooks(len(rev_history), revid)
1575
self._write_revision_history(rev_history)
1576
self._clear_cached_state()
1577
self._cache_revision_history(rev_history)
1578
for hook in Branch.hooks['set_rh']:
1579
hook(self, rev_history)
1580
if Branch.hooks['post_change_branch_tip']:
1581
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1583
def _run_pre_change_branch_tip_hooks(self, new_revno, new_revid):
1584
"""Run the pre_change_branch_tip hooks."""
1585
hooks = Branch.hooks['pre_change_branch_tip']
1588
old_revno, old_revid = self.last_revision_info()
1589
params = ChangeBranchTipParams(
1590
self, old_revno, new_revno, old_revid, new_revid)
1594
except errors.TipChangeRejected:
1597
exc_info = sys.exc_info()
1598
hook_name = Branch.hooks.get_hook_name(hook)
1599
raise errors.HookFailed(
1600
'pre_change_branch_tip', hook_name, exc_info)
1602
def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
1603
"""Run the post_change_branch_tip hooks."""
1604
hooks = Branch.hooks['post_change_branch_tip']
1607
new_revno, new_revid = self.last_revision_info()
1608
params = ChangeBranchTipParams(
1609
self, old_revno, new_revno, old_revid, new_revid)
1614
def set_last_revision_info(self, revno, revision_id):
1615
"""Set the last revision of this branch.
1617
The caller is responsible for checking that the revno is correct
1618
for this revision id.
1620
It may be possible to set the branch last revision to an id not
1621
present in the repository. However, branches can also be
1622
configured to check constraints on history, in which case this may not
1625
revision_id = _mod_revision.ensure_null(revision_id)
1626
# this old format stores the full history, but this api doesn't
1627
# provide it, so we must generate, and might as well check it's
1629
history = self._lefthand_history(revision_id)
1630
if len(history) != revno:
1631
raise AssertionError('%d != %d' % (len(history), revno))
1632
self.set_revision_history(history)
1634
def _gen_revision_history(self):
1635
history = self._transport.get_bytes('revision-history').split('\n')
1636
if history[-1:] == ['']:
1637
# There shouldn't be a trailing newline, but just in case.
1641
def _lefthand_history(self, revision_id, last_rev=None,
1643
if 'evil' in debug.debug_flags:
1644
mutter_callsite(4, "_lefthand_history scales with history.")
1645
# stop_revision must be a descendant of last_revision
1646
graph = self.repository.get_graph()
1647
if last_rev is not None:
1648
if not graph.is_ancestor(last_rev, revision_id):
1649
# our previous tip is not merged into stop_revision
1650
raise errors.DivergedBranches(self, other_branch)
1651
# make a new revision history from the graph
1652
parents_map = graph.get_parent_map([revision_id])
1653
if revision_id not in parents_map:
1654
raise errors.NoSuchRevision(self, revision_id)
1655
current_rev_id = revision_id
1657
check_not_reserved_id = _mod_revision.check_not_reserved_id
1658
# Do not include ghosts or graph origin in revision_history
1659
while (current_rev_id in parents_map and
1660
len(parents_map[current_rev_id]) > 0):
1661
check_not_reserved_id(current_rev_id)
1662
new_history.append(current_rev_id)
1663
current_rev_id = parents_map[current_rev_id][0]
1664
parents_map = graph.get_parent_map([current_rev_id])
1665
new_history.reverse()
1669
def generate_revision_history(self, revision_id, last_rev=None,
1671
"""Create a new revision history that will finish with revision_id.
1673
:param revision_id: the new tip to use.
1674
:param last_rev: The previous last_revision. If not None, then this
1675
must be a ancestory of revision_id, or DivergedBranches is raised.
1676
:param other_branch: The other branch that DivergedBranches should
1677
raise with respect to.
1679
self.set_revision_history(self._lefthand_history(revision_id,
1680
last_rev, other_branch))
1682
def basis_tree(self):
1683
"""See Branch.basis_tree."""
1684
return self.repository.revision_tree(self.last_revision())
1687
def pull(self, source, overwrite=False, stop_revision=None,
1688
_hook_master=None, run_hooks=True, possible_transports=None,
1689
_override_hook_target=None):
1692
:param _hook_master: Private parameter - set the branch to
1693
be supplied as the master to pull hooks.
1694
:param run_hooks: Private parameter - if false, this branch
1695
is being called because it's the master of the primary branch,
1696
so it should not run its hooks.
1697
:param _override_hook_target: Private parameter - set the branch to be
1698
supplied as the target_branch to pull hooks.
1700
result = PullResult()
1701
result.source_branch = source
1702
if _override_hook_target is None:
1703
result.target_branch = self
1705
result.target_branch = _override_hook_target
1708
# We assume that during 'pull' the local repository is closer than
1710
graph = self.repository.get_graph(source.repository)
1711
result.old_revno, result.old_revid = self.last_revision_info()
1712
self.update_revisions(source, stop_revision, overwrite=overwrite,
1714
result.tag_conflicts = source.tags.merge_to(self.tags, overwrite)
1715
result.new_revno, result.new_revid = self.last_revision_info()
1717
result.master_branch = _hook_master
1718
result.local_branch = result.target_branch
1720
result.master_branch = result.target_branch
1721
result.local_branch = None
1723
for hook in Branch.hooks['post_pull']:
1729
def _get_parent_location(self):
1730
_locs = ['parent', 'pull', 'x-pull']
1733
return self._transport.get_bytes(l).strip('\n')
1734
except errors.NoSuchFile:
1739
def push(self, target, overwrite=False, stop_revision=None,
1740
_override_hook_source_branch=None):
1743
This is the basic concrete implementation of push()
1745
:param _override_hook_source_branch: If specified, run
1746
the hooks passing this Branch as the source, rather than self.
1747
This is for use of RemoteBranch, where push is delegated to the
1748
underlying vfs-based Branch.
1750
# TODO: Public option to disable running hooks - should be trivial but
1754
result = self._push_with_bound_branches(target, overwrite,
1756
_override_hook_source_branch=_override_hook_source_branch)
1761
def _push_with_bound_branches(self, target, overwrite,
1763
_override_hook_source_branch=None):
1764
"""Push from self into target, and into target's master if any.
1766
This is on the base BzrBranch class even though it doesn't support
1767
bound branches because the *target* might be bound.
1770
if _override_hook_source_branch:
1771
result.source_branch = _override_hook_source_branch
1772
for hook in Branch.hooks['post_push']:
1775
bound_location = target.get_bound_location()
1776
if bound_location and target.base != bound_location:
1777
# there is a master branch.
1779
# XXX: Why the second check? Is it even supported for a branch to
1780
# be bound to itself? -- mbp 20070507
1781
master_branch = target.get_master_branch()
1782
master_branch.lock_write()
1784
# push into the master from this branch.
1785
self._basic_push(master_branch, overwrite, stop_revision)
1786
# and push into the target branch from this. Note that we push from
1787
# this branch again, because its considered the highest bandwidth
1789
result = self._basic_push(target, overwrite, stop_revision)
1790
result.master_branch = master_branch
1791
result.local_branch = target
1795
master_branch.unlock()
1798
result = self._basic_push(target, overwrite, stop_revision)
1799
# TODO: Why set master_branch and local_branch if there's no
1800
# binding? Maybe cleaner to just leave them unset? -- mbp
1802
result.master_branch = target
1803
result.local_branch = None
1807
def _basic_push(self, target, overwrite, stop_revision):
1808
"""Basic implementation of push without bound branches or hooks.
1810
Must be called with self read locked and target write locked.
1812
result = PushResult()
1813
result.source_branch = self
1814
result.target_branch = target
1815
result.old_revno, result.old_revid = target.last_revision_info()
1816
if result.old_revid != self.last_revision():
1817
# We assume that during 'push' this repository is closer than
1819
graph = self.repository.get_graph(target.repository)
1820
target.update_revisions(self, stop_revision, overwrite=overwrite,
1822
if self._push_should_merge_tags():
1823
result.tag_conflicts = self.tags.merge_to(target.tags, overwrite)
1824
result.new_revno, result.new_revid = target.last_revision_info()
1827
def _push_should_merge_tags(self):
1828
"""Should _basic_push merge this branch's tags into the target?
1830
The default implementation returns False if this branch has no tags,
1831
and True the rest of the time. Subclasses may override this.
1833
return self.tags.supports_tags() and self.tags.get_tag_dict()
1835
def get_parent(self):
1836
"""See Branch.get_parent."""
1837
parent = self._get_parent_location()
1840
# This is an old-format absolute path to a local branch
1841
# turn it into a url
1842
if parent.startswith('/'):
1843
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1845
return urlutils.join(self.base[:-1], parent)
1846
except errors.InvalidURLJoin, e:
1847
raise errors.InaccessibleParent(parent, self.base)
1849
def get_stacked_on_url(self):
1850
raise errors.UnstackableBranchFormat(self._format, self.base)
1852
def set_push_location(self, location):
1853
"""See Branch.set_push_location."""
1854
self.get_config().set_user_option(
1855
'push_location', location,
1856
store=_mod_config.STORE_LOCATION_NORECURSE)
1859
def set_parent(self, url):
1860
"""See Branch.set_parent."""
1861
# TODO: Maybe delete old location files?
1862
# URLs should never be unicode, even on the local fs,
1863
# FIXUP this and get_parent in a future branch format bump:
1864
# read and rewrite the file. RBC 20060125
1866
if isinstance(url, unicode):
1868
url = url.encode('ascii')
1869
except UnicodeEncodeError:
1870
raise errors.InvalidURL(url,
1871
"Urls must be 7-bit ascii, "
1872
"use bzrlib.urlutils.escape")
1873
url = urlutils.relative_url(self.base, url)
1874
self._set_parent_location(url)
1876
def _set_parent_location(self, url):
1878
self._transport.delete('parent')
1880
self._transport.put_bytes('parent', url + '\n',
1881
mode=self.bzrdir._get_file_mode())
1883
def set_stacked_on_url(self, url):
1884
raise errors.UnstackableBranchFormat(self._format, self.base)
1887
class BzrBranch5(BzrBranch):
1888
"""A format 5 branch. This supports new features over plain branches.
1890
It has support for a master_branch which is the data for bound branches.
1894
def pull(self, source, overwrite=False, stop_revision=None,
1895
run_hooks=True, possible_transports=None,
1896
_override_hook_target=None):
1897
"""Pull from source into self, updating my master if any.
1899
:param run_hooks: Private parameter - if false, this branch
1900
is being called because it's the master of the primary branch,
1901
so it should not run its hooks.
1903
bound_location = self.get_bound_location()
1904
master_branch = None
1905
if bound_location and source.base != bound_location:
1906
# not pulling from master, so we need to update master.
1907
master_branch = self.get_master_branch(possible_transports)
1908
master_branch.lock_write()
1911
# pull from source into master.
1912
master_branch.pull(source, overwrite, stop_revision,
1914
return super(BzrBranch5, self).pull(source, overwrite,
1915
stop_revision, _hook_master=master_branch,
1916
run_hooks=run_hooks,
1917
_override_hook_target=_override_hook_target)
1920
master_branch.unlock()
1922
def get_bound_location(self):
1924
return self._transport.get_bytes('bound')[:-1]
1925
except errors.NoSuchFile:
1929
def get_master_branch(self, possible_transports=None):
1930
"""Return the branch we are bound to.
1932
:return: Either a Branch, or None
1934
This could memoise the branch, but if thats done
1935
it must be revalidated on each new lock.
1936
So for now we just don't memoise it.
1937
# RBC 20060304 review this decision.
1939
bound_loc = self.get_bound_location()
1943
return Branch.open(bound_loc,
1944
possible_transports=possible_transports)
1945
except (errors.NotBranchError, errors.ConnectionError), e:
1946
raise errors.BoundBranchConnectionFailure(
1950
def set_bound_location(self, location):
1951
"""Set the target where this branch is bound to.
1953
:param location: URL to the target branch
1956
self._transport.put_bytes('bound', location+'\n',
1957
mode=self.bzrdir._get_file_mode())
1960
self._transport.delete('bound')
1961
except errors.NoSuchFile:
1966
def bind(self, other):
1967
"""Bind this branch to the branch other.
1969
This does not push or pull data between the branches, though it does
1970
check for divergence to raise an error when the branches are not
1971
either the same, or one a prefix of the other. That behaviour may not
1972
be useful, so that check may be removed in future.
1974
:param other: The branch to bind to
1977
# TODO: jam 20051230 Consider checking if the target is bound
1978
# It is debatable whether you should be able to bind to
1979
# a branch which is itself bound.
1980
# Committing is obviously forbidden,
1981
# but binding itself may not be.
1982
# Since we *have* to check at commit time, we don't
1983
# *need* to check here
1985
# we want to raise diverged if:
1986
# last_rev is not in the other_last_rev history, AND
1987
# other_last_rev is not in our history, and do it without pulling
1989
self.set_bound_location(other.base)
1993
"""If bound, unbind"""
1994
return self.set_bound_location(None)
1997
def update(self, possible_transports=None):
1998
"""Synchronise this branch with the master branch if any.
2000
:return: None or the last_revision that was pivoted out during the
2003
master = self.get_master_branch(possible_transports)
2004
if master is not None:
2005
old_tip = _mod_revision.ensure_null(self.last_revision())
2006
self.pull(master, overwrite=True)
2007
if self.repository.get_graph().is_ancestor(old_tip,
2008
_mod_revision.ensure_null(self.last_revision())):
2014
class BzrBranch7(BzrBranch5):
2015
"""A branch with support for a fallback repository."""
2017
def _get_fallback_repository(self, url):
2018
"""Get the repository we fallback to at url."""
2019
url = urlutils.join(self.base, url)
2020
return bzrdir.BzrDir.open(url).open_branch().repository
2022
def _activate_fallback_location(self, url):
2023
"""Activate the branch/repository from url as a fallback repository."""
2024
self.repository.add_fallback_repository(
2025
self._get_fallback_repository(url))
2027
def _open_hook(self):
2029
url = self.get_stacked_on_url()
2030
except (errors.UnstackableRepositoryFormat, errors.NotStacked,
2031
errors.UnstackableBranchFormat):
2034
self._activate_fallback_location(url)
2036
def _check_stackable_repo(self):
2037
if not self.repository._format.supports_external_lookups:
2038
raise errors.UnstackableRepositoryFormat(self.repository._format,
2039
self.repository.base)
2041
def __init__(self, *args, **kwargs):
2042
super(BzrBranch7, self).__init__(*args, **kwargs)
2043
self._last_revision_info_cache = None
2044
self._partial_revision_history_cache = []
2046
def _clear_cached_state(self):
2047
super(BzrBranch7, self)._clear_cached_state()
2048
self._last_revision_info_cache = None
2049
self._partial_revision_history_cache = []
2051
def _last_revision_info(self):
2052
revision_string = self._transport.get_bytes('last-revision')
2053
revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
2054
revision_id = cache_utf8.get_cached_utf8(revision_id)
2056
return revno, revision_id
2058
def _write_last_revision_info(self, revno, revision_id):
2059
"""Simply write out the revision id, with no checks.
2061
Use set_last_revision_info to perform this safely.
2063
Does not update the revision_history cache.
2064
Intended to be called by set_last_revision_info and
2065
_write_revision_history.
2067
revision_id = _mod_revision.ensure_null(revision_id)
2068
out_string = '%d %s\n' % (revno, revision_id)
2069
self._transport.put_bytes('last-revision', out_string,
2070
mode=self.bzrdir._get_file_mode())
2073
def set_last_revision_info(self, revno, revision_id):
2074
revision_id = _mod_revision.ensure_null(revision_id)
2075
old_revno, old_revid = self.last_revision_info()
2076
if self._get_append_revisions_only():
2077
self._check_history_violation(revision_id)
2078
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2079
self._write_last_revision_info(revno, revision_id)
2080
self._clear_cached_state()
2081
self._last_revision_info_cache = revno, revision_id
2082
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2084
def _check_history_violation(self, revision_id):
2085
last_revision = _mod_revision.ensure_null(self.last_revision())
2086
if _mod_revision.is_null(last_revision):
2088
if last_revision not in self._lefthand_history(revision_id):
2089
raise errors.AppendRevisionsOnlyViolation(self.base)
2091
def _gen_revision_history(self):
2092
"""Generate the revision history from last revision
2094
last_revno, last_revision = self.last_revision_info()
2095
self._extend_partial_history(stop_index=last_revno-1)
2096
return list(reversed(self._partial_revision_history_cache))
2098
def _extend_partial_history(self, stop_index=None, stop_revision=None):
2099
"""Extend the partial history to include a given index
2101
If a stop_index is supplied, stop when that index has been reached.
2102
If a stop_revision is supplied, stop when that revision is
2103
encountered. Otherwise, stop when the beginning of history is
2106
:param stop_index: The index which should be present. When it is
2107
present, history extension will stop.
2108
:param revision_id: The revision id which should be present. When
2109
it is encountered, history extension will stop.
2111
repo = self.repository
2112
if len(self._partial_revision_history_cache) == 0:
2113
iterator = repo.iter_reverse_revision_history(self.last_revision())
2115
start_revision = self._partial_revision_history_cache[-1]
2116
iterator = repo.iter_reverse_revision_history(start_revision)
2117
#skip the last revision in the list
2118
next_revision = iterator.next()
2119
for revision_id in iterator:
2120
self._partial_revision_history_cache.append(revision_id)
2121
if (stop_index is not None and
2122
len(self._partial_revision_history_cache) > stop_index):
2124
if revision_id == stop_revision:
2127
def _write_revision_history(self, history):
2128
"""Factored out of set_revision_history.
2130
This performs the actual writing to disk, with format-specific checks.
2131
It is intended to be called by BzrBranch5.set_revision_history.
2133
if len(history) == 0:
2134
last_revision = 'null:'
2136
if history != self._lefthand_history(history[-1]):
2137
raise errors.NotLefthandHistory(history)
2138
last_revision = history[-1]
2139
if self._get_append_revisions_only():
2140
self._check_history_violation(last_revision)
2141
self._write_last_revision_info(len(history), last_revision)
2144
def _set_parent_location(self, url):
2145
"""Set the parent branch"""
2146
self._set_config_location('parent_location', url, make_relative=True)
2149
def _get_parent_location(self):
2150
"""Set the parent branch"""
2151
return self._get_config_location('parent_location')
2153
def set_push_location(self, location):
2154
"""See Branch.set_push_location."""
2155
self._set_config_location('push_location', location)
2157
def set_bound_location(self, location):
2158
"""See Branch.set_push_location."""
2160
config = self.get_config()
2161
if location is None:
2162
if config.get_user_option('bound') != 'True':
2165
config.set_user_option('bound', 'False', warn_masked=True)
2168
self._set_config_location('bound_location', location,
2170
config.set_user_option('bound', 'True', warn_masked=True)
2173
def _get_bound_location(self, bound):
2174
"""Return the bound location in the config file.
2176
Return None if the bound parameter does not match"""
2177
config = self.get_config()
2178
config_bound = (config.get_user_option('bound') == 'True')
2179
if config_bound != bound:
2181
return self._get_config_location('bound_location', config=config)
2183
def get_bound_location(self):
2184
"""See Branch.set_push_location."""
2185
return self._get_bound_location(True)
2187
def get_old_bound_location(self):
2188
"""See Branch.get_old_bound_location"""
2189
return self._get_bound_location(False)
2191
def get_stacked_on_url(self):
2192
# you can always ask for the URL; but you might not be able to use it
2193
# if the repo can't support stacking.
2194
## self._check_stackable_repo()
2195
stacked_url = self._get_config_location('stacked_on_location')
2196
if stacked_url is None:
2197
raise errors.NotStacked(self)
2200
def set_append_revisions_only(self, enabled):
2205
self.get_config().set_user_option('append_revisions_only', value,
2208
def set_stacked_on_url(self, url):
2209
self._check_stackable_repo()
2212
old_url = self.get_stacked_on_url()
2213
except (errors.NotStacked, errors.UnstackableBranchFormat,
2214
errors.UnstackableRepositoryFormat):
2217
# repositories don't offer an interface to remove fallback
2218
# repositories today; take the conceptually simpler option and just
2220
self.repository = self.bzrdir.find_repository()
2221
# for every revision reference the branch has, ensure it is pulled
2223
source_repository = self._get_fallback_repository(old_url)
2224
for revision_id in chain([self.last_revision()],
2225
self.tags.get_reverse_tag_dict()):
2226
self.repository.fetch(source_repository, revision_id,
2229
self._activate_fallback_location(url)
2230
# write this out after the repository is stacked to avoid setting a
2231
# stacked config that doesn't work.
2232
self._set_config_location('stacked_on_location', url)
2234
def _get_append_revisions_only(self):
2235
value = self.get_config().get_user_option('append_revisions_only')
2236
return value == 'True'
2238
def _synchronize_history(self, destination, revision_id):
2239
"""Synchronize last revision and revision history between branches.
2241
This version is most efficient when the destination is also a
2242
BzrBranch6, but works for BzrBranch5, as long as the destination's
2243
repository contains all the lefthand ancestors of the intended
2244
last_revision. If not, set_last_revision_info will fail.
2246
:param destination: The branch to copy the history into
2247
:param revision_id: The revision-id to truncate history at. May
2248
be None to copy complete history.
2250
source_revno, source_revision_id = self.last_revision_info()
2251
if revision_id is None:
2252
revno, revision_id = source_revno, source_revision_id
2253
elif source_revision_id == revision_id:
2254
# we know the revno without needing to walk all of history
2255
revno = source_revno
2257
# To figure out the revno for a random revision, we need to build
2258
# the revision history, and count its length.
2259
# We don't care about the order, just how long it is.
2260
# Alternatively, we could start at the current location, and count
2261
# backwards. But there is no guarantee that we will find it since
2262
# it may be a merged revision.
2263
revno = len(list(self.repository.iter_reverse_revision_history(
2265
destination.set_last_revision_info(revno, revision_id)
2267
def _make_tags(self):
2268
return BasicTags(self)
2271
def generate_revision_history(self, revision_id, last_rev=None,
2273
"""See BzrBranch5.generate_revision_history"""
2274
history = self._lefthand_history(revision_id, last_rev, other_branch)
2275
revno = len(history)
2276
self.set_last_revision_info(revno, revision_id)
2279
def get_rev_id(self, revno, history=None):
2280
"""Find the revision id of the specified revno."""
2282
return _mod_revision.NULL_REVISION
2284
last_revno, last_revision_id = self.last_revision_info()
2285
if revno <= 0 or revno > last_revno:
2286
raise errors.NoSuchRevision(self, revno)
2288
if history is not None:
2289
return history[revno - 1]
2291
index = last_revno - revno
2292
if len(self._partial_revision_history_cache) <= index:
2293
self._extend_partial_history(stop_index=index)
2294
if len(self._partial_revision_history_cache) > index:
2295
return self._partial_revision_history_cache[index]
2297
raise errors.NoSuchRevision(self, revno)
2300
def revision_id_to_revno(self, revision_id):
2301
"""Given a revision id, return its revno"""
2302
if _mod_revision.is_null(revision_id):
2305
index = self._partial_revision_history_cache.index(revision_id)
2307
self._extend_partial_history(stop_revision=revision_id)
2308
index = len(self._partial_revision_history_cache) - 1
2309
if self._partial_revision_history_cache[index] != revision_id:
2310
raise errors.NoSuchRevision(self, revision_id)
2311
return self.revno() - index
2314
class BzrBranch6(BzrBranch7):
2315
"""See BzrBranchFormat6 for the capabilities of this branch.
2317
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
2321
def get_stacked_on_url(self):
2322
raise errors.UnstackableBranchFormat(self._format, self.base)
2324
def set_stacked_on_url(self, url):
2325
raise errors.UnstackableBranchFormat(self._format, self.base)
2328
######################################################################
2329
# results of operations
2332
class _Result(object):
2334
def _show_tag_conficts(self, to_file):
2335
if not getattr(self, 'tag_conflicts', None):
2337
to_file.write('Conflicting tags:\n')
2338
for name, value1, value2 in self.tag_conflicts:
2339
to_file.write(' %s\n' % (name, ))
2342
class PullResult(_Result):
2343
"""Result of a Branch.pull operation.
2345
:ivar old_revno: Revision number before pull.
2346
:ivar new_revno: Revision number after pull.
2347
:ivar old_revid: Tip revision id before pull.
2348
:ivar new_revid: Tip revision id after pull.
2349
:ivar source_branch: Source (local) branch object.
2350
:ivar master_branch: Master branch of the target, or the target if no
2352
:ivar local_branch: target branch if there is a Master, else None
2353
:ivar target_branch: Target/destination branch object.
2354
:ivar tag_conflicts: A list of tag conflicts, see BasicTags.merge_to
2358
# DEPRECATED: pull used to return the change in revno
2359
return self.new_revno - self.old_revno
2361
def report(self, to_file):
2363
if self.old_revid == self.new_revid:
2364
to_file.write('No revisions to pull.\n')
2366
to_file.write('Now on revision %d.\n' % self.new_revno)
2367
self._show_tag_conficts(to_file)
2370
class PushResult(_Result):
2371
"""Result of a Branch.push operation.
2373
:ivar old_revno: Revision number before push.
2374
:ivar new_revno: Revision number after push.
2375
:ivar old_revid: Tip revision id before push.
2376
:ivar new_revid: Tip revision id after push.
2377
:ivar source_branch: Source branch object.
2378
:ivar master_branch: Master branch of the target, or None.
2379
:ivar target_branch: Target/destination branch object.
2383
# DEPRECATED: push used to return the change in revno
2384
return self.new_revno - self.old_revno
2386
def report(self, to_file):
2387
"""Write a human-readable description of the result."""
2388
if self.old_revid == self.new_revid:
2389
to_file.write('No new revisions to push.\n')
2391
to_file.write('Pushed up to revision %d.\n' % self.new_revno)
2392
self._show_tag_conficts(to_file)
2395
class BranchCheckResult(object):
2396
"""Results of checking branch consistency.
2401
def __init__(self, branch):
2402
self.branch = branch
2404
def report_results(self, verbose):
2405
"""Report the check results via trace.note.
2407
:param verbose: Requests more detailed display of what was checked,
2410
note('checked branch %s format %s',
2412
self.branch._format)
2415
class Converter5to6(object):
2416
"""Perform an in-place upgrade of format 5 to format 6"""
2418
def convert(self, branch):
2419
# Data for 5 and 6 can peacefully coexist.
2420
format = BzrBranchFormat6()
2421
new_branch = format.open(branch.bzrdir, _found=True)
2423
# Copy source data into target
2424
new_branch._write_last_revision_info(*branch.last_revision_info())
2425
new_branch.set_parent(branch.get_parent())
2426
new_branch.set_bound_location(branch.get_bound_location())
2427
new_branch.set_push_location(branch.get_push_location())
2429
# New branch has no tags by default
2430
new_branch.tags._set_tag_dict({})
2432
# Copying done; now update target format
2433
new_branch._transport.put_bytes('format',
2434
format.get_format_string(),
2435
mode=new_branch.bzrdir._get_file_mode())
2437
# Clean up old files
2438
new_branch._transport.delete('revision-history')
2440
branch.set_parent(None)
2441
except errors.NoSuchFile:
2443
branch.set_bound_location(None)
2446
class Converter6to7(object):
2447
"""Perform an in-place upgrade of format 6 to format 7"""
2449
def convert(self, branch):
2450
format = BzrBranchFormat7()
2451
branch._set_config_location('stacked_on_location', '')
2452
# update target format
2453
branch._transport.put_bytes('format', format.get_format_string())