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
22
from ..lazy_import import lazy_import
23
lazy_import(globals(), """
26
config as _mod_config,
39
revision as _mod_revision,
42
from ..branch import (
45
BranchWriteLockResult,
47
UnstackableBranchFormat,
49
from ..decorators import (
52
from ..lock import _RelockDebugMixin, LogicalLockResult
53
from ..sixish import (
63
class BzrBranch(Branch, _RelockDebugMixin):
64
"""A branch stored in the actual filesystem.
66
Note that it's "local" in the context of the filesystem; it doesn't
67
really matter if it's on an nfs/smb/afs/coda/... share, as long as
68
it's writable, and can be accessed via the normal filesystem API.
70
:ivar _transport: Transport for file operations on this branch's
71
control files, typically pointing to the .bzr/branch directory.
72
:ivar repository: Repository for this branch.
73
:ivar base: The url of the base directory for this branch; the one
74
containing the .bzr directory.
75
:ivar name: Optional colocated branch name as it exists in the control
79
def __init__(self, _format=None,
80
_control_files=None, a_controldir=None, name=None,
81
_repository=None, ignore_fallbacks=False,
82
possible_transports=None):
83
"""Create new branch object at a particular location."""
84
if a_controldir is None:
85
raise ValueError('a_controldir must be supplied')
87
raise ValueError('name must be supplied')
88
self.controldir = a_controldir
89
self._user_transport = self.controldir.transport.clone('..')
91
self._user_transport.set_segment_parameter(
92
"branch", urlutils.escape(name))
93
self._base = self._user_transport.base
95
self._format = _format
96
if _control_files is None:
97
raise ValueError('BzrBranch _control_files is None')
98
self.control_files = _control_files
99
self._transport = _control_files._transport
100
self.repository = _repository
101
self.conf_store = None
102
Branch.__init__(self, possible_transports)
103
self._tags_bytes = None
106
return '%s(%s)' % (self.__class__.__name__, self.user_url)
111
"""Returns the directory containing the control directory."""
114
base = property(_get_base, doc="The URL for the root of this branch.")
117
def user_transport(self):
118
return self._user_transport
120
def _get_config(self):
121
"""Get the concrete config for just the config in this branch.
123
This is not intended for client use; see Branch.get_config for the
128
:return: An object supporting get_option and set_option.
130
return _mod_config.TransportConfig(self._transport, 'branch.conf')
132
def _get_config_store(self):
133
if self.conf_store is None:
134
self.conf_store = _mod_config.BranchStore(self)
135
return self.conf_store
137
def _uncommitted_branch(self):
138
"""Return the branch that may contain uncommitted changes."""
139
master = self.get_master_branch()
140
if master is not None:
145
def store_uncommitted(self, creator):
146
"""Store uncommitted changes from a ShelfCreator.
148
:param creator: The ShelfCreator containing uncommitted changes, or
149
None to delete any stored changes.
150
:raises: ChangesAlreadyStored if the branch already has changes.
152
branch = self._uncommitted_branch()
154
branch._transport.delete('stored-transform')
156
if branch._transport.has('stored-transform'):
157
raise errors.ChangesAlreadyStored
158
transform = BytesIO()
159
creator.write_shelf(transform)
161
branch._transport.put_file('stored-transform', transform)
163
def get_unshelver(self, tree):
164
"""Return a shelf.Unshelver for this branch and tree.
166
:param tree: The tree to use to construct the Unshelver.
167
:return: an Unshelver or None if no changes are stored.
169
branch = self._uncommitted_branch()
171
transform = branch._transport.get('stored-transform')
172
except errors.NoSuchFile:
174
return shelf.Unshelver.from_tree_and_shelf(tree, transform)
177
return self.control_files.is_locked()
179
def lock_write(self, token=None):
180
"""Lock the branch for write operations.
182
:param token: A token to permit reacquiring a previously held and
184
:return: A BranchWriteLockResult.
186
if not self.is_locked():
188
self.repository._warn_if_deprecated(self)
189
self.repository.lock_write()
194
return BranchWriteLockResult(
196
self.control_files.lock_write(token=token))
197
except BaseException:
199
self.repository.unlock()
203
"""Lock the branch for read operations.
205
:return: A breezy.lock.LogicalLockResult.
207
if not self.is_locked():
209
self.repository._warn_if_deprecated(self)
210
self.repository.lock_read()
215
self.control_files.lock_read()
216
return LogicalLockResult(self.unlock)
217
except BaseException:
219
self.repository.unlock()
222
@only_raises(errors.LockNotHeld, errors.LockBroken)
224
if self.control_files._lock_count == 1 and self.conf_store is not None:
225
self.conf_store.save_changes()
227
self.control_files.unlock()
229
if not self.control_files.is_locked():
230
self.repository.unlock()
231
# we just released the lock
232
self._clear_cached_state()
234
def peek_lock_mode(self):
235
if self.control_files._lock_count == 0:
238
return self.control_files._lock_mode
240
def get_physical_lock_status(self):
241
return self.control_files.get_physical_lock_status()
243
def set_last_revision_info(self, revno, revision_id):
244
if not revision_id or not isinstance(revision_id, bytes):
245
raise errors.InvalidRevisionId(
246
revision_id=revision_id, branch=self)
247
revision_id = _mod_revision.ensure_null(revision_id)
248
with self.lock_write():
249
old_revno, old_revid = self.last_revision_info()
250
if self.get_append_revisions_only():
251
self._check_history_violation(revision_id)
252
self._run_pre_change_branch_tip_hooks(revno, revision_id)
253
self._write_last_revision_info(revno, revision_id)
254
self._clear_cached_state()
255
self._last_revision_info_cache = revno, revision_id
256
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
258
def basis_tree(self):
259
"""See Branch.basis_tree."""
260
return self.repository.revision_tree(self.last_revision())
262
def _get_parent_location(self):
263
_locs = ['parent', 'pull', 'x-pull']
266
contents = self._transport.get_bytes(l)
267
except errors.NoSuchFile:
270
return contents.strip(b'\n').decode('utf-8')
273
def get_stacked_on_url(self):
274
raise UnstackableBranchFormat(self._format, self.user_url)
276
def set_push_location(self, location):
277
"""See Branch.set_push_location."""
278
self.get_config().set_user_option(
279
'push_location', location,
280
store=_mod_config.STORE_LOCATION_NORECURSE)
282
def _set_parent_location(self, url):
284
self._transport.delete('parent')
286
if isinstance(url, text_type):
287
url = url.encode('utf-8')
288
self._transport.put_bytes('parent', url + b'\n',
289
mode=self.controldir._get_file_mode())
292
"""If bound, unbind"""
293
with self.lock_write():
294
return self.set_bound_location(None)
296
def bind(self, other):
297
"""Bind this branch to the branch other.
299
This does not push or pull data between the branches, though it does
300
check for divergence to raise an error when the branches are not
301
either the same, or one a prefix of the other. That behaviour may not
302
be useful, so that check may be removed in future.
304
:param other: The branch to bind to
307
# TODO: jam 20051230 Consider checking if the target is bound
308
# It is debatable whether you should be able to bind to
309
# a branch which is itself bound.
310
# Committing is obviously forbidden,
311
# but binding itself may not be.
312
# Since we *have* to check at commit time, we don't
313
# *need* to check here
315
# we want to raise diverged if:
316
# last_rev is not in the other_last_rev history, AND
317
# other_last_rev is not in our history, and do it without pulling
319
with self.lock_write():
320
self.set_bound_location(other.base)
322
def get_bound_location(self):
324
return self._transport.get_bytes('bound')[:-1].decode('utf-8')
325
except errors.NoSuchFile:
328
def get_master_branch(self, possible_transports=None):
329
"""Return the branch we are bound to.
331
:return: Either a Branch, or None
333
with self.lock_read():
334
if self._master_branch_cache is None:
335
self._master_branch_cache = self._get_master_branch(
337
return self._master_branch_cache
339
def _get_master_branch(self, possible_transports):
340
bound_loc = self.get_bound_location()
344
return Branch.open(bound_loc,
345
possible_transports=possible_transports)
346
except (errors.NotBranchError, errors.ConnectionError) as e:
347
raise errors.BoundBranchConnectionFailure(
350
def set_bound_location(self, location):
351
"""Set the target where this branch is bound to.
353
:param location: URL to the target branch
355
with self.lock_write():
356
self._master_branch_cache = None
358
self._transport.put_bytes(
359
'bound', location.encode('utf-8') + b'\n',
360
mode=self.controldir._get_file_mode())
363
self._transport.delete('bound')
364
except errors.NoSuchFile:
368
def update(self, possible_transports=None):
369
"""Synchronise this branch with the master branch if any.
371
:return: None or the last_revision that was pivoted out during the
374
with self.lock_write():
375
master = self.get_master_branch(possible_transports)
376
if master is not None:
377
old_tip = _mod_revision.ensure_null(self.last_revision())
378
self.pull(master, overwrite=True)
379
if self.repository.get_graph().is_ancestor(
380
old_tip, _mod_revision.ensure_null(
381
self.last_revision())):
386
def _read_last_revision_info(self):
387
revision_string = self._transport.get_bytes('last-revision')
388
revno, revision_id = revision_string.rstrip(b'\n').split(b' ', 1)
389
revision_id = cache_utf8.get_cached_utf8(revision_id)
391
return revno, revision_id
393
def _write_last_revision_info(self, revno, revision_id):
394
"""Simply write out the revision id, with no checks.
396
Use set_last_revision_info to perform this safely.
398
Does not update the revision_history cache.
400
revision_id = _mod_revision.ensure_null(revision_id)
401
out_string = b'%d %s\n' % (revno, revision_id)
402
self._transport.put_bytes('last-revision', out_string,
403
mode=self.controldir._get_file_mode())
405
def update_feature_flags(self, updated_flags):
406
"""Update the feature flags for this branch.
408
:param updated_flags: Dictionary mapping feature names to necessities
409
A necessity can be None to indicate the feature should be removed
411
with self.lock_write():
412
self._format._update_feature_flags(updated_flags)
413
self.control_transport.put_bytes(
414
'format', self._format.as_string())
416
def _get_tags_bytes(self):
417
"""Get the bytes of a serialised tags dict.
419
Note that not all branches support tags, nor do all use the same tags
420
logic: this method is specific to BasicTags. Other tag implementations
421
may use the same method name and behave differently, safely, because
422
of the double-dispatch via
423
format.make_tags->tags_instance->get_tags_dict.
425
:return: The bytes of the tags file.
426
:seealso: Branch._set_tags_bytes.
428
with self.lock_read():
429
if self._tags_bytes is None:
430
self._tags_bytes = self._transport.get_bytes('tags')
431
return self._tags_bytes
433
def _set_tags_bytes(self, bytes):
434
"""Mirror method for _get_tags_bytes.
436
:seealso: Branch._get_tags_bytes.
438
with self.lock_write():
439
self._tags_bytes = bytes
440
return self._transport.put_bytes('tags', bytes)
442
def _clear_cached_state(self):
443
super(BzrBranch, self)._clear_cached_state()
444
self._tags_bytes = None
446
def reconcile(self, thorough=True):
447
"""Make sure the data stored in this branch is consistent."""
448
from .reconcile import BranchReconciler
449
with self.lock_write():
450
reconciler = BranchReconciler(self, thorough=thorough)
451
return reconciler.reconcile()
453
def set_reference_info(self, file_id, branch_location, path=None):
454
"""Set the branch location to use for a tree reference."""
455
raise errors.UnsupportedOperation(self.set_reference_info, self)
457
def get_reference_info(self, file_id, path=None):
458
"""Get the tree_path and branch_location for a tree reference."""
459
raise errors.UnsupportedOperation(self.get_reference_info, self)
461
def reference_parent(self, file_id, path, possible_transports=None):
462
"""Return the parent branch for a tree-reference.
464
:param path: The path of the nested tree in the tree
465
:return: A branch associated with the nested tree
468
branch_location = self.get_reference_info(file_id)[0]
469
except errors.UnsupportedOperation:
470
branch_location = None
471
if branch_location is None:
473
return Branch.open_from_transport(
474
self.controldir.root_transport.clone(path),
475
possible_transports=possible_transports)
476
except errors.NotBranchError:
480
urlutils.strip_segment_parameters(self.user_url), branch_location),
481
possible_transports=possible_transports)
484
class BzrBranch8(BzrBranch):
485
"""A branch that stores tree-reference locations."""
487
def _open_hook(self, possible_transports=None):
488
if self._ignore_fallbacks:
490
if possible_transports is None:
491
possible_transports = [self.controldir.root_transport]
493
url = self.get_stacked_on_url()
494
except (errors.UnstackableRepositoryFormat, errors.NotStacked,
495
UnstackableBranchFormat):
498
for hook in Branch.hooks['transform_fallback_location']:
499
url = hook(self, url)
501
hook_name = Branch.hooks.get_hook_name(hook)
502
raise AssertionError(
503
"'transform_fallback_location' hook %s returned "
504
"None, not a URL." % hook_name)
505
self._activate_fallback_location(
506
url, possible_transports=possible_transports)
508
def __init__(self, *args, **kwargs):
509
self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
510
super(BzrBranch8, self).__init__(*args, **kwargs)
511
self._last_revision_info_cache = None
512
self._reference_info = None
514
def _clear_cached_state(self):
515
super(BzrBranch8, self)._clear_cached_state()
516
self._last_revision_info_cache = None
517
self._reference_info = None
519
def _check_history_violation(self, revision_id):
520
current_revid = self.last_revision()
521
last_revision = _mod_revision.ensure_null(current_revid)
522
if _mod_revision.is_null(last_revision):
524
graph = self.repository.get_graph()
525
for lh_ancestor in graph.iter_lefthand_ancestry(revision_id):
526
if lh_ancestor == current_revid:
528
raise errors.AppendRevisionsOnlyViolation(self.user_url)
530
def _gen_revision_history(self):
531
"""Generate the revision history from last revision
533
last_revno, last_revision = self.last_revision_info()
534
self._extend_partial_history(stop_index=last_revno - 1)
535
return list(reversed(self._partial_revision_history_cache))
537
def _set_parent_location(self, url):
538
"""Set the parent branch"""
539
with self.lock_write():
540
self._set_config_location(
541
'parent_location', url, make_relative=True)
543
def _get_parent_location(self):
544
"""Set the parent branch"""
545
with self.lock_read():
546
return self._get_config_location('parent_location')
548
def _set_all_reference_info(self, info_dict):
549
"""Replace all reference info stored in a branch.
551
:param info_dict: A dict of {file_id: (branch_location, tree_path)}
554
writer = rio.RioWriter(s)
555
for file_id, (branch_location, tree_path) in viewitems(info_dict):
556
stanza = rio.Stanza(file_id=file_id,
557
branch_location=branch_location)
558
if tree_path is not None:
559
stanza.add('tree_path', tree_path)
560
writer.write_stanza(stanza)
561
with self.lock_write():
562
self._transport.put_bytes('references', s.getvalue())
563
self._reference_info = info_dict
565
def _get_all_reference_info(self):
566
"""Return all the reference info stored in a branch.
568
:return: A dict of {tree_path: (branch_location, file_id)}
570
with self.lock_read():
571
if self._reference_info is not None:
572
return self._reference_info
574
with self._transport.get('references') as rio_file:
575
stanzas = rio.read_stanzas(rio_file)
577
s['file_id'].encode('utf-8'): (
578
s['branch_location'],
579
s['tree_path'] if 'tree_path' in s else None)
581
except errors.NoSuchFile:
583
self._reference_info = info_dict
586
def set_reference_info(self, file_id, branch_location, tree_path=None):
587
"""Set the branch location to use for a tree reference.
589
:param branch_location: The location of the branch to retrieve tree
591
:param file_id: The file-id of the tree reference.
592
:param tree_path: The path of the tree reference in the tree.
594
info_dict = self._get_all_reference_info()
595
info_dict[file_id] = (branch_location, tree_path)
596
if branch_location is None:
597
del info_dict[file_id]
598
self._set_all_reference_info(info_dict)
600
def get_reference_info(self, file_id):
601
"""Get the tree_path and branch_location for a tree reference.
603
:return: a tuple of (branch_location, tree_path)
605
return self._get_all_reference_info().get(file_id, (None, None))
607
def set_push_location(self, location):
608
"""See Branch.set_push_location."""
609
self._set_config_location('push_location', location)
611
def set_bound_location(self, location):
612
"""See Branch.set_push_location."""
613
self._master_branch_cache = None
614
conf = self.get_config_stack()
616
if not conf.get('bound'):
619
conf.set('bound', 'False')
622
self._set_config_location('bound_location', location,
624
conf.set('bound', 'True')
627
def _get_bound_location(self, bound):
628
"""Return the bound location in the config file.
630
Return None if the bound parameter does not match"""
631
conf = self.get_config_stack()
632
if conf.get('bound') != bound:
634
return self._get_config_location('bound_location', config=conf)
636
def get_bound_location(self):
637
"""See Branch.get_bound_location."""
638
return self._get_bound_location(True)
640
def get_old_bound_location(self):
641
"""See Branch.get_old_bound_location"""
642
return self._get_bound_location(False)
644
def get_stacked_on_url(self):
645
# you can always ask for the URL; but you might not be able to use it
646
# if the repo can't support stacking.
647
# self._check_stackable_repo()
648
# stacked_on_location is only ever defined in branch.conf, so don't
649
# waste effort reading the whole stack of config files.
650
conf = _mod_config.BranchOnlyStack(self)
651
stacked_url = self._get_config_location('stacked_on_location',
653
if stacked_url is None:
654
raise errors.NotStacked(self)
655
# TODO(jelmer): Clean this up for pad.lv/1696545
656
if sys.version_info[0] == 2:
657
return stacked_url.encode('utf-8')
661
def get_rev_id(self, revno, history=None):
662
"""Find the revision id of the specified revno."""
664
return _mod_revision.NULL_REVISION
666
with self.lock_read():
667
last_revno, last_revision_id = self.last_revision_info()
668
if revno <= 0 or revno > last_revno:
669
raise errors.RevnoOutOfBounds(revno, (0, last_revno))
671
if history is not None:
672
return history[revno - 1]
674
index = last_revno - revno
675
if len(self._partial_revision_history_cache) <= index:
676
self._extend_partial_history(stop_index=index)
677
if len(self._partial_revision_history_cache) > index:
678
return self._partial_revision_history_cache[index]
680
raise errors.NoSuchRevision(self, revno)
682
def revision_id_to_revno(self, revision_id):
683
"""Given a revision id, return its revno"""
684
if _mod_revision.is_null(revision_id):
686
with self.lock_read():
688
index = self._partial_revision_history_cache.index(revision_id)
691
self._extend_partial_history(stop_revision=revision_id)
692
except errors.RevisionNotPresent as e:
693
raise errors.GhostRevisionsHaveNoRevno(
694
revision_id, e.revision_id)
695
index = len(self._partial_revision_history_cache) - 1
697
raise errors.NoSuchRevision(self, revision_id)
698
if self._partial_revision_history_cache[index] != revision_id:
699
raise errors.NoSuchRevision(self, revision_id)
700
return self.revno() - index
703
class BzrBranch7(BzrBranch8):
704
"""A branch with support for a fallback repository."""
706
def set_reference_info(self, file_id, branch_location, tree_path=None):
707
super(BzrBranch7, self).set_reference_info(
708
file_id, branch_location, tree_path)
709
format_string = BzrBranchFormat8.get_format_string()
710
mutter('Upgrading branch to format %r', format_string)
711
self._transport.put_bytes('format', format_string)
714
class BzrBranch6(BzrBranch7):
715
"""See BzrBranchFormat6 for the capabilities of this branch.
717
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
721
def get_stacked_on_url(self):
722
raise UnstackableBranchFormat(self._format, self.user_url)
725
class BranchFormatMetadir(bzrdir.BzrFormat, BranchFormat):
726
"""Base class for branch formats that live in meta directories.
730
BranchFormat.__init__(self)
731
bzrdir.BzrFormat.__init__(self)
734
def find_format(klass, controldir, name=None):
735
"""Return the format for the branch object in controldir."""
737
transport = controldir.get_branch_transport(None, name=name)
738
except errors.NoSuchFile:
739
raise errors.NotBranchError(path=name, controldir=controldir)
741
format_string = transport.get_bytes("format")
742
except errors.NoSuchFile:
743
raise errors.NotBranchError(
744
path=transport.base, controldir=controldir)
745
return klass._find_format(format_registry, 'branch', format_string)
747
def _branch_class(self):
748
"""What class to instantiate on open calls."""
749
raise NotImplementedError(self._branch_class)
751
def _get_initial_config(self, append_revisions_only=None):
752
if append_revisions_only:
753
return b"append_revisions_only = True\n"
755
# Avoid writing anything if append_revisions_only is disabled,
756
# as that is the default.
759
def _initialize_helper(self, a_controldir, utf8_files, name=None,
761
"""Initialize a branch in a control dir, with specified files
763
:param a_controldir: The bzrdir to initialize the branch in
764
:param utf8_files: The files to create as a list of
765
(filename, content) tuples
766
:param name: Name of colocated branch to create, if any
767
:return: a branch in this format
770
name = a_controldir._get_selected_branch()
771
mutter('creating branch %r in %s', self, a_controldir.user_url)
772
branch_transport = a_controldir.get_branch_transport(self, name=name)
773
control_files = lockable_files.LockableFiles(branch_transport,
774
'lock', lockdir.LockDir)
775
control_files.create_lock()
776
control_files.lock_write()
778
utf8_files += [('format', self.as_string())]
779
for (filename, content) in utf8_files:
780
branch_transport.put_bytes(
782
mode=a_controldir._get_file_mode())
784
control_files.unlock()
785
branch = self.open(a_controldir, name, _found=True,
786
found_repository=repository)
787
self._run_post_branch_init_hooks(a_controldir, name, branch)
790
def open(self, a_controldir, name=None, _found=False,
791
ignore_fallbacks=False, found_repository=None,
792
possible_transports=None):
793
"""See BranchFormat.open()."""
795
name = a_controldir._get_selected_branch()
797
format = BranchFormatMetadir.find_format(a_controldir, name=name)
798
if format.__class__ != self.__class__:
799
raise AssertionError("wrong format %r found for %r" %
801
transport = a_controldir.get_branch_transport(None, name=name)
803
control_files = lockable_files.LockableFiles(transport, 'lock',
805
if found_repository is None:
806
found_repository = a_controldir.find_repository()
807
return self._branch_class()(
808
_format=self, _control_files=control_files, name=name,
809
a_controldir=a_controldir, _repository=found_repository,
810
ignore_fallbacks=ignore_fallbacks,
811
possible_transports=possible_transports)
812
except errors.NoSuchFile:
813
raise errors.NotBranchError(
814
path=transport.base, controldir=a_controldir)
817
def _matchingcontroldir(self):
818
ret = bzrdir.BzrDirMetaFormat1()
819
ret.set_branch_format(self)
822
def supports_tags(self):
825
def supports_leaving_lock(self):
828
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
830
BranchFormat.check_support_status(
831
self, allow_unsupported=allow_unsupported,
832
recommend_upgrade=recommend_upgrade, basedir=basedir)
833
bzrdir.BzrFormat.check_support_status(
834
self, allow_unsupported=allow_unsupported,
835
recommend_upgrade=recommend_upgrade, basedir=basedir)
838
class BzrBranchFormat6(BranchFormatMetadir):
839
"""Branch format with last-revision and tags.
841
Unlike previous formats, this has no explicit revision history. Instead,
842
this just stores the last-revision, and the left-hand history leading
843
up to there is the history.
845
This format was introduced in bzr 0.15
846
and became the default in 0.91.
849
def _branch_class(self):
853
def get_format_string(cls):
854
"""See BranchFormat.get_format_string()."""
855
return b"Bazaar Branch Format 6 (bzr 0.15)\n"
857
def get_format_description(self):
858
"""See BranchFormat.get_format_description()."""
859
return "Branch format 6"
861
def initialize(self, a_controldir, name=None, repository=None,
862
append_revisions_only=None):
863
"""Create a branch of this format in a_controldir."""
865
('last-revision', b'0 null:\n'),
866
('branch.conf', self._get_initial_config(append_revisions_only)),
869
return self._initialize_helper(
870
a_controldir, utf8_files, name, repository)
872
def make_tags(self, branch):
873
"""See breezy.branch.BranchFormat.make_tags()."""
874
return _mod_tag.BasicTags(branch)
876
def supports_set_append_revisions_only(self):
879
supports_reference_locations = True
882
class BzrBranchFormat8(BranchFormatMetadir):
883
"""Metadir format supporting storing locations of subtree branches."""
885
def _branch_class(self):
889
def get_format_string(cls):
890
"""See BranchFormat.get_format_string()."""
891
return b"Bazaar Branch Format 8 (needs bzr 1.15)\n"
893
def get_format_description(self):
894
"""See BranchFormat.get_format_description()."""
895
return "Branch format 8"
897
def initialize(self, a_controldir, name=None, repository=None,
898
append_revisions_only=None):
899
"""Create a branch of this format in a_controldir."""
900
utf8_files = [('last-revision', b'0 null:\n'),
902
self._get_initial_config(append_revisions_only)),
906
return self._initialize_helper(
907
a_controldir, utf8_files, name, repository)
909
def make_tags(self, branch):
910
"""See breezy.branch.BranchFormat.make_tags()."""
911
return _mod_tag.BasicTags(branch)
913
def supports_set_append_revisions_only(self):
916
def supports_stacking(self):
919
supports_reference_locations = True
922
class BzrBranchFormat7(BranchFormatMetadir):
923
"""Branch format with last-revision, tags, and a stacked location pointer.
925
The stacked location pointer is passed down to the repository and requires
926
a repository format with supports_external_lookups = True.
928
This format was introduced in bzr 1.6.
931
def initialize(self, a_controldir, name=None, repository=None,
932
append_revisions_only=None):
933
"""Create a branch of this format in a_controldir."""
934
utf8_files = [('last-revision', b'0 null:\n'),
936
self._get_initial_config(append_revisions_only)),
939
return self._initialize_helper(
940
a_controldir, utf8_files, name, repository)
942
def _branch_class(self):
946
def get_format_string(cls):
947
"""See BranchFormat.get_format_string()."""
948
return b"Bazaar Branch Format 7 (needs bzr 1.6)\n"
950
def get_format_description(self):
951
"""See BranchFormat.get_format_description()."""
952
return "Branch format 7"
954
def supports_set_append_revisions_only(self):
957
def supports_stacking(self):
960
def make_tags(self, branch):
961
"""See breezy.branch.BranchFormat.make_tags()."""
962
return _mod_tag.BasicTags(branch)
964
# This is a white lie; as soon as you set a reference location, we upgrade
965
# you to BzrBranchFormat8.
966
supports_reference_locations = True
969
class BranchReferenceFormat(BranchFormatMetadir):
970
"""Bzr branch reference format.
972
Branch references are used in implementing checkouts, they
973
act as an alias to the real branch which is at some other url.
981
def get_format_string(cls):
982
"""See BranchFormat.get_format_string()."""
983
return b"Bazaar-NG Branch Reference Format 1\n"
985
def get_format_description(self):
986
"""See BranchFormat.get_format_description()."""
987
return "Checkout reference format 1"
989
def get_reference(self, a_controldir, name=None):
990
"""See BranchFormat.get_reference()."""
991
transport = a_controldir.get_branch_transport(None, name=name)
992
url = urlutils.strip_segment_parameters(a_controldir.user_url)
993
return urlutils.join(
994
url, transport.get_bytes('location').decode('utf-8'))
996
def _write_reference(self, a_controldir, transport, to_branch):
997
to_url = to_branch.user_url
998
# Ideally, we'd write a relative path here for the benefit of colocated
999
# branches - so that moving a control directory doesn't break
1000
# any references to colocated branches. Unfortunately, bzr
1001
# does not support relative URLs. See pad.lv/1803845 -- jelmer
1002
# to_url = urlutils.relative_url(
1003
# a_controldir.user_url, to_branch.user_url)
1004
transport.put_bytes('location', to_url.encode('utf-8'))
1006
def set_reference(self, a_controldir, name, to_branch):
1007
"""See BranchFormat.set_reference()."""
1008
transport = a_controldir.get_branch_transport(None, name=name)
1009
self._write_reference(a_controldir, transport, to_branch)
1011
def initialize(self, a_controldir, name=None, target_branch=None,
1012
repository=None, append_revisions_only=None):
1013
"""Create a branch of this format in a_controldir."""
1014
if target_branch is None:
1015
# this format does not implement branch itself, thus the implicit
1016
# creation contract must see it as uninitializable
1017
raise errors.UninitializableFormat(self)
1018
mutter('creating branch reference in %s', a_controldir.user_url)
1019
if a_controldir._format.fixed_components:
1020
raise errors.IncompatibleFormat(self, a_controldir._format)
1022
name = a_controldir._get_selected_branch()
1023
branch_transport = a_controldir.get_branch_transport(self, name=name)
1024
self._write_reference(a_controldir, branch_transport, target_branch)
1025
branch_transport.put_bytes('format', self.as_string())
1026
branch = self.open(a_controldir, name, _found=True,
1027
possible_transports=[target_branch.controldir.root_transport])
1028
self._run_post_branch_init_hooks(a_controldir, name, branch)
1031
def _make_reference_clone_function(format, a_branch):
1032
"""Create a clone() routine for a branch dynamically."""
1033
def clone(to_bzrdir, revision_id=None,
1034
repository_policy=None):
1035
"""See Branch.clone()."""
1036
return format.initialize(to_bzrdir, target_branch=a_branch)
1037
# cannot obey revision_id limits when cloning a reference ...
1038
# FIXME RBC 20060210 either nuke revision_id for clone, or
1039
# emit some sort of warning/error to the caller ?!
1042
def open(self, a_controldir, name=None, _found=False, location=None,
1043
possible_transports=None, ignore_fallbacks=False,
1044
found_repository=None):
1045
"""Return the branch that the branch reference in a_controldir points at.
1047
:param a_controldir: A BzrDir that contains a branch.
1048
:param name: Name of colocated branch to open, if any
1049
:param _found: a private parameter, do not use it. It is used to
1050
indicate if format probing has already be done.
1051
:param ignore_fallbacks: when set, no fallback branches will be opened
1052
(if there are any). Default is to open fallbacks.
1053
:param location: The location of the referenced branch. If
1054
unspecified, this will be determined from the branch reference in
1056
:param possible_transports: An optional reusable transports list.
1059
name = a_controldir._get_selected_branch()
1061
format = BranchFormatMetadir.find_format(a_controldir, name=name)
1062
if format.__class__ != self.__class__:
1063
raise AssertionError("wrong format %r found for %r" %
1065
if location is None:
1066
location = self.get_reference(a_controldir, name)
1067
real_bzrdir = controldir.ControlDir.open(
1068
location, possible_transports=possible_transports)
1069
result = real_bzrdir.open_branch(
1070
ignore_fallbacks=ignore_fallbacks,
1071
possible_transports=possible_transports)
1072
# this changes the behaviour of result.clone to create a new reference
1073
# rather than a copy of the content of the branch.
1074
# I did not use a proxy object because that needs much more extensive
1075
# testing, and we are only changing one behaviour at the moment.
1076
# If we decide to alter more behaviours - i.e. the implicit nickname
1077
# then this should be refactored to introduce a tested proxy branch
1078
# and a subclass of that for use in overriding clone() and ....
1080
result.clone = self._make_reference_clone_function(result)
1084
class Converter5to6(object):
1085
"""Perform an in-place upgrade of format 5 to format 6"""
1087
def convert(self, branch):
1088
# Data for 5 and 6 can peacefully coexist.
1089
format = BzrBranchFormat6()
1090
new_branch = format.open(branch.controldir, _found=True)
1092
# Copy source data into target
1093
new_branch._write_last_revision_info(*branch.last_revision_info())
1094
with new_branch.lock_write():
1095
new_branch.set_parent(branch.get_parent())
1096
new_branch.set_bound_location(branch.get_bound_location())
1097
new_branch.set_push_location(branch.get_push_location())
1099
# New branch has no tags by default
1100
new_branch.tags._set_tag_dict({})
1102
# Copying done; now update target format
1103
new_branch._transport.put_bytes(
1104
'format', format.as_string(),
1105
mode=new_branch.controldir._get_file_mode())
1107
# Clean up old files
1108
new_branch._transport.delete('revision-history')
1109
with branch.lock_write():
1111
branch.set_parent(None)
1112
except errors.NoSuchFile:
1114
branch.set_bound_location(None)
1117
class Converter6to7(object):
1118
"""Perform an in-place upgrade of format 6 to format 7"""
1120
def convert(self, branch):
1121
format = BzrBranchFormat7()
1122
branch._set_config_location('stacked_on_location', '')
1123
# update target format
1124
branch._transport.put_bytes('format', format.as_string())
1127
class Converter7to8(object):
1128
"""Perform an in-place upgrade of format 7 to format 8"""
1130
def convert(self, branch):
1131
format = BzrBranchFormat8()
1132
branch._transport.put_bytes('references', b'')
1133
# update target format
1134
branch._transport.put_bytes('format', format.as_string())