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,
40
from ..branch import (
43
BranchWriteLockResult,
45
UnstackableBranchFormat,
47
from ..decorators import (
50
from ..lock import _RelockDebugMixin, LogicalLockResult
51
from ..sixish import (
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_controldir=None, name=None,
78
_repository=None, ignore_fallbacks=False,
79
possible_transports=None):
80
"""Create new branch object at a particular location."""
81
if a_controldir is None:
82
raise ValueError('a_controldir must be supplied')
84
raise ValueError('name must be supplied')
85
self.controldir = a_controldir
86
self._user_transport = self.controldir.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
"""Get the concrete config for just the config in this branch.
119
This is not intended for client use; see Branch.get_config for the
124
:return: An object supporting get_option and set_option.
126
return _mod_config.TransportConfig(self._transport, 'branch.conf')
128
def _get_config_store(self):
129
if self.conf_store is None:
130
self.conf_store = _mod_config.BranchStore(self)
131
return self.conf_store
133
def _uncommitted_branch(self):
134
"""Return the branch that may contain uncommitted changes."""
135
master = self.get_master_branch()
136
if master is not None:
141
def store_uncommitted(self, creator):
142
"""Store uncommitted changes from a ShelfCreator.
144
:param creator: The ShelfCreator containing uncommitted changes, or
145
None to delete any stored changes.
146
:raises: ChangesAlreadyStored if the branch already has changes.
148
branch = self._uncommitted_branch()
150
branch._transport.delete('stored-transform')
152
if branch._transport.has('stored-transform'):
153
raise errors.ChangesAlreadyStored
154
transform = BytesIO()
155
creator.write_shelf(transform)
157
branch._transport.put_file('stored-transform', transform)
159
def get_unshelver(self, tree):
160
"""Return a shelf.Unshelver for this branch and tree.
162
:param tree: The tree to use to construct the Unshelver.
163
:return: an Unshelver or None if no changes are stored.
165
branch = self._uncommitted_branch()
167
transform = branch._transport.get('stored-transform')
168
except errors.NoSuchFile:
170
return shelf.Unshelver.from_tree_and_shelf(tree, transform)
173
return self.control_files.is_locked()
175
def lock_write(self, token=None):
176
"""Lock the branch for write operations.
178
:param token: A token to permit reacquiring a previously held and
180
:return: A BranchWriteLockResult.
182
if not self.is_locked():
184
self.repository._warn_if_deprecated(self)
185
self.repository.lock_write()
190
return BranchWriteLockResult(
192
self.control_files.lock_write(token=token))
195
self.repository.unlock()
199
"""Lock the branch for read operations.
201
:return: A breezy.lock.LogicalLockResult.
203
if not self.is_locked():
205
self.repository._warn_if_deprecated(self)
206
self.repository.lock_read()
211
self.control_files.lock_read()
212
return LogicalLockResult(self.unlock)
215
self.repository.unlock()
218
@only_raises(errors.LockNotHeld, errors.LockBroken)
220
if self.control_files._lock_count == 1 and self.conf_store is not None:
221
self.conf_store.save_changes()
223
self.control_files.unlock()
225
if not self.control_files.is_locked():
226
self.repository.unlock()
227
# we just released the lock
228
self._clear_cached_state()
230
def peek_lock_mode(self):
231
if self.control_files._lock_count == 0:
234
return self.control_files._lock_mode
236
def get_physical_lock_status(self):
237
return self.control_files.get_physical_lock_status()
239
def set_last_revision_info(self, revno, revision_id):
240
if not revision_id or not isinstance(revision_id, bytes):
241
raise errors.InvalidRevisionId(
242
revision_id=revision_id, branch=self)
243
revision_id = _mod_revision.ensure_null(revision_id)
244
with self.lock_write():
245
old_revno, old_revid = self.last_revision_info()
246
if self.get_append_revisions_only():
247
self._check_history_violation(revision_id)
248
self._run_pre_change_branch_tip_hooks(revno, revision_id)
249
self._write_last_revision_info(revno, revision_id)
250
self._clear_cached_state()
251
self._last_revision_info_cache = revno, revision_id
252
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
254
def basis_tree(self):
255
"""See Branch.basis_tree."""
256
return self.repository.revision_tree(self.last_revision())
258
def _get_parent_location(self):
259
_locs = ['parent', 'pull', 'x-pull']
262
return self._transport.get_bytes(l).strip('\n')
263
except errors.NoSuchFile:
267
def get_stacked_on_url(self):
268
raise UnstackableBranchFormat(self._format, self.user_url)
270
def set_push_location(self, location):
271
"""See Branch.set_push_location."""
272
self.get_config().set_user_option(
273
'push_location', location,
274
store=_mod_config.STORE_LOCATION_NORECURSE)
276
def _set_parent_location(self, url):
278
self._transport.delete('parent')
280
self._transport.put_bytes('parent', url + '\n',
281
mode=self.controldir._get_file_mode())
284
"""If bound, unbind"""
285
with self.lock_write():
286
return self.set_bound_location(None)
288
def bind(self, other):
289
"""Bind this branch to the branch other.
291
This does not push or pull data between the branches, though it does
292
check for divergence to raise an error when the branches are not
293
either the same, or one a prefix of the other. That behaviour may not
294
be useful, so that check may be removed in future.
296
:param other: The branch to bind to
299
# TODO: jam 20051230 Consider checking if the target is bound
300
# It is debatable whether you should be able to bind to
301
# a branch which is itself bound.
302
# Committing is obviously forbidden,
303
# but binding itself may not be.
304
# Since we *have* to check at commit time, we don't
305
# *need* to check here
307
# we want to raise diverged if:
308
# last_rev is not in the other_last_rev history, AND
309
# other_last_rev is not in our history, and do it without pulling
311
with self.lock_write():
312
self.set_bound_location(other.base)
314
def get_bound_location(self):
316
return self._transport.get_bytes('bound')[:-1]
317
except errors.NoSuchFile:
320
def get_master_branch(self, possible_transports=None):
321
"""Return the branch we are bound to.
323
:return: Either a Branch, or None
325
with self.lock_read():
326
if self._master_branch_cache is None:
327
self._master_branch_cache = self._get_master_branch(
329
return self._master_branch_cache
331
def _get_master_branch(self, possible_transports):
332
bound_loc = self.get_bound_location()
336
return Branch.open(bound_loc,
337
possible_transports=possible_transports)
338
except (errors.NotBranchError, errors.ConnectionError) as e:
339
raise errors.BoundBranchConnectionFailure(
342
def set_bound_location(self, location):
343
"""Set the target where this branch is bound to.
345
:param location: URL to the target branch
347
with self.lock_write():
348
self._master_branch_cache = None
350
self._transport.put_bytes('bound', location+'\n',
351
mode=self.controldir._get_file_mode())
354
self._transport.delete('bound')
355
except errors.NoSuchFile:
359
def update(self, possible_transports=None):
360
"""Synchronise this branch with the master branch if any.
362
:return: None or the last_revision that was pivoted out during the
365
with self.lock_write():
366
master = self.get_master_branch(possible_transports)
367
if master is not None:
368
old_tip = _mod_revision.ensure_null(self.last_revision())
369
self.pull(master, overwrite=True)
370
if self.repository.get_graph().is_ancestor(old_tip,
371
_mod_revision.ensure_null(self.last_revision())):
376
def _read_last_revision_info(self):
377
revision_string = self._transport.get_bytes('last-revision')
378
revno, revision_id = revision_string.rstrip(b'\n').split(b' ', 1)
379
revision_id = cache_utf8.get_cached_utf8(revision_id)
381
return revno, revision_id
383
def _write_last_revision_info(self, revno, revision_id):
384
"""Simply write out the revision id, with no checks.
386
Use set_last_revision_info to perform this safely.
388
Does not update the revision_history cache.
390
revision_id = _mod_revision.ensure_null(revision_id)
391
out_string = b'%d %s\n' % (revno, revision_id)
392
self._transport.put_bytes('last-revision', out_string,
393
mode=self.controldir._get_file_mode())
395
def update_feature_flags(self, updated_flags):
396
"""Update the feature flags for this branch.
398
:param updated_flags: Dictionary mapping feature names to necessities
399
A necessity can be None to indicate the feature should be removed
401
with self.lock_write():
402
self._format._update_feature_flags(updated_flags)
403
self.control_transport.put_bytes('format', self._format.as_string())
406
class BzrBranch8(BzrBranch):
407
"""A branch that stores tree-reference locations."""
409
def _open_hook(self, possible_transports=None):
410
if self._ignore_fallbacks:
412
if possible_transports is None:
413
possible_transports = [self.controldir.root_transport]
415
url = self.get_stacked_on_url()
416
except (errors.UnstackableRepositoryFormat, errors.NotStacked,
417
UnstackableBranchFormat):
420
for hook in Branch.hooks['transform_fallback_location']:
421
url = hook(self, url)
423
hook_name = Branch.hooks.get_hook_name(hook)
424
raise AssertionError(
425
"'transform_fallback_location' hook %s returned "
426
"None, not a URL." % hook_name)
427
self._activate_fallback_location(url,
428
possible_transports=possible_transports)
430
def __init__(self, *args, **kwargs):
431
self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
432
super(BzrBranch8, self).__init__(*args, **kwargs)
433
self._last_revision_info_cache = None
434
self._reference_info = None
436
def _clear_cached_state(self):
437
super(BzrBranch8, self)._clear_cached_state()
438
self._last_revision_info_cache = None
439
self._reference_info = None
441
def _check_history_violation(self, revision_id):
442
current_revid = self.last_revision()
443
last_revision = _mod_revision.ensure_null(current_revid)
444
if _mod_revision.is_null(last_revision):
446
graph = self.repository.get_graph()
447
for lh_ancestor in graph.iter_lefthand_ancestry(revision_id):
448
if lh_ancestor == current_revid:
450
raise errors.AppendRevisionsOnlyViolation(self.user_url)
452
def _gen_revision_history(self):
453
"""Generate the revision history from last revision
455
last_revno, last_revision = self.last_revision_info()
456
self._extend_partial_history(stop_index=last_revno-1)
457
return list(reversed(self._partial_revision_history_cache))
459
def _set_parent_location(self, url):
460
"""Set the parent branch"""
461
with self.lock_write():
462
self._set_config_location('parent_location', url, make_relative=True)
464
def _get_parent_location(self):
465
"""Set the parent branch"""
466
with self.lock_read():
467
return self._get_config_location('parent_location')
469
def _set_all_reference_info(self, info_dict):
470
"""Replace all reference info stored in a branch.
472
:param info_dict: A dict of {file_id: (tree_path, branch_location)}
475
writer = rio.RioWriter(s)
476
for key, (tree_path, branch_location) in viewitems(info_dict):
477
stanza = rio.Stanza(file_id=key, tree_path=tree_path,
478
branch_location=branch_location)
479
writer.write_stanza(stanza)
480
with self.lock_write():
481
self._transport.put_bytes('references', s.getvalue())
482
self._reference_info = info_dict
484
def _get_all_reference_info(self):
485
"""Return all the reference info stored in a branch.
487
:return: A dict of {file_id: (tree_path, branch_location)}
489
with self.lock_read():
490
if self._reference_info is not None:
491
return self._reference_info
492
rio_file = self._transport.get('references')
494
stanzas = rio.read_stanzas(rio_file)
495
info_dict = dict((s['file_id'], (s['tree_path'],
496
s['branch_location'])) for s in stanzas)
499
self._reference_info = info_dict
502
def set_reference_info(self, file_id, tree_path, branch_location):
503
"""Set the branch location to use for a tree reference.
505
:param file_id: The file-id of the tree reference.
506
:param tree_path: The path of the tree reference in the tree.
507
:param branch_location: The location of the branch to retrieve tree
510
info_dict = self._get_all_reference_info()
511
info_dict[file_id] = (tree_path, branch_location)
512
if None in (tree_path, branch_location):
513
if tree_path is not None:
514
raise ValueError('tree_path must be None when branch_location'
516
if branch_location is not None:
517
raise ValueError('branch_location must be None when tree_path'
519
del info_dict[file_id]
520
self._set_all_reference_info(info_dict)
522
def get_reference_info(self, file_id):
523
"""Get the tree_path and branch_location for a tree reference.
525
:return: a tuple of (tree_path, branch_location)
527
return self._get_all_reference_info().get(file_id, (None, None))
529
def reference_parent(self, file_id, path, possible_transports=None):
530
"""Return the parent branch for a tree-reference file_id.
532
:param file_id: The file_id of the tree reference
533
:param path: The path of the file_id in the tree
534
:return: A branch associated with the file_id
536
branch_location = self.get_reference_info(file_id)[1]
537
if branch_location is None:
538
return Branch.reference_parent(self, file_id, path,
540
branch_location = urlutils.join(self.user_url, branch_location)
541
return Branch.open(branch_location,
542
possible_transports=possible_transports)
544
def set_push_location(self, location):
545
"""See Branch.set_push_location."""
546
self._set_config_location('push_location', location)
548
def set_bound_location(self, location):
549
"""See Branch.set_push_location."""
550
self._master_branch_cache = None
552
conf = self.get_config_stack()
554
if not conf.get('bound'):
557
conf.set('bound', 'False')
560
self._set_config_location('bound_location', location,
562
conf.set('bound', 'True')
565
def _get_bound_location(self, bound):
566
"""Return the bound location in the config file.
568
Return None if the bound parameter does not match"""
569
conf = self.get_config_stack()
570
if conf.get('bound') != bound:
572
return self._get_config_location('bound_location', config=conf)
574
def get_bound_location(self):
575
"""See Branch.get_bound_location."""
576
return self._get_bound_location(True)
578
def get_old_bound_location(self):
579
"""See Branch.get_old_bound_location"""
580
return self._get_bound_location(False)
582
def get_stacked_on_url(self):
583
# you can always ask for the URL; but you might not be able to use it
584
# if the repo can't support stacking.
585
## self._check_stackable_repo()
586
# stacked_on_location is only ever defined in branch.conf, so don't
587
# waste effort reading the whole stack of config files.
588
conf = _mod_config.BranchOnlyStack(self)
589
stacked_url = self._get_config_location('stacked_on_location',
591
if stacked_url is None:
592
raise errors.NotStacked(self)
593
return stacked_url.encode('utf-8')
595
def get_rev_id(self, revno, history=None):
596
"""Find the revision id of the specified revno."""
598
return _mod_revision.NULL_REVISION
600
with self.lock_read():
601
last_revno, last_revision_id = self.last_revision_info()
602
if revno <= 0 or revno > last_revno:
603
raise errors.NoSuchRevision(self, revno)
605
if history is not None:
606
return history[revno - 1]
608
index = last_revno - revno
609
if len(self._partial_revision_history_cache) <= index:
610
self._extend_partial_history(stop_index=index)
611
if len(self._partial_revision_history_cache) > index:
612
return self._partial_revision_history_cache[index]
614
raise errors.NoSuchRevision(self, revno)
616
def revision_id_to_revno(self, revision_id):
617
"""Given a revision id, return its revno"""
618
if _mod_revision.is_null(revision_id):
620
with self.lock_read():
622
index = self._partial_revision_history_cache.index(revision_id)
625
self._extend_partial_history(stop_revision=revision_id)
626
except errors.RevisionNotPresent as e:
627
raise errors.GhostRevisionsHaveNoRevno(
628
revision_id, e.revision_id)
629
index = len(self._partial_revision_history_cache) - 1
631
raise errors.NoSuchRevision(self, revision_id)
632
if self._partial_revision_history_cache[index] != revision_id:
633
raise errors.NoSuchRevision(self, revision_id)
634
return self.revno() - index
637
class BzrBranch7(BzrBranch8):
638
"""A branch with support for a fallback repository."""
640
def set_reference_info(self, file_id, tree_path, branch_location):
641
Branch.set_reference_info(self, file_id, tree_path, branch_location)
643
def get_reference_info(self, file_id):
644
Branch.get_reference_info(self, file_id)
646
def reference_parent(self, file_id, path, possible_transports=None):
647
return Branch.reference_parent(self, file_id, path,
651
class BzrBranch6(BzrBranch7):
652
"""See BzrBranchFormat6 for the capabilities of this branch.
654
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
658
def get_stacked_on_url(self):
659
raise UnstackableBranchFormat(self._format, self.user_url)
662
class BranchFormatMetadir(bzrdir.BzrFormat, BranchFormat):
663
"""Base class for branch formats that live in meta directories.
667
BranchFormat.__init__(self)
668
bzrdir.BzrFormat.__init__(self)
671
def find_format(klass, controldir, name=None):
672
"""Return the format for the branch object in controldir."""
674
transport = controldir.get_branch_transport(None, name=name)
675
except errors.NoSuchFile:
676
raise errors.NotBranchError(path=name, controldir=controldir)
678
format_string = transport.get_bytes("format")
679
# GZ 2017-06-09: Where should format strings get decoded...
680
format_text = format_string.decode("ascii")
681
except errors.NoSuchFile:
682
raise errors.NotBranchError(
683
path=transport.base, controldir=controldir)
684
return klass._find_format(format_registry, 'branch', format_text)
686
def _branch_class(self):
687
"""What class to instantiate on open calls."""
688
raise NotImplementedError(self._branch_class)
690
def _get_initial_config(self, append_revisions_only=None):
691
if append_revisions_only:
692
return b"append_revisions_only = True\n"
694
# Avoid writing anything if append_revisions_only is disabled,
695
# as that is the default.
698
def _initialize_helper(self, a_controldir, utf8_files, name=None,
700
"""Initialize a branch in a control dir, with specified files
702
:param a_controldir: The bzrdir to initialize the branch in
703
:param utf8_files: The files to create as a list of
704
(filename, content) tuples
705
:param name: Name of colocated branch to create, if any
706
:return: a branch in this format
709
name = a_controldir._get_selected_branch()
710
mutter('creating branch %r in %s', self, a_controldir.user_url)
711
branch_transport = a_controldir.get_branch_transport(self, name=name)
712
control_files = lockable_files.LockableFiles(branch_transport,
713
'lock', lockdir.LockDir)
714
control_files.create_lock()
715
control_files.lock_write()
717
utf8_files += [('format', self.as_string())]
718
for (filename, content) in utf8_files:
719
branch_transport.put_bytes(
721
mode=a_controldir._get_file_mode())
723
control_files.unlock()
724
branch = self.open(a_controldir, name, _found=True,
725
found_repository=repository)
726
self._run_post_branch_init_hooks(a_controldir, name, branch)
729
def open(self, a_controldir, name=None, _found=False, ignore_fallbacks=False,
730
found_repository=None, possible_transports=None):
731
"""See BranchFormat.open()."""
733
name = a_controldir._get_selected_branch()
735
format = BranchFormatMetadir.find_format(a_controldir, name=name)
736
if format.__class__ != self.__class__:
737
raise AssertionError("wrong format %r found for %r" %
739
transport = a_controldir.get_branch_transport(None, name=name)
741
control_files = lockable_files.LockableFiles(transport, 'lock',
743
if found_repository is None:
744
found_repository = a_controldir.find_repository()
745
return self._branch_class()(_format=self,
746
_control_files=control_files,
748
a_controldir=a_controldir,
749
_repository=found_repository,
750
ignore_fallbacks=ignore_fallbacks,
751
possible_transports=possible_transports)
752
except errors.NoSuchFile:
753
raise errors.NotBranchError(path=transport.base, controldir=a_controldir)
756
def _matchingcontroldir(self):
757
ret = bzrdir.BzrDirMetaFormat1()
758
ret.set_branch_format(self)
761
def supports_tags(self):
764
def supports_leaving_lock(self):
767
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
769
BranchFormat.check_support_status(self,
770
allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
772
bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
773
recommend_upgrade=recommend_upgrade, basedir=basedir)
776
class BzrBranchFormat6(BranchFormatMetadir):
777
"""Branch format with last-revision and tags.
779
Unlike previous formats, this has no explicit revision history. Instead,
780
this just stores the last-revision, and the left-hand history leading
781
up to there is the history.
783
This format was introduced in bzr 0.15
784
and became the default in 0.91.
787
def _branch_class(self):
791
def get_format_string(cls):
792
"""See BranchFormat.get_format_string()."""
793
return "Bazaar Branch Format 6 (bzr 0.15)\n"
795
def get_format_description(self):
796
"""See BranchFormat.get_format_description()."""
797
return "Branch format 6"
799
def initialize(self, a_controldir, name=None, repository=None,
800
append_revisions_only=None):
801
"""Create a branch of this format in a_controldir."""
802
utf8_files = [('last-revision', '0 null:\n'),
804
self._get_initial_config(append_revisions_only)),
807
return self._initialize_helper(a_controldir, utf8_files, name, repository)
809
def make_tags(self, branch):
810
"""See breezy.branch.BranchFormat.make_tags()."""
811
return _mod_tag.BasicTags(branch)
813
def supports_set_append_revisions_only(self):
817
class BzrBranchFormat8(BranchFormatMetadir):
818
"""Metadir format supporting storing locations of subtree branches."""
820
def _branch_class(self):
824
def get_format_string(cls):
825
"""See BranchFormat.get_format_string()."""
826
return "Bazaar Branch Format 8 (needs bzr 1.15)\n"
828
def get_format_description(self):
829
"""See BranchFormat.get_format_description()."""
830
return "Branch format 8"
832
def initialize(self, a_controldir, name=None, repository=None,
833
append_revisions_only=None):
834
"""Create a branch of this format in a_controldir."""
835
utf8_files = [('last-revision', '0 null:\n'),
837
self._get_initial_config(append_revisions_only)),
841
return self._initialize_helper(a_controldir, utf8_files, name, repository)
843
def make_tags(self, branch):
844
"""See breezy.branch.BranchFormat.make_tags()."""
845
return _mod_tag.BasicTags(branch)
847
def supports_set_append_revisions_only(self):
850
def supports_stacking(self):
853
supports_reference_locations = True
856
class BzrBranchFormat7(BranchFormatMetadir):
857
"""Branch format with last-revision, tags, and a stacked location pointer.
859
The stacked location pointer is passed down to the repository and requires
860
a repository format with supports_external_lookups = True.
862
This format was introduced in bzr 1.6.
865
def initialize(self, a_controldir, name=None, repository=None,
866
append_revisions_only=None):
867
"""Create a branch of this format in a_controldir."""
868
utf8_files = [('last-revision', b'0 null:\n'),
870
self._get_initial_config(append_revisions_only)),
873
return self._initialize_helper(a_controldir, utf8_files, name, repository)
875
def _branch_class(self):
879
def get_format_string(cls):
880
"""See BranchFormat.get_format_string()."""
881
return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
883
def get_format_description(self):
884
"""See BranchFormat.get_format_description()."""
885
return "Branch format 7"
887
def supports_set_append_revisions_only(self):
890
def supports_stacking(self):
893
def make_tags(self, branch):
894
"""See breezy.branch.BranchFormat.make_tags()."""
895
return _mod_tag.BasicTags(branch)
897
supports_reference_locations = False
900
class BranchReferenceFormat(BranchFormatMetadir):
901
"""Bzr branch reference format.
903
Branch references are used in implementing checkouts, they
904
act as an alias to the real branch which is at some other url.
912
def get_format_string(cls):
913
"""See BranchFormat.get_format_string()."""
914
return "Bazaar-NG Branch Reference Format 1\n"
916
def get_format_description(self):
917
"""See BranchFormat.get_format_description()."""
918
return "Checkout reference format 1"
920
def get_reference(self, a_controldir, name=None):
921
"""See BranchFormat.get_reference()."""
922
transport = a_controldir.get_branch_transport(None, name=name)
923
return transport.get_bytes('location')
925
def set_reference(self, a_controldir, name, to_branch):
926
"""See BranchFormat.set_reference()."""
927
transport = a_controldir.get_branch_transport(None, name=name)
928
location = transport.put_bytes('location', to_branch.base)
930
def initialize(self, a_controldir, name=None, target_branch=None,
931
repository=None, append_revisions_only=None):
932
"""Create a branch of this format in a_controldir."""
933
if target_branch is None:
934
# this format does not implement branch itself, thus the implicit
935
# creation contract must see it as uninitializable
936
raise errors.UninitializableFormat(self)
937
mutter('creating branch reference in %s', a_controldir.user_url)
938
if a_controldir._format.fixed_components:
939
raise errors.IncompatibleFormat(self, a_controldir._format)
941
name = a_controldir._get_selected_branch()
942
branch_transport = a_controldir.get_branch_transport(self, name=name)
943
branch_transport.put_bytes('location',
944
target_branch.user_url)
945
branch_transport.put_bytes('format', self.as_string())
946
branch = self.open(a_controldir, name, _found=True,
947
possible_transports=[target_branch.controldir.root_transport])
948
self._run_post_branch_init_hooks(a_controldir, name, branch)
951
def _make_reference_clone_function(format, a_branch):
952
"""Create a clone() routine for a branch dynamically."""
953
def clone(to_bzrdir, revision_id=None,
954
repository_policy=None):
955
"""See Branch.clone()."""
956
return format.initialize(to_bzrdir, target_branch=a_branch)
957
# cannot obey revision_id limits when cloning a reference ...
958
# FIXME RBC 20060210 either nuke revision_id for clone, or
959
# emit some sort of warning/error to the caller ?!
962
def open(self, a_controldir, name=None, _found=False, location=None,
963
possible_transports=None, ignore_fallbacks=False,
964
found_repository=None):
965
"""Return the branch that the branch reference in a_controldir points at.
967
:param a_controldir: A BzrDir that contains a branch.
968
:param name: Name of colocated branch to open, if any
969
:param _found: a private parameter, do not use it. It is used to
970
indicate if format probing has already be done.
971
:param ignore_fallbacks: when set, no fallback branches will be opened
972
(if there are any). Default is to open fallbacks.
973
:param location: The location of the referenced branch. If
974
unspecified, this will be determined from the branch reference in
976
:param possible_transports: An optional reusable transports list.
979
name = a_controldir._get_selected_branch()
981
format = BranchFormatMetadir.find_format(a_controldir, name=name)
982
if format.__class__ != self.__class__:
983
raise AssertionError("wrong format %r found for %r" %
986
location = self.get_reference(a_controldir, name)
987
real_bzrdir = controldir.ControlDir.open(
988
location, possible_transports=possible_transports)
989
result = real_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks,
990
possible_transports=possible_transports)
991
# this changes the behaviour of result.clone to create a new reference
992
# rather than a copy of the content of the branch.
993
# I did not use a proxy object because that needs much more extensive
994
# testing, and we are only changing one behaviour at the moment.
995
# If we decide to alter more behaviours - i.e. the implicit nickname
996
# then this should be refactored to introduce a tested proxy branch
997
# and a subclass of that for use in overriding clone() and ....
999
result.clone = self._make_reference_clone_function(result)
1003
class Converter5to6(object):
1004
"""Perform an in-place upgrade of format 5 to format 6"""
1006
def convert(self, branch):
1007
# Data for 5 and 6 can peacefully coexist.
1008
format = BzrBranchFormat6()
1009
new_branch = format.open(branch.controldir, _found=True)
1011
# Copy source data into target
1012
new_branch._write_last_revision_info(*branch.last_revision_info())
1013
new_branch.lock_write()
1015
new_branch.set_parent(branch.get_parent())
1016
new_branch.set_bound_location(branch.get_bound_location())
1017
new_branch.set_push_location(branch.get_push_location())
1021
# New branch has no tags by default
1022
new_branch.tags._set_tag_dict({})
1024
# Copying done; now update target format
1025
new_branch._transport.put_bytes('format',
1027
mode=new_branch.controldir._get_file_mode())
1029
# Clean up old files
1030
new_branch._transport.delete('revision-history')
1034
branch.set_parent(None)
1035
except errors.NoSuchFile:
1037
branch.set_bound_location(None)
1042
class Converter6to7(object):
1043
"""Perform an in-place upgrade of format 6 to format 7"""
1045
def convert(self, branch):
1046
format = BzrBranchFormat7()
1047
branch._set_config_location('stacked_on_location', '')
1048
# update target format
1049
branch._transport.put_bytes('format', format.as_string())
1052
class Converter7to8(object):
1053
"""Perform an in-place upgrade of format 7 to format 8"""
1055
def convert(self, branch):
1056
format = BzrBranchFormat8()
1057
branch._transport.put_bytes('references', '')
1058
# update target format
1059
branch._transport.put_bytes('format', format.as_string())