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._partial_revision_id_to_revno_cache = {}
93
self._last_revision_info_cache = None
95
hooks = Branch.hooks['open']
100
"""Called by init to allow simpler extension of the base class."""
102
def break_lock(self):
103
"""Break a lock if one is present from another instance.
105
Uses the ui factory to ask for confirmation if the lock may be from
108
This will probe the repository for its lock as well.
110
self.control_files.break_lock()
111
self.repository.break_lock()
112
master = self.get_master_branch()
113
if master is not None:
117
def open(base, _unsupported=False, possible_transports=None):
118
"""Open the branch rooted at base.
120
For instance, if the branch is at URL/.bzr/branch,
121
Branch.open(URL) -> a Branch instance.
123
control = bzrdir.BzrDir.open(base, _unsupported,
124
possible_transports=possible_transports)
125
return control.open_branch(_unsupported)
128
def open_from_transport(transport, _unsupported=False):
129
"""Open the branch rooted at transport"""
130
control = bzrdir.BzrDir.open_from_transport(transport, _unsupported)
131
return control.open_branch(_unsupported)
134
def open_containing(url, possible_transports=None):
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,
147
return control.open_branch(), relpath
149
def get_config(self):
150
return BranchConfig(self)
152
def _get_nick(self, local=False, possible_transports=None):
153
config = self.get_config()
154
# explicit overrides master, but don't look for master if local is True
155
if not local and not config.has_explicit_nickname():
157
master = self.get_master_branch(possible_transports)
158
if master is not None:
159
# return the master branch value
161
except errors.BzrError, e:
162
# Silently fall back to local implicit nick if the master is
164
mutter("Could not connect to bound branch, "
165
"falling back to local nick.\n " + str(e))
166
return config.get_nickname()
168
def _set_nick(self, nick):
169
self.get_config().set_user_option('nickname', nick, warn_masked=True)
171
nick = property(_get_nick, _set_nick)
174
raise NotImplementedError(self.is_locked)
176
def lock_write(self):
177
raise NotImplementedError(self.lock_write)
180
raise NotImplementedError(self.lock_read)
183
raise NotImplementedError(self.unlock)
185
def peek_lock_mode(self):
186
"""Return lock mode for the Branch: 'r', 'w' or None"""
187
raise NotImplementedError(self.peek_lock_mode)
189
def get_physical_lock_status(self):
190
raise NotImplementedError(self.get_physical_lock_status)
193
def dotted_revno_to_revision_id(self, revno, _cache_reverse=False):
194
"""Return the revision_id for a dotted revno.
196
:param revno: a tuple like (1,) or (1,1,2)
197
:param _cache_reverse: a private parameter enabling storage
198
of the reverse mapping in a top level cache. (This should
199
only be done in selective circumstances as we want to
200
avoid having the mapping cached multiple times.)
201
:return: the revision_id
202
:raises errors.NoSuchRevision: if the revno doesn't exist
204
rev_id = self._do_dotted_revno_to_revision_id(revno)
206
self._partial_revision_id_to_revno_cache[rev_id] = revno
209
def _do_dotted_revno_to_revision_id(self, revno):
210
"""Worker function for dotted_revno_to_revision_id.
212
Subclasses should override this if they wish to
213
provide a more efficient implementation.
216
return self.get_rev_id(revno[0])
217
revision_id_to_revno = self.get_revision_id_to_revno_map()
218
revision_ids = [revision_id for revision_id, this_revno
219
in revision_id_to_revno.iteritems()
220
if revno == this_revno]
221
if len(revision_ids) == 1:
222
return revision_ids[0]
224
revno_str = '.'.join(map(str, revno))
225
raise errors.NoSuchRevision(self, revno_str)
228
def revision_id_to_dotted_revno(self, revision_id):
229
"""Given a revision id, return its dotted revno.
231
:return: a tuple like (1,) or (400,1,3).
233
return self._do_revision_id_to_dotted_revno(revision_id)
235
def _do_revision_id_to_dotted_revno(self, revision_id):
236
"""Worker function for revision_id_to_revno."""
237
# Try the caches if they are loaded
238
result = self._partial_revision_id_to_revno_cache.get(revision_id)
239
if result is not None:
241
if self._revision_id_to_revno_cache:
242
result = self._revision_id_to_revno_cache.get(revision_id)
244
raise errors.NoSuchRevision(self, revision_id)
245
# Try the mainline as it's optimised
247
revno = self.revision_id_to_revno(revision_id)
249
except errors.NoSuchRevision:
250
# We need to load and use the full revno map after all
251
result = self.get_revision_id_to_revno_map().get(revision_id)
253
raise errors.NoSuchRevision(self, revision_id)
256
def get_revision_id_to_revno_map(self):
257
"""Return the revision_id => dotted revno map.
259
This will be regenerated on demand, but will be cached.
261
:return: A dictionary mapping revision_id => dotted revno.
262
This dictionary should not be modified by the caller.
264
if self._revision_id_to_revno_cache is not None:
265
mapping = self._revision_id_to_revno_cache
267
mapping = self._gen_revno_map()
268
self._cache_revision_id_to_revno(mapping)
269
# TODO: jam 20070417 Since this is being cached, should we be returning
271
# I would rather not, and instead just declare that users should not
272
# modify the return value.
275
def _gen_revno_map(self):
276
"""Create a new mapping from revision ids to dotted revnos.
278
Dotted revnos are generated based on the current tip in the revision
280
This is the worker function for get_revision_id_to_revno_map, which
281
just caches the return value.
283
:return: A dictionary mapping revision_id => dotted revno.
285
last_revision = self.last_revision()
286
revision_graph = repository._old_get_graph(self.repository,
288
merge_sorted_revisions = tsort.merge_sort(
293
revision_id_to_revno = dict((rev_id, revno)
294
for seq_num, rev_id, depth, revno, end_of_merge
295
in merge_sorted_revisions)
296
return revision_id_to_revno
298
def leave_lock_in_place(self):
299
"""Tell this branch object not to release the physical lock when this
302
If lock_write doesn't return a token, then this method is not supported.
304
self.control_files.leave_in_place()
306
def dont_leave_lock_in_place(self):
307
"""Tell this branch object to release the physical lock when this
308
object is unlocked, even if it didn't originally acquire it.
310
If lock_write doesn't return a token, then this method is not supported.
312
self.control_files.dont_leave_in_place()
314
def bind(self, other):
315
"""Bind the local branch the other branch.
317
:param other: The branch to bind to
320
raise errors.UpgradeRequired(self.base)
323
def fetch(self, from_branch, last_revision=None, pb=None):
324
"""Copy revisions from from_branch into this branch.
326
:param from_branch: Where to copy from.
327
:param last_revision: What revision to stop at (None for at the end
329
:param pb: An optional progress bar to use.
331
Returns the copied revision count and the failed revisions in a tuple:
334
if self.base == from_branch.base:
337
nested_pb = ui.ui_factory.nested_progress_bar()
342
from_branch.lock_read()
344
if last_revision is None:
345
pb.update('get source history')
346
last_revision = from_branch.last_revision()
347
last_revision = _mod_revision.ensure_null(last_revision)
348
return self.repository.fetch(from_branch.repository,
349
revision_id=last_revision,
352
if nested_pb is not None:
356
def get_bound_location(self):
357
"""Return the URL of the branch we are bound to.
359
Older format branches cannot bind, please be sure to use a metadir
364
def get_old_bound_location(self):
365
"""Return the URL of the branch we used to be bound to
367
raise errors.UpgradeRequired(self.base)
369
def get_commit_builder(self, parents, config=None, timestamp=None,
370
timezone=None, committer=None, revprops=None,
372
"""Obtain a CommitBuilder for this branch.
374
:param parents: Revision ids of the parents of the new revision.
375
:param config: Optional configuration to use.
376
:param timestamp: Optional timestamp recorded for commit.
377
:param timezone: Optional timezone for timestamp.
378
:param committer: Optional committer to set for commit.
379
:param revprops: Optional dictionary of revision properties.
380
:param revision_id: Optional revision id.
384
config = self.get_config()
386
return self.repository.get_commit_builder(self, parents, config,
387
timestamp, timezone, committer, revprops, revision_id)
389
def get_master_branch(self, possible_transports=None):
390
"""Return the branch we are bound to.
392
:return: Either a Branch, or None
396
def get_revision_delta(self, revno):
397
"""Return the delta for one revision.
399
The delta is relative to its mainline predecessor, or the
400
empty tree for revision 1.
402
rh = self.revision_history()
403
if not (1 <= revno <= len(rh)):
404
raise errors.InvalidRevisionNumber(revno)
405
return self.repository.get_revision_delta(rh[revno-1])
407
def get_stacked_on_url(self):
408
"""Get the URL this branch is stacked against.
410
:raises NotStacked: If the branch is not stacked.
411
:raises UnstackableBranchFormat: If the branch does not support
414
raise NotImplementedError(self.get_stacked_on_url)
416
def print_file(self, file, revision_id):
417
"""Print `file` to stdout."""
418
raise NotImplementedError(self.print_file)
420
def set_revision_history(self, rev_history):
421
raise NotImplementedError(self.set_revision_history)
423
def set_stacked_on_url(self, url):
424
"""Set the URL this branch is stacked against.
426
:raises UnstackableBranchFormat: If the branch does not support
428
:raises UnstackableRepositoryFormat: If the repository does not support
431
raise NotImplementedError(self.set_stacked_on_url)
433
def _cache_revision_history(self, rev_history):
434
"""Set the cached revision history to rev_history.
436
The revision_history method will use this cache to avoid regenerating
437
the revision history.
439
This API is semi-public; it only for use by subclasses, all other code
440
should consider it to be private.
442
self._revision_history_cache = rev_history
444
def _cache_revision_id_to_revno(self, revision_id_to_revno):
445
"""Set the cached revision_id => revno map to revision_id_to_revno.
447
This API is semi-public; it only for use by subclasses, all other code
448
should consider it to be private.
450
self._revision_id_to_revno_cache = revision_id_to_revno
452
def _clear_cached_state(self):
453
"""Clear any cached data on this branch, e.g. cached revision history.
455
This means the next call to revision_history will need to call
456
_gen_revision_history.
458
This API is semi-public; it only for use by subclasses, all other code
459
should consider it to be private.
461
self._revision_history_cache = None
462
self._revision_id_to_revno_cache = None
463
self._last_revision_info_cache = None
465
def _gen_revision_history(self):
466
"""Return sequence of revision hashes on to this branch.
468
Unlike revision_history, this method always regenerates or rereads the
469
revision history, i.e. it does not cache the result, so repeated calls
472
Concrete subclasses should override this instead of revision_history so
473
that subclasses do not need to deal with caching logic.
475
This API is semi-public; it only for use by subclasses, all other code
476
should consider it to be private.
478
raise NotImplementedError(self._gen_revision_history)
481
def revision_history(self):
482
"""Return sequence of revision ids on this branch.
484
This method will cache the revision history for as long as it is safe to
487
if 'evil' in debug.debug_flags:
488
mutter_callsite(3, "revision_history scales with history.")
489
if self._revision_history_cache is not None:
490
history = self._revision_history_cache
492
history = self._gen_revision_history()
493
self._cache_revision_history(history)
497
"""Return current revision number for this branch.
499
That is equivalent to the number of revisions committed to
502
return self.last_revision_info()[0]
505
"""Older format branches cannot bind or unbind."""
506
raise errors.UpgradeRequired(self.base)
508
def set_append_revisions_only(self, enabled):
509
"""Older format branches are never restricted to append-only"""
510
raise errors.UpgradeRequired(self.base)
512
def last_revision(self):
513
"""Return last revision id, or NULL_REVISION."""
514
return self.last_revision_info()[1]
517
def last_revision_info(self):
518
"""Return information about the last revision.
520
:return: A tuple (revno, revision_id).
522
if self._last_revision_info_cache is None:
523
self._last_revision_info_cache = self._last_revision_info()
524
return self._last_revision_info_cache
526
def _last_revision_info(self):
527
rh = self.revision_history()
530
return (revno, rh[-1])
532
return (0, _mod_revision.NULL_REVISION)
534
@deprecated_method(deprecated_in((1, 6, 0)))
535
def missing_revisions(self, other, stop_revision=None):
536
"""Return a list of new revisions that would perfectly fit.
538
If self and other have not diverged, return a list of the revisions
539
present in other, but missing from self.
541
self_history = self.revision_history()
542
self_len = len(self_history)
543
other_history = other.revision_history()
544
other_len = len(other_history)
545
common_index = min(self_len, other_len) -1
546
if common_index >= 0 and \
547
self_history[common_index] != other_history[common_index]:
548
raise errors.DivergedBranches(self, other)
550
if stop_revision is None:
551
stop_revision = other_len
553
if stop_revision > other_len:
554
raise errors.NoSuchRevision(self, stop_revision)
555
return other_history[self_len:stop_revision]
558
def update_revisions(self, other, stop_revision=None, overwrite=False,
560
"""Pull in new perfect-fit revisions.
562
:param other: Another Branch to pull from
563
:param stop_revision: Updated until the given revision
564
:param overwrite: Always set the branch pointer, rather than checking
565
to see if it is a proper descendant.
566
:param graph: A Graph object that can be used to query history
567
information. This can be None.
572
other_revno, other_last_revision = other.last_revision_info()
573
stop_revno = None # unknown
574
if stop_revision is None:
575
stop_revision = other_last_revision
576
if _mod_revision.is_null(stop_revision):
577
# if there are no commits, we're done.
579
stop_revno = other_revno
581
# what's the current last revision, before we fetch [and change it
583
last_rev = _mod_revision.ensure_null(self.last_revision())
584
# we fetch here so that we don't process data twice in the common
585
# case of having something to pull, and so that the check for
586
# already merged can operate on the just fetched graph, which will
587
# be cached in memory.
588
self.fetch(other, stop_revision)
589
# Check to see if one is an ancestor of the other
592
graph = self.repository.get_graph()
593
if self._check_if_descendant_or_diverged(
594
stop_revision, last_rev, graph, other):
595
# stop_revision is a descendant of last_rev, but we aren't
596
# overwriting, so we're done.
598
if stop_revno is None:
600
graph = self.repository.get_graph()
601
this_revno, this_last_revision = self.last_revision_info()
602
stop_revno = graph.find_distance_to_null(stop_revision,
603
[(other_last_revision, other_revno),
604
(this_last_revision, this_revno)])
605
self.set_last_revision_info(stop_revno, stop_revision)
609
def revision_id_to_revno(self, revision_id):
610
"""Given a revision id, return its revno"""
611
if _mod_revision.is_null(revision_id):
613
history = self.revision_history()
615
return history.index(revision_id) + 1
617
raise errors.NoSuchRevision(self, revision_id)
619
def get_rev_id(self, revno, history=None):
620
"""Find the revision id of the specified revno."""
622
return _mod_revision.NULL_REVISION
624
history = self.revision_history()
625
if revno <= 0 or revno > len(history):
626
raise errors.NoSuchRevision(self, revno)
627
return history[revno - 1]
629
def pull(self, source, overwrite=False, stop_revision=None,
630
possible_transports=None, _override_hook_target=None):
631
"""Mirror source into this branch.
633
This branch is considered to be 'local', having low latency.
635
:returns: PullResult instance
637
raise NotImplementedError(self.pull)
639
def push(self, target, overwrite=False, stop_revision=None):
640
"""Mirror this branch into target.
642
This branch is considered to be 'local', having low latency.
644
raise NotImplementedError(self.push)
646
def basis_tree(self):
647
"""Return `Tree` object for last revision."""
648
return self.repository.revision_tree(self.last_revision())
650
def get_parent(self):
651
"""Return the parent location of the branch.
653
This is the default location for push/pull/missing. The usual
654
pattern is that the user can override it by specifying a
657
raise NotImplementedError(self.get_parent)
659
def _set_config_location(self, name, url, config=None,
660
make_relative=False):
662
config = self.get_config()
666
url = urlutils.relative_url(self.base, url)
667
config.set_user_option(name, url, warn_masked=True)
669
def _get_config_location(self, name, config=None):
671
config = self.get_config()
672
location = config.get_user_option(name)
677
def get_submit_branch(self):
678
"""Return the submit location of the branch.
680
This is the default location for bundle. The usual
681
pattern is that the user can override it by specifying a
684
return self.get_config().get_user_option('submit_branch')
686
def set_submit_branch(self, location):
687
"""Return the submit location of the branch.
689
This is the default location for bundle. The usual
690
pattern is that the user can override it by specifying a
693
self.get_config().set_user_option('submit_branch', location,
696
def get_public_branch(self):
697
"""Return the public location of the branch.
699
This is is used by merge directives.
701
return self._get_config_location('public_branch')
703
def set_public_branch(self, location):
704
"""Return the submit location of the branch.
706
This is the default location for bundle. The usual
707
pattern is that the user can override it by specifying a
710
self._set_config_location('public_branch', location)
712
def get_push_location(self):
713
"""Return the None or the location to push this branch to."""
714
push_loc = self.get_config().get_user_option('push_location')
717
def set_push_location(self, location):
718
"""Set a new push location for this branch."""
719
raise NotImplementedError(self.set_push_location)
721
def set_parent(self, url):
722
raise NotImplementedError(self.set_parent)
726
"""Synchronise this branch with the master branch if any.
728
:return: None or the last_revision pivoted out during the update.
732
def check_revno(self, revno):
734
Check whether a revno corresponds to any revision.
735
Zero (the NULL revision) is considered valid.
738
self.check_real_revno(revno)
740
def check_real_revno(self, revno):
742
Check whether a revno corresponds to a real revision.
743
Zero (the NULL revision) is considered invalid
745
if revno < 1 or revno > self.revno():
746
raise errors.InvalidRevisionNumber(revno)
749
def clone(self, to_bzrdir, revision_id=None):
750
"""Clone this branch into to_bzrdir preserving all semantic values.
752
revision_id: if not None, the revision history in the new branch will
753
be truncated to end with revision_id.
755
result = to_bzrdir.create_branch()
756
self.copy_content_into(result, revision_id=revision_id)
760
def sprout(self, to_bzrdir, revision_id=None):
761
"""Create a new line of development from the branch, into to_bzrdir.
763
to_bzrdir controls the branch format.
765
revision_id: if not None, the revision history in the new branch will
766
be truncated to end with revision_id.
768
result = to_bzrdir.create_branch()
769
self.copy_content_into(result, revision_id=revision_id)
770
result.set_parent(self.bzrdir.root_transport.base)
773
def _synchronize_history(self, destination, revision_id):
774
"""Synchronize last revision and revision history between branches.
776
This version is most efficient when the destination is also a
777
BzrBranch6, but works for BzrBranch5, as long as the destination's
778
repository contains all the lefthand ancestors of the intended
779
last_revision. If not, set_last_revision_info will fail.
781
:param destination: The branch to copy the history into
782
:param revision_id: The revision-id to truncate history at. May
783
be None to copy complete history.
785
source_revno, source_revision_id = self.last_revision_info()
786
if revision_id is None:
787
revno, revision_id = source_revno, source_revision_id
788
elif source_revision_id == revision_id:
789
# we know the revno without needing to walk all of history
792
# To figure out the revno for a random revision, we need to build
793
# the revision history, and count its length.
794
# We don't care about the order, just how long it is.
795
# Alternatively, we could start at the current location, and count
796
# backwards. But there is no guarantee that we will find it since
797
# it may be a merged revision.
798
revno = len(list(self.repository.iter_reverse_revision_history(
800
destination.set_last_revision_info(revno, revision_id)
803
def copy_content_into(self, destination, revision_id=None):
804
"""Copy the content of self into destination.
806
revision_id: if not None, the revision history in the new branch will
807
be truncated to end with revision_id.
809
self._synchronize_history(destination, revision_id)
811
parent = self.get_parent()
812
except errors.InaccessibleParent, e:
813
mutter('parent was not accessible to copy: %s', e)
816
destination.set_parent(parent)
817
self.tags.merge_to(destination.tags)
821
"""Check consistency of the branch.
823
In particular this checks that revisions given in the revision-history
824
do actually match up in the revision graph, and that they're all
825
present in the repository.
827
Callers will typically also want to check the repository.
829
:return: A BranchCheckResult.
831
mainline_parent_id = None
832
last_revno, last_revision_id = self.last_revision_info()
833
real_rev_history = list(self.repository.iter_reverse_revision_history(
835
real_rev_history.reverse()
836
if len(real_rev_history) != last_revno:
837
raise errors.BzrCheckError('revno does not match len(mainline)'
838
' %s != %s' % (last_revno, len(real_rev_history)))
839
# TODO: We should probably also check that real_rev_history actually
840
# matches self.revision_history()
841
for revision_id in real_rev_history:
843
revision = self.repository.get_revision(revision_id)
844
except errors.NoSuchRevision, e:
845
raise errors.BzrCheckError("mainline revision {%s} not in repository"
847
# In general the first entry on the revision history has no parents.
848
# But it's not illegal for it to have parents listed; this can happen
849
# in imports from Arch when the parents weren't reachable.
850
if mainline_parent_id is not None:
851
if mainline_parent_id not in revision.parent_ids:
852
raise errors.BzrCheckError("previous revision {%s} not listed among "
854
% (mainline_parent_id, revision_id))
855
mainline_parent_id = revision_id
856
return BranchCheckResult(self)
858
def _get_checkout_format(self):
859
"""Return the most suitable metadir for a checkout of this branch.
860
Weaves are used if this branch's repository uses weaves.
862
if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
863
from bzrlib.repofmt import weaverepo
864
format = bzrdir.BzrDirMetaFormat1()
865
format.repository_format = weaverepo.RepositoryFormat7()
867
format = self.repository.bzrdir.checkout_metadir()
868
format.set_branch_format(self._format)
871
def create_checkout(self, to_location, revision_id=None,
872
lightweight=False, accelerator_tree=None,
874
"""Create a checkout of a branch.
876
:param to_location: The url to produce the checkout at
877
:param revision_id: The revision to check out
878
:param lightweight: If True, produce a lightweight checkout, otherwise,
879
produce a bound branch (heavyweight checkout)
880
:param accelerator_tree: A tree which can be used for retrieving file
881
contents more quickly than the revision tree, i.e. a workingtree.
882
The revision tree will be used for cases where accelerator_tree's
883
content is different.
884
:param hardlink: If true, hard-link files from accelerator_tree,
886
:return: The tree of the created checkout
888
t = transport.get_transport(to_location)
891
format = self._get_checkout_format()
892
checkout = format.initialize_on_transport(t)
893
from_branch = BranchReferenceFormat().initialize(checkout, self)
895
format = self._get_checkout_format()
896
checkout_branch = bzrdir.BzrDir.create_branch_convenience(
897
to_location, force_new_tree=False, format=format)
898
checkout = checkout_branch.bzrdir
899
checkout_branch.bind(self)
900
# pull up to the specified revision_id to set the initial
901
# branch tip correctly, and seed it with history.
902
checkout_branch.pull(self, stop_revision=revision_id)
904
tree = checkout.create_workingtree(revision_id,
905
from_branch=from_branch,
906
accelerator_tree=accelerator_tree,
908
basis_tree = tree.basis_tree()
909
basis_tree.lock_read()
911
for path, file_id in basis_tree.iter_references():
912
reference_parent = self.reference_parent(file_id, path)
913
reference_parent.create_checkout(tree.abspath(path),
914
basis_tree.get_reference_revision(file_id, path),
921
def reconcile(self, thorough=True):
922
"""Make sure the data stored in this branch is consistent."""
923
from bzrlib.reconcile import BranchReconciler
924
reconciler = BranchReconciler(self, thorough=thorough)
925
reconciler.reconcile()
928
def reference_parent(self, file_id, path):
929
"""Return the parent branch for a tree-reference file_id
930
:param file_id: The file_id of the tree reference
931
:param path: The path of the file_id in the tree
932
:return: A branch associated with the file_id
934
# FIXME should provide multiple branches, based on config
935
return Branch.open(self.bzrdir.root_transport.clone(path).base)
937
def supports_tags(self):
938
return self._format.supports_tags()
940
def _check_if_descendant_or_diverged(self, revision_a, revision_b, graph,
942
"""Ensure that revision_b is a descendant of revision_a.
944
This is a helper function for update_revisions.
946
:raises: DivergedBranches if revision_b has diverged from revision_a.
947
:returns: True if revision_b is a descendant of revision_a.
949
relation = self._revision_relations(revision_a, revision_b, graph)
950
if relation == 'b_descends_from_a':
952
elif relation == 'diverged':
953
raise errors.DivergedBranches(self, other_branch)
954
elif relation == 'a_descends_from_b':
957
raise AssertionError("invalid relation: %r" % (relation,))
959
def _revision_relations(self, revision_a, revision_b, graph):
960
"""Determine the relationship between two revisions.
962
:returns: One of: 'a_descends_from_b', 'b_descends_from_a', 'diverged'
964
heads = graph.heads([revision_a, revision_b])
965
if heads == set([revision_b]):
966
return 'b_descends_from_a'
967
elif heads == set([revision_a, revision_b]):
968
# These branches have diverged
970
elif heads == set([revision_a]):
971
return 'a_descends_from_b'
973
raise AssertionError("invalid heads: %r" % (heads,))
976
class BranchFormat(object):
977
"""An encapsulation of the initialization and open routines for a format.
979
Formats provide three things:
980
* An initialization routine,
984
Formats are placed in an dict by their format string for reference
985
during branch opening. Its not required that these be instances, they
986
can be classes themselves with class methods - it simply depends on
987
whether state is needed for a given format or not.
989
Once a format is deprecated, just deprecate the initialize and open
990
methods on the format class. Do not deprecate the object, as the
991
object will be created every time regardless.
994
_default_format = None
995
"""The default format used for new branches."""
998
"""The known formats."""
1000
def __eq__(self, other):
1001
return self.__class__ is other.__class__
1003
def __ne__(self, other):
1004
return not (self == other)
1007
def find_format(klass, a_bzrdir):
1008
"""Return the format for the branch object in a_bzrdir."""
1010
transport = a_bzrdir.get_branch_transport(None)
1011
format_string = transport.get("format").read()
1012
return klass._formats[format_string]
1013
except errors.NoSuchFile:
1014
raise errors.NotBranchError(path=transport.base)
1016
raise errors.UnknownFormatError(format=format_string, kind='branch')
1019
def get_default_format(klass):
1020
"""Return the current default format."""
1021
return klass._default_format
1023
def get_reference(self, a_bzrdir):
1024
"""Get the target reference of the branch in a_bzrdir.
1026
format probing must have been completed before calling
1027
this method - it is assumed that the format of the branch
1028
in a_bzrdir is correct.
1030
:param a_bzrdir: The bzrdir to get the branch data from.
1031
:return: None if the branch is not a reference branch.
1036
def set_reference(self, a_bzrdir, to_branch):
1037
"""Set the target reference of the branch in a_bzrdir.
1039
format probing must have been completed before calling
1040
this method - it is assumed that the format of the branch
1041
in a_bzrdir is correct.
1043
:param a_bzrdir: The bzrdir to set the branch reference for.
1044
:param to_branch: branch that the checkout is to reference
1046
raise NotImplementedError(self.set_reference)
1048
def get_format_string(self):
1049
"""Return the ASCII format string that identifies this format."""
1050
raise NotImplementedError(self.get_format_string)
1052
def get_format_description(self):
1053
"""Return the short format description for this format."""
1054
raise NotImplementedError(self.get_format_description)
1056
def _initialize_helper(self, a_bzrdir, utf8_files, lock_type='metadir',
1058
"""Initialize a branch in a bzrdir, with specified files
1060
:param a_bzrdir: The bzrdir to initialize the branch in
1061
:param utf8_files: The files to create as a list of
1062
(filename, content) tuples
1063
:param set_format: If True, set the format with
1064
self.get_format_string. (BzrBranch4 has its format set
1066
:return: a branch in this format
1068
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
1069
branch_transport = a_bzrdir.get_branch_transport(self)
1071
'metadir': ('lock', lockdir.LockDir),
1072
'branch4': ('branch-lock', lockable_files.TransportLock),
1074
lock_name, lock_class = lock_map[lock_type]
1075
control_files = lockable_files.LockableFiles(branch_transport,
1076
lock_name, lock_class)
1077
control_files.create_lock()
1078
control_files.lock_write()
1080
utf8_files += [('format', self.get_format_string())]
1082
for (filename, content) in utf8_files:
1083
branch_transport.put_bytes(
1085
mode=a_bzrdir._get_file_mode())
1087
control_files.unlock()
1088
return self.open(a_bzrdir, _found=True)
1090
def initialize(self, a_bzrdir):
1091
"""Create a branch of this format in a_bzrdir."""
1092
raise NotImplementedError(self.initialize)
1094
def is_supported(self):
1095
"""Is this format supported?
1097
Supported formats can be initialized and opened.
1098
Unsupported formats may not support initialization or committing or
1099
some other features depending on the reason for not being supported.
1103
def open(self, a_bzrdir, _found=False):
1104
"""Return the branch object for a_bzrdir
1106
_found is a private parameter, do not use it. It is used to indicate
1107
if format probing has already be done.
1109
raise NotImplementedError(self.open)
1112
def register_format(klass, format):
1113
klass._formats[format.get_format_string()] = format
1116
def set_default_format(klass, format):
1117
klass._default_format = format
1119
def supports_stacking(self):
1120
"""True if this format records a stacked-on branch."""
1124
def unregister_format(klass, format):
1125
del klass._formats[format.get_format_string()]
1128
return self.get_format_string().rstrip()
1130
def supports_tags(self):
1131
"""True if this format supports tags stored in the branch"""
1132
return False # by default
1135
class BranchHooks(Hooks):
1136
"""A dictionary mapping hook name to a list of callables for branch hooks.
1138
e.g. ['set_rh'] Is the list of items to be called when the
1139
set_revision_history function is invoked.
1143
"""Create the default hooks.
1145
These are all empty initially, because by default nothing should get
1148
Hooks.__init__(self)
1149
# Introduced in 0.15:
1150
# invoked whenever the revision history has been set
1151
# with set_revision_history. The api signature is
1152
# (branch, revision_history), and the branch will
1155
# Invoked after a branch is opened. The api signature is (branch).
1157
# invoked after a push operation completes.
1158
# the api signature is
1160
# containing the members
1161
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
1162
# where local is the local target branch or None, master is the target
1163
# master branch, and the rest should be self explanatory. The source
1164
# is read locked and the target branches write locked. Source will
1165
# be the local low-latency branch.
1166
self['post_push'] = []
1167
# invoked after a pull operation completes.
1168
# the api signature is
1170
# containing the members
1171
# (source, local, master, old_revno, old_revid, new_revno, new_revid)
1172
# where local is the local branch or None, master is the target
1173
# master branch, and the rest should be self explanatory. The source
1174
# is read locked and the target branches write locked. The local
1175
# branch is the low-latency branch.
1176
self['post_pull'] = []
1177
# invoked before a commit operation takes place.
1178
# the api signature is
1179
# (local, master, old_revno, old_revid, future_revno, future_revid,
1180
# tree_delta, future_tree).
1181
# old_revid is NULL_REVISION for the first commit to a branch
1182
# tree_delta is a TreeDelta object describing changes from the basis
1183
# revision, hooks MUST NOT modify this delta
1184
# future_tree is an in-memory tree obtained from
1185
# CommitBuilder.revision_tree() and hooks MUST NOT modify this tree
1186
self['pre_commit'] = []
1187
# invoked after a commit operation completes.
1188
# the api signature is
1189
# (local, master, old_revno, old_revid, new_revno, new_revid)
1190
# old_revid is NULL_REVISION for the first commit to a branch.
1191
self['post_commit'] = []
1192
# invoked after a uncommit operation completes.
1193
# the api signature is
1194
# (local, master, old_revno, old_revid, new_revno, new_revid) where
1195
# local is the local branch or None, master is the target branch,
1196
# and an empty branch recieves new_revno of 0, new_revid of None.
1197
self['post_uncommit'] = []
1199
# Invoked before the tip of a branch changes.
1200
# the api signature is
1201
# (params) where params is a ChangeBranchTipParams with the members
1202
# (branch, old_revno, new_revno, old_revid, new_revid)
1203
self['pre_change_branch_tip'] = []
1205
# Invoked after the tip of a branch changes.
1206
# the api signature is
1207
# (params) where params is a ChangeBranchTipParams with the members
1208
# (branch, old_revno, new_revno, old_revid, new_revid)
1209
self['post_change_branch_tip'] = []
1211
# Invoked when a stacked branch activates its fallback locations and
1212
# allows the transformation of the url of said location.
1213
# the api signature is
1214
# (branch, url) where branch is the branch having its fallback
1215
# location activated and url is the url for the fallback location.
1216
# The hook should return a url.
1217
self['transform_fallback_location'] = []
1220
# install the default hooks into the Branch class.
1221
Branch.hooks = BranchHooks()
1224
class ChangeBranchTipParams(object):
1225
"""Object holding parameters passed to *_change_branch_tip hooks.
1227
There are 5 fields that hooks may wish to access:
1229
:ivar branch: the branch being changed
1230
:ivar old_revno: revision number before the change
1231
:ivar new_revno: revision number after the change
1232
:ivar old_revid: revision id before the change
1233
:ivar new_revid: revision id after the change
1235
The revid fields are strings. The revno fields are integers.
1238
def __init__(self, branch, old_revno, new_revno, old_revid, new_revid):
1239
"""Create a group of ChangeBranchTip parameters.
1241
:param branch: The branch being changed.
1242
:param old_revno: Revision number before the change.
1243
:param new_revno: Revision number after the change.
1244
:param old_revid: Tip revision id before the change.
1245
:param new_revid: Tip revision id after the change.
1247
self.branch = branch
1248
self.old_revno = old_revno
1249
self.new_revno = new_revno
1250
self.old_revid = old_revid
1251
self.new_revid = new_revid
1253
def __eq__(self, other):
1254
return self.__dict__ == other.__dict__
1257
return "<%s of %s from (%s, %s) to (%s, %s)>" % (
1258
self.__class__.__name__, self.branch,
1259
self.old_revno, self.old_revid, self.new_revno, self.new_revid)
1262
class BzrBranchFormat4(BranchFormat):
1263
"""Bzr branch format 4.
1266
- a revision-history file.
1267
- a branch-lock lock file [ to be shared with the bzrdir ]
1270
def get_format_description(self):
1271
"""See BranchFormat.get_format_description()."""
1272
return "Branch format 4"
1274
def initialize(self, a_bzrdir):
1275
"""Create a branch of this format in a_bzrdir."""
1276
utf8_files = [('revision-history', ''),
1277
('branch-name', ''),
1279
return self._initialize_helper(a_bzrdir, utf8_files,
1280
lock_type='branch4', set_format=False)
1283
super(BzrBranchFormat4, self).__init__()
1284
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1286
def open(self, a_bzrdir, _found=False):
1287
"""Return the branch object for a_bzrdir
1289
_found is a private parameter, do not use it. It is used to indicate
1290
if format probing has already be done.
1293
# we are being called directly and must probe.
1294
raise NotImplementedError
1295
return BzrBranch(_format=self,
1296
_control_files=a_bzrdir._control_files,
1298
_repository=a_bzrdir.open_repository())
1301
return "Bazaar-NG branch format 4"
1304
class BranchFormatMetadir(BranchFormat):
1305
"""Common logic for meta-dir based branch formats."""
1307
def _branch_class(self):
1308
"""What class to instantiate on open calls."""
1309
raise NotImplementedError(self._branch_class)
1311
def open(self, a_bzrdir, _found=False):
1312
"""Return the branch object for a_bzrdir.
1314
_found is a private parameter, do not use it. It is used to indicate
1315
if format probing has already be done.
1318
format = BranchFormat.find_format(a_bzrdir)
1319
if format.__class__ != self.__class__:
1320
raise AssertionError("wrong format %r found for %r" %
1323
transport = a_bzrdir.get_branch_transport(None)
1324
control_files = lockable_files.LockableFiles(transport, 'lock',
1326
return self._branch_class()(_format=self,
1327
_control_files=control_files,
1329
_repository=a_bzrdir.find_repository())
1330
except errors.NoSuchFile:
1331
raise errors.NotBranchError(path=transport.base)
1334
super(BranchFormatMetadir, self).__init__()
1335
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1336
self._matchingbzrdir.set_branch_format(self)
1338
def supports_tags(self):
1342
class BzrBranchFormat5(BranchFormatMetadir):
1343
"""Bzr branch format 5.
1346
- a revision-history file.
1348
- a lock dir guarding the branch itself
1349
- all of this stored in a branch/ subdirectory
1350
- works with shared repositories.
1352
This format is new in bzr 0.8.
1355
def _branch_class(self):
1358
def get_format_string(self):
1359
"""See BranchFormat.get_format_string()."""
1360
return "Bazaar-NG branch format 5\n"
1362
def get_format_description(self):
1363
"""See BranchFormat.get_format_description()."""
1364
return "Branch format 5"
1366
def initialize(self, a_bzrdir):
1367
"""Create a branch of this format in a_bzrdir."""
1368
utf8_files = [('revision-history', ''),
1369
('branch-name', ''),
1371
return self._initialize_helper(a_bzrdir, utf8_files)
1373
def supports_tags(self):
1377
class BzrBranchFormat6(BranchFormatMetadir):
1378
"""Branch format with last-revision and tags.
1380
Unlike previous formats, this has no explicit revision history. Instead,
1381
this just stores the last-revision, and the left-hand history leading
1382
up to there is the history.
1384
This format was introduced in bzr 0.15
1385
and became the default in 0.91.
1388
def _branch_class(self):
1391
def get_format_string(self):
1392
"""See BranchFormat.get_format_string()."""
1393
return "Bazaar Branch Format 6 (bzr 0.15)\n"
1395
def get_format_description(self):
1396
"""See BranchFormat.get_format_description()."""
1397
return "Branch format 6"
1399
def initialize(self, a_bzrdir):
1400
"""Create a branch of this format in a_bzrdir."""
1401
utf8_files = [('last-revision', '0 null:\n'),
1402
('branch.conf', ''),
1405
return self._initialize_helper(a_bzrdir, utf8_files)
1408
class BzrBranchFormat7(BranchFormatMetadir):
1409
"""Branch format with last-revision, tags, and a stacked location pointer.
1411
The stacked location pointer is passed down to the repository and requires
1412
a repository format with supports_external_lookups = True.
1414
This format was introduced in bzr 1.6.
1417
def _branch_class(self):
1420
def get_format_string(self):
1421
"""See BranchFormat.get_format_string()."""
1422
return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
1424
def get_format_description(self):
1425
"""See BranchFormat.get_format_description()."""
1426
return "Branch format 7"
1428
def initialize(self, a_bzrdir):
1429
"""Create a branch of this format in a_bzrdir."""
1430
utf8_files = [('last-revision', '0 null:\n'),
1431
('branch.conf', ''),
1434
return self._initialize_helper(a_bzrdir, utf8_files)
1437
super(BzrBranchFormat7, self).__init__()
1438
self._matchingbzrdir.repository_format = \
1439
RepositoryFormatKnitPack5RichRoot()
1441
def supports_stacking(self):
1445
class BranchReferenceFormat(BranchFormat):
1446
"""Bzr branch reference format.
1448
Branch references are used in implementing checkouts, they
1449
act as an alias to the real branch which is at some other url.
1456
def get_format_string(self):
1457
"""See BranchFormat.get_format_string()."""
1458
return "Bazaar-NG Branch Reference Format 1\n"
1460
def get_format_description(self):
1461
"""See BranchFormat.get_format_description()."""
1462
return "Checkout reference format 1"
1464
def get_reference(self, a_bzrdir):
1465
"""See BranchFormat.get_reference()."""
1466
transport = a_bzrdir.get_branch_transport(None)
1467
return transport.get('location').read()
1469
def set_reference(self, a_bzrdir, to_branch):
1470
"""See BranchFormat.set_reference()."""
1471
transport = a_bzrdir.get_branch_transport(None)
1472
location = transport.put_bytes('location', to_branch.base)
1474
def initialize(self, a_bzrdir, target_branch=None):
1475
"""Create a branch of this format in a_bzrdir."""
1476
if target_branch is None:
1477
# this format does not implement branch itself, thus the implicit
1478
# creation contract must see it as uninitializable
1479
raise errors.UninitializableFormat(self)
1480
mutter('creating branch reference in %s', a_bzrdir.transport.base)
1481
branch_transport = a_bzrdir.get_branch_transport(self)
1482
branch_transport.put_bytes('location',
1483
target_branch.bzrdir.root_transport.base)
1484
branch_transport.put_bytes('format', self.get_format_string())
1486
a_bzrdir, _found=True,
1487
possible_transports=[target_branch.bzrdir.root_transport])
1490
super(BranchReferenceFormat, self).__init__()
1491
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1492
self._matchingbzrdir.set_branch_format(self)
1494
def _make_reference_clone_function(format, a_branch):
1495
"""Create a clone() routine for a branch dynamically."""
1496
def clone(to_bzrdir, revision_id=None):
1497
"""See Branch.clone()."""
1498
return format.initialize(to_bzrdir, a_branch)
1499
# cannot obey revision_id limits when cloning a reference ...
1500
# FIXME RBC 20060210 either nuke revision_id for clone, or
1501
# emit some sort of warning/error to the caller ?!
1504
def open(self, a_bzrdir, _found=False, location=None,
1505
possible_transports=None):
1506
"""Return the branch that the branch reference in a_bzrdir points at.
1508
_found is a private parameter, do not use it. It is used to indicate
1509
if format probing has already be done.
1512
format = BranchFormat.find_format(a_bzrdir)
1513
if format.__class__ != self.__class__:
1514
raise AssertionError("wrong format %r found for %r" %
1516
if location is None:
1517
location = self.get_reference(a_bzrdir)
1518
real_bzrdir = bzrdir.BzrDir.open(
1519
location, possible_transports=possible_transports)
1520
result = real_bzrdir.open_branch()
1521
# this changes the behaviour of result.clone to create a new reference
1522
# rather than a copy of the content of the branch.
1523
# I did not use a proxy object because that needs much more extensive
1524
# testing, and we are only changing one behaviour at the moment.
1525
# If we decide to alter more behaviours - i.e. the implicit nickname
1526
# then this should be refactored to introduce a tested proxy branch
1527
# and a subclass of that for use in overriding clone() and ....
1529
result.clone = self._make_reference_clone_function(result)
1533
# formats which have no format string are not discoverable
1534
# and not independently creatable, so are not registered.
1535
__format5 = BzrBranchFormat5()
1536
__format6 = BzrBranchFormat6()
1537
__format7 = BzrBranchFormat7()
1538
BranchFormat.register_format(__format5)
1539
BranchFormat.register_format(BranchReferenceFormat())
1540
BranchFormat.register_format(__format6)
1541
BranchFormat.register_format(__format7)
1542
BranchFormat.set_default_format(__format6)
1543
_legacy_formats = [BzrBranchFormat4(),
1546
class BzrBranch(Branch):
1547
"""A branch stored in the actual filesystem.
1549
Note that it's "local" in the context of the filesystem; it doesn't
1550
really matter if it's on an nfs/smb/afs/coda/... share, as long as
1551
it's writable, and can be accessed via the normal filesystem API.
1553
:ivar _transport: Transport for file operations on this branch's
1554
control files, typically pointing to the .bzr/branch directory.
1555
:ivar repository: Repository for this branch.
1556
:ivar base: The url of the base directory for this branch; the one
1557
containing the .bzr directory.
1560
def __init__(self, _format=None,
1561
_control_files=None, a_bzrdir=None, _repository=None):
1562
"""Create new branch object at a particular location."""
1563
if a_bzrdir is None:
1564
raise ValueError('a_bzrdir must be supplied')
1566
self.bzrdir = a_bzrdir
1567
self._base = self.bzrdir.transport.clone('..').base
1568
# XXX: We should be able to just do
1569
# self.base = self.bzrdir.root_transport.base
1570
# but this does not quite work yet -- mbp 20080522
1571
self._format = _format
1572
if _control_files is None:
1573
raise ValueError('BzrBranch _control_files is None')
1574
self.control_files = _control_files
1575
self._transport = _control_files._transport
1576
self.repository = _repository
1577
Branch.__init__(self)
1580
return '%s(%r)' % (self.__class__.__name__, self.base)
1584
def _get_base(self):
1585
"""Returns the directory containing the control directory."""
1588
base = property(_get_base, doc="The URL for the root of this branch.")
1590
def is_locked(self):
1591
return self.control_files.is_locked()
1593
def lock_write(self, token=None):
1594
repo_token = self.repository.lock_write()
1596
token = self.control_files.lock_write(token=token)
1598
self.repository.unlock()
1602
def lock_read(self):
1603
self.repository.lock_read()
1605
self.control_files.lock_read()
1607
self.repository.unlock()
1611
# TODO: test for failed two phase locks. This is known broken.
1613
self.control_files.unlock()
1615
self.repository.unlock()
1616
if not self.control_files.is_locked():
1617
# we just released the lock
1618
self._clear_cached_state()
1620
def peek_lock_mode(self):
1621
if self.control_files._lock_count == 0:
1624
return self.control_files._lock_mode
1626
def get_physical_lock_status(self):
1627
return self.control_files.get_physical_lock_status()
1630
def print_file(self, file, revision_id):
1631
"""See Branch.print_file."""
1632
return self.repository.print_file(file, revision_id)
1634
def _write_revision_history(self, history):
1635
"""Factored out of set_revision_history.
1637
This performs the actual writing to disk.
1638
It is intended to be called by BzrBranch5.set_revision_history."""
1639
self._transport.put_bytes(
1640
'revision-history', '\n'.join(history),
1641
mode=self.bzrdir._get_file_mode())
1644
def set_revision_history(self, rev_history):
1645
"""See Branch.set_revision_history."""
1646
if 'evil' in debug.debug_flags:
1647
mutter_callsite(3, "set_revision_history scales with history.")
1648
check_not_reserved_id = _mod_revision.check_not_reserved_id
1649
for rev_id in rev_history:
1650
check_not_reserved_id(rev_id)
1651
if Branch.hooks['post_change_branch_tip']:
1652
# Don't calculate the last_revision_info() if there are no hooks
1654
old_revno, old_revid = self.last_revision_info()
1655
if len(rev_history) == 0:
1656
revid = _mod_revision.NULL_REVISION
1658
revid = rev_history[-1]
1659
self._run_pre_change_branch_tip_hooks(len(rev_history), revid)
1660
self._write_revision_history(rev_history)
1661
self._clear_cached_state()
1662
self._cache_revision_history(rev_history)
1663
for hook in Branch.hooks['set_rh']:
1664
hook(self, rev_history)
1665
if Branch.hooks['post_change_branch_tip']:
1666
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1668
def _synchronize_history(self, destination, revision_id):
1669
"""Synchronize last revision and revision history between branches.
1671
This version is most efficient when the destination is also a
1672
BzrBranch5, but works for BzrBranch6 as long as the revision
1673
history is the true lefthand parent history, and all of the revisions
1674
are in the destination's repository. If not, set_revision_history
1677
:param destination: The branch to copy the history into
1678
:param revision_id: The revision-id to truncate history at. May
1679
be None to copy complete history.
1681
if not isinstance(destination._format, BzrBranchFormat5):
1682
super(BzrBranch, self)._synchronize_history(
1683
destination, revision_id)
1685
if revision_id == _mod_revision.NULL_REVISION:
1688
new_history = self.revision_history()
1689
if revision_id is not None and new_history != []:
1691
new_history = new_history[:new_history.index(revision_id) + 1]
1693
rev = self.repository.get_revision(revision_id)
1694
new_history = rev.get_history(self.repository)[1:]
1695
destination.set_revision_history(new_history)
1697
def _run_pre_change_branch_tip_hooks(self, new_revno, new_revid):
1698
"""Run the pre_change_branch_tip hooks."""
1699
hooks = Branch.hooks['pre_change_branch_tip']
1702
old_revno, old_revid = self.last_revision_info()
1703
params = ChangeBranchTipParams(
1704
self, old_revno, new_revno, old_revid, new_revid)
1708
except errors.TipChangeRejected:
1711
exc_info = sys.exc_info()
1712
hook_name = Branch.hooks.get_hook_name(hook)
1713
raise errors.HookFailed(
1714
'pre_change_branch_tip', hook_name, exc_info)
1716
def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
1717
"""Run the post_change_branch_tip hooks."""
1718
hooks = Branch.hooks['post_change_branch_tip']
1721
new_revno, new_revid = self.last_revision_info()
1722
params = ChangeBranchTipParams(
1723
self, old_revno, new_revno, old_revid, new_revid)
1728
def set_last_revision_info(self, revno, revision_id):
1729
"""Set the last revision of this branch.
1731
The caller is responsible for checking that the revno is correct
1732
for this revision id.
1734
It may be possible to set the branch last revision to an id not
1735
present in the repository. However, branches can also be
1736
configured to check constraints on history, in which case this may not
1739
revision_id = _mod_revision.ensure_null(revision_id)
1740
# this old format stores the full history, but this api doesn't
1741
# provide it, so we must generate, and might as well check it's
1743
history = self._lefthand_history(revision_id)
1744
if len(history) != revno:
1745
raise AssertionError('%d != %d' % (len(history), revno))
1746
self.set_revision_history(history)
1748
def _gen_revision_history(self):
1749
history = self._transport.get_bytes('revision-history').split('\n')
1750
if history[-1:] == ['']:
1751
# There shouldn't be a trailing newline, but just in case.
1755
def _lefthand_history(self, revision_id, last_rev=None,
1757
if 'evil' in debug.debug_flags:
1758
mutter_callsite(4, "_lefthand_history scales with history.")
1759
# stop_revision must be a descendant of last_revision
1760
graph = self.repository.get_graph()
1761
if last_rev is not None:
1762
if not graph.is_ancestor(last_rev, revision_id):
1763
# our previous tip is not merged into stop_revision
1764
raise errors.DivergedBranches(self, other_branch)
1765
# make a new revision history from the graph
1766
parents_map = graph.get_parent_map([revision_id])
1767
if revision_id not in parents_map:
1768
raise errors.NoSuchRevision(self, revision_id)
1769
current_rev_id = revision_id
1771
check_not_reserved_id = _mod_revision.check_not_reserved_id
1772
# Do not include ghosts or graph origin in revision_history
1773
while (current_rev_id in parents_map and
1774
len(parents_map[current_rev_id]) > 0):
1775
check_not_reserved_id(current_rev_id)
1776
new_history.append(current_rev_id)
1777
current_rev_id = parents_map[current_rev_id][0]
1778
parents_map = graph.get_parent_map([current_rev_id])
1779
new_history.reverse()
1783
def generate_revision_history(self, revision_id, last_rev=None,
1785
"""Create a new revision history that will finish with revision_id.
1787
:param revision_id: the new tip to use.
1788
:param last_rev: The previous last_revision. If not None, then this
1789
must be a ancestory of revision_id, or DivergedBranches is raised.
1790
:param other_branch: The other branch that DivergedBranches should
1791
raise with respect to.
1793
self.set_revision_history(self._lefthand_history(revision_id,
1794
last_rev, other_branch))
1796
def basis_tree(self):
1797
"""See Branch.basis_tree."""
1798
return self.repository.revision_tree(self.last_revision())
1801
def pull(self, source, overwrite=False, stop_revision=None,
1802
_hook_master=None, run_hooks=True, possible_transports=None,
1803
_override_hook_target=None):
1806
:param _hook_master: Private parameter - set the branch to
1807
be supplied as the master to pull hooks.
1808
:param run_hooks: Private parameter - if false, this branch
1809
is being called because it's the master of the primary branch,
1810
so it should not run its hooks.
1811
:param _override_hook_target: Private parameter - set the branch to be
1812
supplied as the target_branch to pull hooks.
1814
result = PullResult()
1815
result.source_branch = source
1816
if _override_hook_target is None:
1817
result.target_branch = self
1819
result.target_branch = _override_hook_target
1822
# We assume that during 'pull' the local repository is closer than
1824
graph = self.repository.get_graph(source.repository)
1825
result.old_revno, result.old_revid = self.last_revision_info()
1826
self.update_revisions(source, stop_revision, overwrite=overwrite,
1828
result.tag_conflicts = source.tags.merge_to(self.tags, overwrite)
1829
result.new_revno, result.new_revid = self.last_revision_info()
1831
result.master_branch = _hook_master
1832
result.local_branch = result.target_branch
1834
result.master_branch = result.target_branch
1835
result.local_branch = None
1837
for hook in Branch.hooks['post_pull']:
1843
def _get_parent_location(self):
1844
_locs = ['parent', 'pull', 'x-pull']
1847
return self._transport.get_bytes(l).strip('\n')
1848
except errors.NoSuchFile:
1853
def push(self, target, overwrite=False, stop_revision=None,
1854
_override_hook_source_branch=None):
1857
This is the basic concrete implementation of push()
1859
:param _override_hook_source_branch: If specified, run
1860
the hooks passing this Branch as the source, rather than self.
1861
This is for use of RemoteBranch, where push is delegated to the
1862
underlying vfs-based Branch.
1864
# TODO: Public option to disable running hooks - should be trivial but
1866
return _run_with_write_locked_target(
1867
target, self._push_with_bound_branches, target, overwrite,
1869
_override_hook_source_branch=_override_hook_source_branch)
1871
def _push_with_bound_branches(self, target, overwrite,
1873
_override_hook_source_branch=None):
1874
"""Push from self into target, and into target's master if any.
1876
This is on the base BzrBranch class even though it doesn't support
1877
bound branches because the *target* might be bound.
1880
if _override_hook_source_branch:
1881
result.source_branch = _override_hook_source_branch
1882
for hook in Branch.hooks['post_push']:
1885
bound_location = target.get_bound_location()
1886
if bound_location and target.base != bound_location:
1887
# there is a master branch.
1889
# XXX: Why the second check? Is it even supported for a branch to
1890
# be bound to itself? -- mbp 20070507
1891
master_branch = target.get_master_branch()
1892
master_branch.lock_write()
1894
# push into the master from this branch.
1895
self._basic_push(master_branch, overwrite, stop_revision)
1896
# and push into the target branch from this. Note that we push from
1897
# this branch again, because its considered the highest bandwidth
1899
result = self._basic_push(target, overwrite, stop_revision)
1900
result.master_branch = master_branch
1901
result.local_branch = target
1905
master_branch.unlock()
1908
result = self._basic_push(target, overwrite, stop_revision)
1909
# TODO: Why set master_branch and local_branch if there's no
1910
# binding? Maybe cleaner to just leave them unset? -- mbp
1912
result.master_branch = target
1913
result.local_branch = None
1917
def _basic_push(self, target, overwrite, stop_revision):
1918
"""Basic implementation of push without bound branches or hooks.
1920
Must be called with self read locked and target write locked.
1922
result = PushResult()
1923
result.source_branch = self
1924
result.target_branch = target
1925
result.old_revno, result.old_revid = target.last_revision_info()
1926
if result.old_revid != self.last_revision():
1927
# We assume that during 'push' this repository is closer than
1929
graph = self.repository.get_graph(target.repository)
1930
target.update_revisions(self, stop_revision, overwrite=overwrite,
1932
if self._push_should_merge_tags():
1933
result.tag_conflicts = self.tags.merge_to(target.tags, overwrite)
1934
result.new_revno, result.new_revid = target.last_revision_info()
1937
def _push_should_merge_tags(self):
1938
"""Should _basic_push merge this branch's tags into the target?
1940
The default implementation returns False if this branch has no tags,
1941
and True the rest of the time. Subclasses may override this.
1943
return self.tags.supports_tags() and self.tags.get_tag_dict()
1945
def get_parent(self):
1946
"""See Branch.get_parent."""
1947
parent = self._get_parent_location()
1950
# This is an old-format absolute path to a local branch
1951
# turn it into a url
1952
if parent.startswith('/'):
1953
parent = urlutils.local_path_to_url(parent.decode('utf8'))
1955
return urlutils.join(self.base[:-1], parent)
1956
except errors.InvalidURLJoin, e:
1957
raise errors.InaccessibleParent(parent, self.base)
1959
def get_stacked_on_url(self):
1960
raise errors.UnstackableBranchFormat(self._format, self.base)
1962
def set_push_location(self, location):
1963
"""See Branch.set_push_location."""
1964
self.get_config().set_user_option(
1965
'push_location', location,
1966
store=_mod_config.STORE_LOCATION_NORECURSE)
1969
def set_parent(self, url):
1970
"""See Branch.set_parent."""
1971
# TODO: Maybe delete old location files?
1972
# URLs should never be unicode, even on the local fs,
1973
# FIXUP this and get_parent in a future branch format bump:
1974
# read and rewrite the file. RBC 20060125
1976
if isinstance(url, unicode):
1978
url = url.encode('ascii')
1979
except UnicodeEncodeError:
1980
raise errors.InvalidURL(url,
1981
"Urls must be 7-bit ascii, "
1982
"use bzrlib.urlutils.escape")
1983
url = urlutils.relative_url(self.base, url)
1984
self._set_parent_location(url)
1986
def _set_parent_location(self, url):
1988
self._transport.delete('parent')
1990
self._transport.put_bytes('parent', url + '\n',
1991
mode=self.bzrdir._get_file_mode())
1993
def set_stacked_on_url(self, url):
1994
raise errors.UnstackableBranchFormat(self._format, self.base)
1997
class BzrBranch5(BzrBranch):
1998
"""A format 5 branch. This supports new features over plain branches.
2000
It has support for a master_branch which is the data for bound branches.
2004
def pull(self, source, overwrite=False, stop_revision=None,
2005
run_hooks=True, possible_transports=None,
2006
_override_hook_target=None):
2007
"""Pull from source into self, updating my master if any.
2009
:param run_hooks: Private parameter - if false, this branch
2010
is being called because it's the master of the primary branch,
2011
so it should not run its hooks.
2013
bound_location = self.get_bound_location()
2014
master_branch = None
2015
if bound_location and source.base != bound_location:
2016
# not pulling from master, so we need to update master.
2017
master_branch = self.get_master_branch(possible_transports)
2018
master_branch.lock_write()
2021
# pull from source into master.
2022
master_branch.pull(source, overwrite, stop_revision,
2024
return super(BzrBranch5, self).pull(source, overwrite,
2025
stop_revision, _hook_master=master_branch,
2026
run_hooks=run_hooks,
2027
_override_hook_target=_override_hook_target)
2030
master_branch.unlock()
2032
def get_bound_location(self):
2034
return self._transport.get_bytes('bound')[:-1]
2035
except errors.NoSuchFile:
2039
def get_master_branch(self, possible_transports=None):
2040
"""Return the branch we are bound to.
2042
:return: Either a Branch, or None
2044
This could memoise the branch, but if thats done
2045
it must be revalidated on each new lock.
2046
So for now we just don't memoise it.
2047
# RBC 20060304 review this decision.
2049
bound_loc = self.get_bound_location()
2053
return Branch.open(bound_loc,
2054
possible_transports=possible_transports)
2055
except (errors.NotBranchError, errors.ConnectionError), e:
2056
raise errors.BoundBranchConnectionFailure(
2060
def set_bound_location(self, location):
2061
"""Set the target where this branch is bound to.
2063
:param location: URL to the target branch
2066
self._transport.put_bytes('bound', location+'\n',
2067
mode=self.bzrdir._get_file_mode())
2070
self._transport.delete('bound')
2071
except errors.NoSuchFile:
2076
def bind(self, other):
2077
"""Bind this branch to the branch other.
2079
This does not push or pull data between the branches, though it does
2080
check for divergence to raise an error when the branches are not
2081
either the same, or one a prefix of the other. That behaviour may not
2082
be useful, so that check may be removed in future.
2084
:param other: The branch to bind to
2087
# TODO: jam 20051230 Consider checking if the target is bound
2088
# It is debatable whether you should be able to bind to
2089
# a branch which is itself bound.
2090
# Committing is obviously forbidden,
2091
# but binding itself may not be.
2092
# Since we *have* to check at commit time, we don't
2093
# *need* to check here
2095
# we want to raise diverged if:
2096
# last_rev is not in the other_last_rev history, AND
2097
# other_last_rev is not in our history, and do it without pulling
2099
self.set_bound_location(other.base)
2103
"""If bound, unbind"""
2104
return self.set_bound_location(None)
2107
def update(self, possible_transports=None):
2108
"""Synchronise this branch with the master branch if any.
2110
:return: None or the last_revision that was pivoted out during the
2113
master = self.get_master_branch(possible_transports)
2114
if master is not None:
2115
old_tip = _mod_revision.ensure_null(self.last_revision())
2116
self.pull(master, overwrite=True)
2117
if self.repository.get_graph().is_ancestor(old_tip,
2118
_mod_revision.ensure_null(self.last_revision())):
2124
class BzrBranch7(BzrBranch5):
2125
"""A branch with support for a fallback repository."""
2127
def _get_fallback_repository(self, url):
2128
"""Get the repository we fallback to at url."""
2129
url = urlutils.join(self.base, url)
2130
a_bzrdir = bzrdir.BzrDir.open(url,
2131
possible_transports=[self._transport])
2132
return a_bzrdir.open_branch().repository
2134
def _activate_fallback_location(self, url):
2135
"""Activate the branch/repository from url as a fallback repository."""
2136
self.repository.add_fallback_repository(
2137
self._get_fallback_repository(url))
2139
def _open_hook(self):
2141
url = self.get_stacked_on_url()
2142
except (errors.UnstackableRepositoryFormat, errors.NotStacked,
2143
errors.UnstackableBranchFormat):
2146
for hook in Branch.hooks['transform_fallback_location']:
2147
url = hook(self, url)
2149
hook_name = Branch.hooks.get_hook_name(hook)
2150
raise AssertionError(
2151
"'transform_fallback_location' hook %s returned "
2152
"None, not a URL." % hook_name)
2153
self._activate_fallback_location(url)
2155
def _check_stackable_repo(self):
2156
if not self.repository._format.supports_external_lookups:
2157
raise errors.UnstackableRepositoryFormat(self.repository._format,
2158
self.repository.base)
2160
def __init__(self, *args, **kwargs):
2161
super(BzrBranch7, self).__init__(*args, **kwargs)
2162
self._last_revision_info_cache = None
2163
self._partial_revision_history_cache = []
2165
def _clear_cached_state(self):
2166
super(BzrBranch7, self)._clear_cached_state()
2167
self._last_revision_info_cache = None
2168
self._partial_revision_history_cache = []
2170
def _last_revision_info(self):
2171
revision_string = self._transport.get_bytes('last-revision')
2172
revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
2173
revision_id = cache_utf8.get_cached_utf8(revision_id)
2175
return revno, revision_id
2177
def _write_last_revision_info(self, revno, revision_id):
2178
"""Simply write out the revision id, with no checks.
2180
Use set_last_revision_info to perform this safely.
2182
Does not update the revision_history cache.
2183
Intended to be called by set_last_revision_info and
2184
_write_revision_history.
2186
revision_id = _mod_revision.ensure_null(revision_id)
2187
out_string = '%d %s\n' % (revno, revision_id)
2188
self._transport.put_bytes('last-revision', out_string,
2189
mode=self.bzrdir._get_file_mode())
2192
def set_last_revision_info(self, revno, revision_id):
2193
revision_id = _mod_revision.ensure_null(revision_id)
2194
old_revno, old_revid = self.last_revision_info()
2195
if self._get_append_revisions_only():
2196
self._check_history_violation(revision_id)
2197
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2198
self._write_last_revision_info(revno, revision_id)
2199
self._clear_cached_state()
2200
self._last_revision_info_cache = revno, revision_id
2201
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2203
def _synchronize_history(self, destination, revision_id):
2204
"""Synchronize last revision and revision history between branches.
2206
:see: Branch._synchronize_history
2208
# XXX: The base Branch has a fast implementation of this method based
2209
# on set_last_revision_info, but BzrBranch/BzrBranch5 have a slower one
2210
# that uses set_revision_history. This class inherits from BzrBranch5,
2211
# but wants the fast implementation, so it calls
2212
# Branch._synchronize_history directly.
2213
Branch._synchronize_history(self, destination, revision_id)
2215
def _check_history_violation(self, revision_id):
2216
last_revision = _mod_revision.ensure_null(self.last_revision())
2217
if _mod_revision.is_null(last_revision):
2219
if last_revision not in self._lefthand_history(revision_id):
2220
raise errors.AppendRevisionsOnlyViolation(self.base)
2222
def _gen_revision_history(self):
2223
"""Generate the revision history from last revision
2225
last_revno, last_revision = self.last_revision_info()
2226
self._extend_partial_history(stop_index=last_revno-1)
2227
return list(reversed(self._partial_revision_history_cache))
2229
def _extend_partial_history(self, stop_index=None, stop_revision=None):
2230
"""Extend the partial history to include a given index
2232
If a stop_index is supplied, stop when that index has been reached.
2233
If a stop_revision is supplied, stop when that revision is
2234
encountered. Otherwise, stop when the beginning of history is
2237
:param stop_index: The index which should be present. When it is
2238
present, history extension will stop.
2239
:param revision_id: The revision id which should be present. When
2240
it is encountered, history extension will stop.
2242
repo = self.repository
2243
if len(self._partial_revision_history_cache) == 0:
2244
iterator = repo.iter_reverse_revision_history(self.last_revision())
2246
start_revision = self._partial_revision_history_cache[-1]
2247
iterator = repo.iter_reverse_revision_history(start_revision)
2248
#skip the last revision in the list
2249
next_revision = iterator.next()
2250
for revision_id in iterator:
2251
self._partial_revision_history_cache.append(revision_id)
2252
if (stop_index is not None and
2253
len(self._partial_revision_history_cache) > stop_index):
2255
if revision_id == stop_revision:
2258
def _write_revision_history(self, history):
2259
"""Factored out of set_revision_history.
2261
This performs the actual writing to disk, with format-specific checks.
2262
It is intended to be called by BzrBranch5.set_revision_history.
2264
if len(history) == 0:
2265
last_revision = 'null:'
2267
if history != self._lefthand_history(history[-1]):
2268
raise errors.NotLefthandHistory(history)
2269
last_revision = history[-1]
2270
if self._get_append_revisions_only():
2271
self._check_history_violation(last_revision)
2272
self._write_last_revision_info(len(history), last_revision)
2275
def _set_parent_location(self, url):
2276
"""Set the parent branch"""
2277
self._set_config_location('parent_location', url, make_relative=True)
2280
def _get_parent_location(self):
2281
"""Set the parent branch"""
2282
return self._get_config_location('parent_location')
2284
def set_push_location(self, location):
2285
"""See Branch.set_push_location."""
2286
self._set_config_location('push_location', location)
2288
def set_bound_location(self, location):
2289
"""See Branch.set_push_location."""
2291
config = self.get_config()
2292
if location is None:
2293
if config.get_user_option('bound') != 'True':
2296
config.set_user_option('bound', 'False', warn_masked=True)
2299
self._set_config_location('bound_location', location,
2301
config.set_user_option('bound', 'True', warn_masked=True)
2304
def _get_bound_location(self, bound):
2305
"""Return the bound location in the config file.
2307
Return None if the bound parameter does not match"""
2308
config = self.get_config()
2309
config_bound = (config.get_user_option('bound') == 'True')
2310
if config_bound != bound:
2312
return self._get_config_location('bound_location', config=config)
2314
def get_bound_location(self):
2315
"""See Branch.set_push_location."""
2316
return self._get_bound_location(True)
2318
def get_old_bound_location(self):
2319
"""See Branch.get_old_bound_location"""
2320
return self._get_bound_location(False)
2322
def get_stacked_on_url(self):
2323
# you can always ask for the URL; but you might not be able to use it
2324
# if the repo can't support stacking.
2325
## self._check_stackable_repo()
2326
stacked_url = self._get_config_location('stacked_on_location')
2327
if stacked_url is None:
2328
raise errors.NotStacked(self)
2331
def set_append_revisions_only(self, enabled):
2336
self.get_config().set_user_option('append_revisions_only', value,
2339
def set_stacked_on_url(self, url):
2340
self._check_stackable_repo()
2343
old_url = self.get_stacked_on_url()
2344
except (errors.NotStacked, errors.UnstackableBranchFormat,
2345
errors.UnstackableRepositoryFormat):
2348
# repositories don't offer an interface to remove fallback
2349
# repositories today; take the conceptually simpler option and just
2351
self.repository = self.bzrdir.find_repository()
2352
# for every revision reference the branch has, ensure it is pulled
2354
source_repository = self._get_fallback_repository(old_url)
2355
for revision_id in chain([self.last_revision()],
2356
self.tags.get_reverse_tag_dict()):
2357
self.repository.fetch(source_repository, revision_id,
2360
self._activate_fallback_location(url)
2361
# write this out after the repository is stacked to avoid setting a
2362
# stacked config that doesn't work.
2363
self._set_config_location('stacked_on_location', url)
2365
def _get_append_revisions_only(self):
2366
value = self.get_config().get_user_option('append_revisions_only')
2367
return value == 'True'
2369
def _make_tags(self):
2370
return BasicTags(self)
2373
def generate_revision_history(self, revision_id, last_rev=None,
2375
"""See BzrBranch5.generate_revision_history"""
2376
history = self._lefthand_history(revision_id, last_rev, other_branch)
2377
revno = len(history)
2378
self.set_last_revision_info(revno, revision_id)
2381
def get_rev_id(self, revno, history=None):
2382
"""Find the revision id of the specified revno."""
2384
return _mod_revision.NULL_REVISION
2386
last_revno, last_revision_id = self.last_revision_info()
2387
if revno <= 0 or revno > last_revno:
2388
raise errors.NoSuchRevision(self, revno)
2390
if history is not None:
2391
return history[revno - 1]
2393
index = last_revno - revno
2394
if len(self._partial_revision_history_cache) <= index:
2395
self._extend_partial_history(stop_index=index)
2396
if len(self._partial_revision_history_cache) > index:
2397
return self._partial_revision_history_cache[index]
2399
raise errors.NoSuchRevision(self, revno)
2402
def revision_id_to_revno(self, revision_id):
2403
"""Given a revision id, return its revno"""
2404
if _mod_revision.is_null(revision_id):
2407
index = self._partial_revision_history_cache.index(revision_id)
2409
self._extend_partial_history(stop_revision=revision_id)
2410
index = len(self._partial_revision_history_cache) - 1
2411
if self._partial_revision_history_cache[index] != revision_id:
2412
raise errors.NoSuchRevision(self, revision_id)
2413
return self.revno() - index
2416
class BzrBranch6(BzrBranch7):
2417
"""See BzrBranchFormat6 for the capabilities of this branch.
2419
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
2423
def get_stacked_on_url(self):
2424
raise errors.UnstackableBranchFormat(self._format, self.base)
2426
def set_stacked_on_url(self, url):
2427
raise errors.UnstackableBranchFormat(self._format, self.base)
2430
######################################################################
2431
# results of operations
2434
class _Result(object):
2436
def _show_tag_conficts(self, to_file):
2437
if not getattr(self, 'tag_conflicts', None):
2439
to_file.write('Conflicting tags:\n')
2440
for name, value1, value2 in self.tag_conflicts:
2441
to_file.write(' %s\n' % (name, ))
2444
class PullResult(_Result):
2445
"""Result of a Branch.pull operation.
2447
:ivar old_revno: Revision number before pull.
2448
:ivar new_revno: Revision number after pull.
2449
:ivar old_revid: Tip revision id before pull.
2450
:ivar new_revid: Tip revision id after pull.
2451
:ivar source_branch: Source (local) branch object.
2452
:ivar master_branch: Master branch of the target, or the target if no
2454
:ivar local_branch: target branch if there is a Master, else None
2455
:ivar target_branch: Target/destination branch object.
2456
:ivar tag_conflicts: A list of tag conflicts, see BasicTags.merge_to
2460
# DEPRECATED: pull used to return the change in revno
2461
return self.new_revno - self.old_revno
2463
def report(self, to_file):
2465
if self.old_revid == self.new_revid:
2466
to_file.write('No revisions to pull.\n')
2468
to_file.write('Now on revision %d.\n' % self.new_revno)
2469
self._show_tag_conficts(to_file)
2472
class PushResult(_Result):
2473
"""Result of a Branch.push operation.
2475
:ivar old_revno: Revision number before push.
2476
:ivar new_revno: Revision number after push.
2477
:ivar old_revid: Tip revision id before push.
2478
:ivar new_revid: Tip revision id after push.
2479
:ivar source_branch: Source branch object.
2480
:ivar master_branch: Master branch of the target, or None.
2481
:ivar target_branch: Target/destination branch object.
2485
# DEPRECATED: push used to return the change in revno
2486
return self.new_revno - self.old_revno
2488
def report(self, to_file):
2489
"""Write a human-readable description of the result."""
2490
if self.old_revid == self.new_revid:
2491
to_file.write('No new revisions to push.\n')
2493
to_file.write('Pushed up to revision %d.\n' % self.new_revno)
2494
self._show_tag_conficts(to_file)
2497
class BranchCheckResult(object):
2498
"""Results of checking branch consistency.
2503
def __init__(self, branch):
2504
self.branch = branch
2506
def report_results(self, verbose):
2507
"""Report the check results via trace.note.
2509
:param verbose: Requests more detailed display of what was checked,
2512
note('checked branch %s format %s',
2514
self.branch._format)
2517
class Converter5to6(object):
2518
"""Perform an in-place upgrade of format 5 to format 6"""
2520
def convert(self, branch):
2521
# Data for 5 and 6 can peacefully coexist.
2522
format = BzrBranchFormat6()
2523
new_branch = format.open(branch.bzrdir, _found=True)
2525
# Copy source data into target
2526
new_branch._write_last_revision_info(*branch.last_revision_info())
2527
new_branch.set_parent(branch.get_parent())
2528
new_branch.set_bound_location(branch.get_bound_location())
2529
new_branch.set_push_location(branch.get_push_location())
2531
# New branch has no tags by default
2532
new_branch.tags._set_tag_dict({})
2534
# Copying done; now update target format
2535
new_branch._transport.put_bytes('format',
2536
format.get_format_string(),
2537
mode=new_branch.bzrdir._get_file_mode())
2539
# Clean up old files
2540
new_branch._transport.delete('revision-history')
2542
branch.set_parent(None)
2543
except errors.NoSuchFile:
2545
branch.set_bound_location(None)
2548
class Converter6to7(object):
2549
"""Perform an in-place upgrade of format 6 to format 7"""
2551
def convert(self, branch):
2552
format = BzrBranchFormat7()
2553
branch._set_config_location('stacked_on_location', '')
2554
# update target format
2555
branch._transport.put_bytes('format', format.get_format_string())
2559
def _run_with_write_locked_target(target, callable, *args, **kwargs):
2560
"""Run ``callable(*args, **kwargs)``, write-locking target for the
2563
_run_with_write_locked_target will attempt to release the lock it acquires.
2565
If an exception is raised by callable, then that exception *will* be
2566
propagated, even if the unlock attempt raises its own error. Thus
2567
_run_with_write_locked_target should be preferred to simply doing::
2571
return callable(*args, **kwargs)
2576
# This is very similar to bzrlib.decorators.needs_write_lock. Perhaps they
2577
# should share code?
2580
result = callable(*args, **kwargs)
2582
exc_info = sys.exc_info()
2586
raise exc_info[0], exc_info[1], exc_info[2]