1
# Copyright (C) 2005-2012 Canonical Ltd
2
# Copyright (C) 2017 Breezy Developers
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
from __future__ import absolute_import
20
from .lazy_import import lazy_import
21
lazy_import(globals(), """
24
config as _mod_config,
37
revision as _mod_revision,
43
BranchWriteLockResult,
46
from .decorators import (
51
from .lock import _RelockDebugMixin, LogicalLockResult
60
class BzrBranch(Branch, _RelockDebugMixin):
61
"""A branch stored in the actual filesystem.
63
Note that it's "local" in the context of the filesystem; it doesn't
64
really matter if it's on an nfs/smb/afs/coda/... share, as long as
65
it's writable, and can be accessed via the normal filesystem API.
67
:ivar _transport: Transport for file operations on this branch's
68
control files, typically pointing to the .bzr/branch directory.
69
:ivar repository: Repository for this branch.
70
:ivar base: The url of the base directory for this branch; the one
71
containing the .bzr directory.
72
:ivar name: Optional colocated branch name as it exists in the control
76
def __init__(self, _format=None,
77
_control_files=None, a_bzrdir=None, name=None,
78
_repository=None, ignore_fallbacks=False,
79
possible_transports=None):
80
"""Create new branch object at a particular location."""
82
raise ValueError('a_bzrdir must be supplied')
84
raise ValueError('name must be supplied')
85
self.bzrdir = a_bzrdir
86
self._user_transport = self.bzrdir.transport.clone('..')
88
self._user_transport.set_segment_parameter(
89
"branch", urlutils.escape(name))
90
self._base = self._user_transport.base
92
self._format = _format
93
if _control_files is None:
94
raise ValueError('BzrBranch _control_files is None')
95
self.control_files = _control_files
96
self._transport = _control_files._transport
97
self.repository = _repository
98
self.conf_store = None
99
Branch.__init__(self, possible_transports)
102
return '%s(%s)' % (self.__class__.__name__, self.user_url)
107
"""Returns the directory containing the control directory."""
110
base = property(_get_base, doc="The URL for the root of this branch.")
113
def user_transport(self):
114
return self._user_transport
116
def _get_config(self):
117
return _mod_config.TransportConfig(self._transport, 'branch.conf')
119
def _get_config_store(self):
120
if self.conf_store is None:
121
self.conf_store = _mod_config.BranchStore(self)
122
return self.conf_store
124
def _uncommitted_branch(self):
125
"""Return the branch that may contain uncommitted changes."""
126
master = self.get_master_branch()
127
if master is not None:
132
def store_uncommitted(self, creator):
133
"""Store uncommitted changes from a ShelfCreator.
135
:param creator: The ShelfCreator containing uncommitted changes, or
136
None to delete any stored changes.
137
:raises: ChangesAlreadyStored if the branch already has changes.
139
branch = self._uncommitted_branch()
141
branch._transport.delete('stored-transform')
143
if branch._transport.has('stored-transform'):
144
raise errors.ChangesAlreadyStored
145
transform = BytesIO()
146
creator.write_shelf(transform)
148
branch._transport.put_file('stored-transform', transform)
150
def get_unshelver(self, tree):
151
"""Return a shelf.Unshelver for this branch and tree.
153
:param tree: The tree to use to construct the Unshelver.
154
:return: an Unshelver or None if no changes are stored.
156
branch = self._uncommitted_branch()
158
transform = branch._transport.get('stored-transform')
159
except errors.NoSuchFile:
161
return shelf.Unshelver.from_tree_and_shelf(tree, transform)
164
return self.control_files.is_locked()
166
def lock_write(self, token=None):
167
"""Lock the branch for write operations.
169
:param token: A token to permit reacquiring a previously held and
171
:return: A BranchWriteLockResult.
173
if not self.is_locked():
175
self.repository._warn_if_deprecated(self)
176
self.repository.lock_write()
181
return BranchWriteLockResult(self.unlock,
182
self.control_files.lock_write(token=token))
185
self.repository.unlock()
189
"""Lock the branch for read operations.
191
:return: A breezy.lock.LogicalLockResult.
193
if not self.is_locked():
195
self.repository._warn_if_deprecated(self)
196
self.repository.lock_read()
201
self.control_files.lock_read()
202
return LogicalLockResult(self.unlock)
205
self.repository.unlock()
208
@only_raises(errors.LockNotHeld, errors.LockBroken)
210
if self.control_files._lock_count == 1 and self.conf_store is not None:
211
self.conf_store.save_changes()
213
self.control_files.unlock()
215
if not self.control_files.is_locked():
216
self.repository.unlock()
217
# we just released the lock
218
self._clear_cached_state()
220
def peek_lock_mode(self):
221
if self.control_files._lock_count == 0:
224
return self.control_files._lock_mode
226
def get_physical_lock_status(self):
227
return self.control_files.get_physical_lock_status()
230
def print_file(self, file, revision_id):
231
"""See Branch.print_file."""
232
return self.repository.print_file(file, revision_id)
235
def set_last_revision_info(self, revno, revision_id):
236
if not revision_id or not isinstance(revision_id, basestring):
237
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
238
revision_id = _mod_revision.ensure_null(revision_id)
239
old_revno, old_revid = self.last_revision_info()
240
if self.get_append_revisions_only():
241
self._check_history_violation(revision_id)
242
self._run_pre_change_branch_tip_hooks(revno, revision_id)
243
self._write_last_revision_info(revno, revision_id)
244
self._clear_cached_state()
245
self._last_revision_info_cache = revno, revision_id
246
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
248
def basis_tree(self):
249
"""See Branch.basis_tree."""
250
return self.repository.revision_tree(self.last_revision())
252
def _get_parent_location(self):
253
_locs = ['parent', 'pull', 'x-pull']
256
return self._transport.get_bytes(l).strip('\n')
257
except errors.NoSuchFile:
261
def get_stacked_on_url(self):
262
raise errors.UnstackableBranchFormat(self._format, self.user_url)
264
def set_push_location(self, location):
265
"""See Branch.set_push_location."""
266
self.get_config().set_user_option(
267
'push_location', location,
268
store=_mod_config.STORE_LOCATION_NORECURSE)
270
def _set_parent_location(self, url):
272
self._transport.delete('parent')
274
self._transport.put_bytes('parent', url + '\n',
275
mode=self.bzrdir._get_file_mode())
279
"""If bound, unbind"""
280
return self.set_bound_location(None)
283
def bind(self, other):
284
"""Bind this branch to the branch other.
286
This does not push or pull data between the branches, though it does
287
check for divergence to raise an error when the branches are not
288
either the same, or one a prefix of the other. That behaviour may not
289
be useful, so that check may be removed in future.
291
:param other: The branch to bind to
294
# TODO: jam 20051230 Consider checking if the target is bound
295
# It is debatable whether you should be able to bind to
296
# a branch which is itself bound.
297
# Committing is obviously forbidden,
298
# but binding itself may not be.
299
# Since we *have* to check at commit time, we don't
300
# *need* to check here
302
# we want to raise diverged if:
303
# last_rev is not in the other_last_rev history, AND
304
# other_last_rev is not in our history, and do it without pulling
306
self.set_bound_location(other.base)
308
def get_bound_location(self):
310
return self._transport.get_bytes('bound')[:-1]
311
except errors.NoSuchFile:
315
def get_master_branch(self, possible_transports=None):
316
"""Return the branch we are bound to.
318
:return: Either a Branch, or None
320
if self._master_branch_cache is None:
321
self._master_branch_cache = self._get_master_branch(
323
return self._master_branch_cache
325
def _get_master_branch(self, possible_transports):
326
bound_loc = self.get_bound_location()
330
return Branch.open(bound_loc,
331
possible_transports=possible_transports)
332
except (errors.NotBranchError, errors.ConnectionError) as e:
333
raise errors.BoundBranchConnectionFailure(
337
def set_bound_location(self, location):
338
"""Set the target where this branch is bound to.
340
:param location: URL to the target branch
342
self._master_branch_cache = None
344
self._transport.put_bytes('bound', location+'\n',
345
mode=self.bzrdir._get_file_mode())
348
self._transport.delete('bound')
349
except errors.NoSuchFile:
354
def update(self, possible_transports=None):
355
"""Synchronise this branch with the master branch if any.
357
:return: None or the last_revision that was pivoted out during the
360
master = self.get_master_branch(possible_transports)
361
if master is not None:
362
old_tip = _mod_revision.ensure_null(self.last_revision())
363
self.pull(master, overwrite=True)
364
if self.repository.get_graph().is_ancestor(old_tip,
365
_mod_revision.ensure_null(self.last_revision())):
370
def _read_last_revision_info(self):
371
revision_string = self._transport.get_bytes('last-revision')
372
revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
373
revision_id = cache_utf8.get_cached_utf8(revision_id)
375
return revno, revision_id
377
def _write_last_revision_info(self, revno, revision_id):
378
"""Simply write out the revision id, with no checks.
380
Use set_last_revision_info to perform this safely.
382
Does not update the revision_history cache.
384
revision_id = _mod_revision.ensure_null(revision_id)
385
out_string = '%d %s\n' % (revno, revision_id)
386
self._transport.put_bytes('last-revision', out_string,
387
mode=self.bzrdir._get_file_mode())
390
def update_feature_flags(self, updated_flags):
391
"""Update the feature flags for this branch.
393
:param updated_flags: Dictionary mapping feature names to necessities
394
A necessity can be None to indicate the feature should be removed
396
self._format._update_feature_flags(updated_flags)
397
self.control_transport.put_bytes('format', self._format.as_string())
400
class BzrBranch8(BzrBranch):
401
"""A branch that stores tree-reference locations."""
403
def _open_hook(self, possible_transports=None):
404
if self._ignore_fallbacks:
406
if possible_transports is None:
407
possible_transports = [self.bzrdir.root_transport]
409
url = self.get_stacked_on_url()
410
except (errors.UnstackableRepositoryFormat, errors.NotStacked,
411
errors.UnstackableBranchFormat):
414
for hook in Branch.hooks['transform_fallback_location']:
415
url = hook(self, url)
417
hook_name = Branch.hooks.get_hook_name(hook)
418
raise AssertionError(
419
"'transform_fallback_location' hook %s returned "
420
"None, not a URL." % hook_name)
421
self._activate_fallback_location(url,
422
possible_transports=possible_transports)
424
def __init__(self, *args, **kwargs):
425
self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
426
super(BzrBranch8, self).__init__(*args, **kwargs)
427
self._last_revision_info_cache = None
428
self._reference_info = None
430
def _clear_cached_state(self):
431
super(BzrBranch8, self)._clear_cached_state()
432
self._last_revision_info_cache = None
433
self._reference_info = None
435
def _check_history_violation(self, revision_id):
436
current_revid = self.last_revision()
437
last_revision = _mod_revision.ensure_null(current_revid)
438
if _mod_revision.is_null(last_revision):
440
graph = self.repository.get_graph()
441
for lh_ancestor in graph.iter_lefthand_ancestry(revision_id):
442
if lh_ancestor == current_revid:
444
raise errors.AppendRevisionsOnlyViolation(self.user_url)
446
def _gen_revision_history(self):
447
"""Generate the revision history from last revision
449
last_revno, last_revision = self.last_revision_info()
450
self._extend_partial_history(stop_index=last_revno-1)
451
return list(reversed(self._partial_revision_history_cache))
454
def _set_parent_location(self, url):
455
"""Set the parent branch"""
456
self._set_config_location('parent_location', url, make_relative=True)
459
def _get_parent_location(self):
460
"""Set the parent branch"""
461
return self._get_config_location('parent_location')
464
def _set_all_reference_info(self, info_dict):
465
"""Replace all reference info stored in a branch.
467
:param info_dict: A dict of {file_id: (tree_path, branch_location)}
470
writer = rio.RioWriter(s)
471
for key, (tree_path, branch_location) in info_dict.iteritems():
472
stanza = rio.Stanza(file_id=key, tree_path=tree_path,
473
branch_location=branch_location)
474
writer.write_stanza(stanza)
475
self._transport.put_bytes('references', s.getvalue())
476
self._reference_info = info_dict
479
def _get_all_reference_info(self):
480
"""Return all the reference info stored in a branch.
482
:return: A dict of {file_id: (tree_path, branch_location)}
484
if self._reference_info is not None:
485
return self._reference_info
486
rio_file = self._transport.get('references')
488
stanzas = rio.read_stanzas(rio_file)
489
info_dict = dict((s['file_id'], (s['tree_path'],
490
s['branch_location'])) for s in stanzas)
493
self._reference_info = info_dict
496
def set_reference_info(self, file_id, tree_path, branch_location):
497
"""Set the branch location to use for a tree reference.
499
:param file_id: The file-id of the tree reference.
500
:param tree_path: The path of the tree reference in the tree.
501
:param branch_location: The location of the branch to retrieve tree
504
info_dict = self._get_all_reference_info()
505
info_dict[file_id] = (tree_path, branch_location)
506
if None in (tree_path, branch_location):
507
if tree_path is not None:
508
raise ValueError('tree_path must be None when branch_location'
510
if branch_location is not None:
511
raise ValueError('branch_location must be None when tree_path'
513
del info_dict[file_id]
514
self._set_all_reference_info(info_dict)
516
def get_reference_info(self, file_id):
517
"""Get the tree_path and branch_location for a tree reference.
519
:return: a tuple of (tree_path, branch_location)
521
return self._get_all_reference_info().get(file_id, (None, None))
523
def reference_parent(self, file_id, path, possible_transports=None):
524
"""Return the parent branch for a tree-reference file_id.
526
:param file_id: The file_id of the tree reference
527
:param path: The path of the file_id in the tree
528
:return: A branch associated with the file_id
530
branch_location = self.get_reference_info(file_id)[1]
531
if branch_location is None:
532
return Branch.reference_parent(self, file_id, path,
534
branch_location = urlutils.join(self.user_url, branch_location)
535
return Branch.open(branch_location,
536
possible_transports=possible_transports)
538
def set_push_location(self, location):
539
"""See Branch.set_push_location."""
540
self._set_config_location('push_location', location)
542
def set_bound_location(self, location):
543
"""See Branch.set_push_location."""
544
self._master_branch_cache = None
546
conf = self.get_config_stack()
548
if not conf.get('bound'):
551
conf.set('bound', 'False')
554
self._set_config_location('bound_location', location,
556
conf.set('bound', 'True')
559
def _get_bound_location(self, bound):
560
"""Return the bound location in the config file.
562
Return None if the bound parameter does not match"""
563
conf = self.get_config_stack()
564
if conf.get('bound') != bound:
566
return self._get_config_location('bound_location', config=conf)
568
def get_bound_location(self):
569
"""See Branch.get_bound_location."""
570
return self._get_bound_location(True)
572
def get_old_bound_location(self):
573
"""See Branch.get_old_bound_location"""
574
return self._get_bound_location(False)
576
def get_stacked_on_url(self):
577
# you can always ask for the URL; but you might not be able to use it
578
# if the repo can't support stacking.
579
## self._check_stackable_repo()
580
# stacked_on_location is only ever defined in branch.conf, so don't
581
# waste effort reading the whole stack of config files.
582
conf = _mod_config.BranchOnlyStack(self)
583
stacked_url = self._get_config_location('stacked_on_location',
585
if stacked_url is None:
586
raise errors.NotStacked(self)
587
return stacked_url.encode('utf-8')
590
def get_rev_id(self, revno, history=None):
591
"""Find the revision id of the specified revno."""
593
return _mod_revision.NULL_REVISION
595
last_revno, last_revision_id = self.last_revision_info()
596
if revno <= 0 or revno > last_revno:
597
raise errors.NoSuchRevision(self, revno)
599
if history is not None:
600
return history[revno - 1]
602
index = last_revno - revno
603
if len(self._partial_revision_history_cache) <= index:
604
self._extend_partial_history(stop_index=index)
605
if len(self._partial_revision_history_cache) > index:
606
return self._partial_revision_history_cache[index]
608
raise errors.NoSuchRevision(self, revno)
611
def revision_id_to_revno(self, revision_id):
612
"""Given a revision id, return its revno"""
613
if _mod_revision.is_null(revision_id):
616
index = self._partial_revision_history_cache.index(revision_id)
619
self._extend_partial_history(stop_revision=revision_id)
620
except errors.RevisionNotPresent as e:
621
raise errors.GhostRevisionsHaveNoRevno(revision_id, e.revision_id)
622
index = len(self._partial_revision_history_cache) - 1
624
raise errors.NoSuchRevision(self, revision_id)
625
if self._partial_revision_history_cache[index] != revision_id:
626
raise errors.NoSuchRevision(self, revision_id)
627
return self.revno() - index
630
class BzrBranch7(BzrBranch8):
631
"""A branch with support for a fallback repository."""
633
def set_reference_info(self, file_id, tree_path, branch_location):
634
Branch.set_reference_info(self, file_id, tree_path, branch_location)
636
def get_reference_info(self, file_id):
637
Branch.get_reference_info(self, file_id)
639
def reference_parent(self, file_id, path, possible_transports=None):
640
return Branch.reference_parent(self, file_id, path,
644
class BzrBranch6(BzrBranch7):
645
"""See BzrBranchFormat6 for the capabilities of this branch.
647
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
651
def get_stacked_on_url(self):
652
raise errors.UnstackableBranchFormat(self._format, self.user_url)
655
class BranchFormatMetadir(bzrdir.BzrFormat, BranchFormat):
656
"""Base class for branch formats that live in meta directories.
660
BranchFormat.__init__(self)
661
bzrdir.BzrFormat.__init__(self)
664
def find_format(klass, controldir, name=None):
665
"""Return the format for the branch object in controldir."""
667
transport = controldir.get_branch_transport(None, name=name)
668
except errors.NoSuchFile:
669
raise errors.NotBranchError(path=name, bzrdir=controldir)
671
format_string = transport.get_bytes("format")
672
except errors.NoSuchFile:
673
raise errors.NotBranchError(path=transport.base, bzrdir=controldir)
674
return klass._find_format(format_registry, 'branch', format_string)
676
def _branch_class(self):
677
"""What class to instantiate on open calls."""
678
raise NotImplementedError(self._branch_class)
680
def _get_initial_config(self, append_revisions_only=None):
681
if append_revisions_only:
682
return "append_revisions_only = True\n"
684
# Avoid writing anything if append_revisions_only is disabled,
685
# as that is the default.
688
def _initialize_helper(self, a_bzrdir, utf8_files, name=None,
690
"""Initialize a branch in a control dir, with specified files
692
:param a_bzrdir: The bzrdir to initialize the branch in
693
:param utf8_files: The files to create as a list of
694
(filename, content) tuples
695
:param name: Name of colocated branch to create, if any
696
:return: a branch in this format
699
name = a_bzrdir._get_selected_branch()
700
mutter('creating branch %r in %s', self, a_bzrdir.user_url)
701
branch_transport = a_bzrdir.get_branch_transport(self, name=name)
702
control_files = lockable_files.LockableFiles(branch_transport,
703
'lock', lockdir.LockDir)
704
control_files.create_lock()
705
control_files.lock_write()
707
utf8_files += [('format', self.as_string())]
708
for (filename, content) in utf8_files:
709
branch_transport.put_bytes(
711
mode=a_bzrdir._get_file_mode())
713
control_files.unlock()
714
branch = self.open(a_bzrdir, name, _found=True,
715
found_repository=repository)
716
self._run_post_branch_init_hooks(a_bzrdir, name, branch)
719
def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False,
720
found_repository=None, possible_transports=None):
721
"""See BranchFormat.open()."""
723
name = a_bzrdir._get_selected_branch()
725
format = BranchFormatMetadir.find_format(a_bzrdir, name=name)
726
if format.__class__ != self.__class__:
727
raise AssertionError("wrong format %r found for %r" %
729
transport = a_bzrdir.get_branch_transport(None, name=name)
731
control_files = lockable_files.LockableFiles(transport, 'lock',
733
if found_repository is None:
734
found_repository = a_bzrdir.find_repository()
735
return self._branch_class()(_format=self,
736
_control_files=control_files,
739
_repository=found_repository,
740
ignore_fallbacks=ignore_fallbacks,
741
possible_transports=possible_transports)
742
except errors.NoSuchFile:
743
raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
746
def _matchingbzrdir(self):
747
ret = bzrdir.BzrDirMetaFormat1()
748
ret.set_branch_format(self)
751
def supports_tags(self):
754
def supports_leaving_lock(self):
757
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
759
BranchFormat.check_support_status(self,
760
allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
762
bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
763
recommend_upgrade=recommend_upgrade, basedir=basedir)
766
class BzrBranchFormat6(BranchFormatMetadir):
767
"""Branch format with last-revision and tags.
769
Unlike previous formats, this has no explicit revision history. Instead,
770
this just stores the last-revision, and the left-hand history leading
771
up to there is the history.
773
This format was introduced in bzr 0.15
774
and became the default in 0.91.
777
def _branch_class(self):
781
def get_format_string(cls):
782
"""See BranchFormat.get_format_string()."""
783
return "Bazaar Branch Format 6 (bzr 0.15)\n"
785
def get_format_description(self):
786
"""See BranchFormat.get_format_description()."""
787
return "Branch format 6"
789
def initialize(self, a_bzrdir, name=None, repository=None,
790
append_revisions_only=None):
791
"""Create a branch of this format in a_bzrdir."""
792
utf8_files = [('last-revision', '0 null:\n'),
794
self._get_initial_config(append_revisions_only)),
797
return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
799
def make_tags(self, branch):
800
"""See breezy.branch.BranchFormat.make_tags()."""
801
return _mod_tag.BasicTags(branch)
803
def supports_set_append_revisions_only(self):
807
class BzrBranchFormat8(BranchFormatMetadir):
808
"""Metadir format supporting storing locations of subtree branches."""
810
def _branch_class(self):
814
def get_format_string(cls):
815
"""See BranchFormat.get_format_string()."""
816
return "Bazaar Branch Format 8 (needs bzr 1.15)\n"
818
def get_format_description(self):
819
"""See BranchFormat.get_format_description()."""
820
return "Branch format 8"
822
def initialize(self, a_bzrdir, name=None, repository=None,
823
append_revisions_only=None):
824
"""Create a branch of this format in a_bzrdir."""
825
utf8_files = [('last-revision', '0 null:\n'),
827
self._get_initial_config(append_revisions_only)),
831
return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
833
def make_tags(self, branch):
834
"""See breezy.branch.BranchFormat.make_tags()."""
835
return _mod_tag.BasicTags(branch)
837
def supports_set_append_revisions_only(self):
840
def supports_stacking(self):
843
supports_reference_locations = True
846
class BzrBranchFormat7(BranchFormatMetadir):
847
"""Branch format with last-revision, tags, and a stacked location pointer.
849
The stacked location pointer is passed down to the repository and requires
850
a repository format with supports_external_lookups = True.
852
This format was introduced in bzr 1.6.
855
def initialize(self, a_bzrdir, name=None, repository=None,
856
append_revisions_only=None):
857
"""Create a branch of this format in a_bzrdir."""
858
utf8_files = [('last-revision', '0 null:\n'),
860
self._get_initial_config(append_revisions_only)),
863
return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
865
def _branch_class(self):
869
def get_format_string(cls):
870
"""See BranchFormat.get_format_string()."""
871
return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
873
def get_format_description(self):
874
"""See BranchFormat.get_format_description()."""
875
return "Branch format 7"
877
def supports_set_append_revisions_only(self):
880
def supports_stacking(self):
883
def make_tags(self, branch):
884
"""See breezy.branch.BranchFormat.make_tags()."""
885
return _mod_tag.BasicTags(branch)
887
supports_reference_locations = False
890
class BranchReferenceFormat(BranchFormatMetadir):
891
"""Bzr branch reference format.
893
Branch references are used in implementing checkouts, they
894
act as an alias to the real branch which is at some other url.
902
def get_format_string(cls):
903
"""See BranchFormat.get_format_string()."""
904
return "Bazaar-NG Branch Reference Format 1\n"
906
def get_format_description(self):
907
"""See BranchFormat.get_format_description()."""
908
return "Checkout reference format 1"
910
def get_reference(self, a_bzrdir, name=None):
911
"""See BranchFormat.get_reference()."""
912
transport = a_bzrdir.get_branch_transport(None, name=name)
913
return transport.get_bytes('location')
915
def set_reference(self, a_bzrdir, name, to_branch):
916
"""See BranchFormat.set_reference()."""
917
transport = a_bzrdir.get_branch_transport(None, name=name)
918
location = transport.put_bytes('location', to_branch.base)
920
def initialize(self, a_bzrdir, name=None, target_branch=None,
921
repository=None, append_revisions_only=None):
922
"""Create a branch of this format in a_bzrdir."""
923
if target_branch is None:
924
# this format does not implement branch itself, thus the implicit
925
# creation contract must see it as uninitializable
926
raise errors.UninitializableFormat(self)
927
mutter('creating branch reference in %s', a_bzrdir.user_url)
928
if a_bzrdir._format.fixed_components:
929
raise errors.IncompatibleFormat(self, a_bzrdir._format)
931
name = a_bzrdir._get_selected_branch()
932
branch_transport = a_bzrdir.get_branch_transport(self, name=name)
933
branch_transport.put_bytes('location',
934
target_branch.user_url)
935
branch_transport.put_bytes('format', self.as_string())
936
branch = self.open(a_bzrdir, name, _found=True,
937
possible_transports=[target_branch.bzrdir.root_transport])
938
self._run_post_branch_init_hooks(a_bzrdir, name, branch)
941
def _make_reference_clone_function(format, a_branch):
942
"""Create a clone() routine for a branch dynamically."""
943
def clone(to_bzrdir, revision_id=None,
944
repository_policy=None):
945
"""See Branch.clone()."""
946
return format.initialize(to_bzrdir, target_branch=a_branch)
947
# cannot obey revision_id limits when cloning a reference ...
948
# FIXME RBC 20060210 either nuke revision_id for clone, or
949
# emit some sort of warning/error to the caller ?!
952
def open(self, a_bzrdir, name=None, _found=False, location=None,
953
possible_transports=None, ignore_fallbacks=False,
954
found_repository=None):
955
"""Return the branch that the branch reference in a_bzrdir points at.
957
:param a_bzrdir: A BzrDir that contains a branch.
958
:param name: Name of colocated branch to open, if any
959
:param _found: a private parameter, do not use it. It is used to
960
indicate if format probing has already be done.
961
:param ignore_fallbacks: when set, no fallback branches will be opened
962
(if there are any). Default is to open fallbacks.
963
:param location: The location of the referenced branch. If
964
unspecified, this will be determined from the branch reference in
966
:param possible_transports: An optional reusable transports list.
969
name = a_bzrdir._get_selected_branch()
971
format = BranchFormatMetadir.find_format(a_bzrdir, name=name)
972
if format.__class__ != self.__class__:
973
raise AssertionError("wrong format %r found for %r" %
976
location = self.get_reference(a_bzrdir, name)
977
real_bzrdir = controldir.ControlDir.open(
978
location, possible_transports=possible_transports)
979
result = real_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks,
980
possible_transports=possible_transports)
981
# this changes the behaviour of result.clone to create a new reference
982
# rather than a copy of the content of the branch.
983
# I did not use a proxy object because that needs much more extensive
984
# testing, and we are only changing one behaviour at the moment.
985
# If we decide to alter more behaviours - i.e. the implicit nickname
986
# then this should be refactored to introduce a tested proxy branch
987
# and a subclass of that for use in overriding clone() and ....
989
result.clone = self._make_reference_clone_function(result)
993
class Converter5to6(object):
994
"""Perform an in-place upgrade of format 5 to format 6"""
996
def convert(self, branch):
997
# Data for 5 and 6 can peacefully coexist.
998
format = BzrBranchFormat6()
999
new_branch = format.open(branch.bzrdir, _found=True)
1001
# Copy source data into target
1002
new_branch._write_last_revision_info(*branch.last_revision_info())
1003
new_branch.lock_write()
1005
new_branch.set_parent(branch.get_parent())
1006
new_branch.set_bound_location(branch.get_bound_location())
1007
new_branch.set_push_location(branch.get_push_location())
1011
# New branch has no tags by default
1012
new_branch.tags._set_tag_dict({})
1014
# Copying done; now update target format
1015
new_branch._transport.put_bytes('format',
1017
mode=new_branch.bzrdir._get_file_mode())
1019
# Clean up old files
1020
new_branch._transport.delete('revision-history')
1024
branch.set_parent(None)
1025
except errors.NoSuchFile:
1027
branch.set_bound_location(None)
1032
class Converter6to7(object):
1033
"""Perform an in-place upgrade of format 6 to format 7"""
1035
def convert(self, branch):
1036
format = BzrBranchFormat7()
1037
branch._set_config_location('stacked_on_location', '')
1038
# update target format
1039
branch._transport.put_bytes('format', format.as_string())
1042
class Converter7to8(object):
1043
"""Perform an in-place upgrade of format 7 to format 8"""
1045
def convert(self, branch):
1046
format = BzrBranchFormat8()
1047
branch._transport.put_bytes('references', '')
1048
# update target format
1049
branch._transport.put_bytes('format', format.as_string())