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,
32
from breezy.bzr import (
41
revision as _mod_revision,
44
from ..branch import (
47
BranchWriteLockResult,
49
UnstackableBranchFormat,
51
from ..decorators import (
54
from ..lock import _RelockDebugMixin, LogicalLockResult
55
from ..sixish import (
65
class BzrBranch(Branch, _RelockDebugMixin):
66
"""A branch stored in the actual filesystem.
68
Note that it's "local" in the context of the filesystem; it doesn't
69
really matter if it's on an nfs/smb/afs/coda/... share, as long as
70
it's writable, and can be accessed via the normal filesystem API.
72
:ivar _transport: Transport for file operations on this branch's
73
control files, typically pointing to the .bzr/branch directory.
74
:ivar repository: Repository for this branch.
75
:ivar base: The url of the base directory for this branch; the one
76
containing the .bzr directory.
77
:ivar name: Optional colocated branch name as it exists in the control
81
def __init__(self, _format=None,
82
_control_files=None, a_controldir=None, name=None,
83
_repository=None, ignore_fallbacks=False,
84
possible_transports=None):
85
"""Create new branch object at a particular location."""
86
if a_controldir is None:
87
raise ValueError('a_controldir must be supplied')
89
raise ValueError('name must be supplied')
90
self.controldir = a_controldir
91
self._user_transport = self.controldir.transport.clone('..')
93
self._user_transport.set_segment_parameter(
94
"branch", urlutils.escape(name))
95
self._base = self._user_transport.base
97
self._format = _format
98
if _control_files is None:
99
raise ValueError('BzrBranch _control_files is None')
100
self.control_files = _control_files
101
self._transport = _control_files._transport
102
self.repository = _repository
103
self.conf_store = None
104
Branch.__init__(self, possible_transports)
105
self._tags_bytes = None
108
return '%s(%s)' % (self.__class__.__name__, self.user_url)
113
"""Returns the directory containing the control directory."""
116
base = property(_get_base, doc="The URL for the root of this branch.")
119
def user_transport(self):
120
return self._user_transport
122
def _get_config(self):
123
"""Get the concrete config for just the config in this branch.
125
This is not intended for client use; see Branch.get_config for the
130
:return: An object supporting get_option and set_option.
132
return _mod_config.TransportConfig(self._transport, 'branch.conf')
134
def _get_config_store(self):
135
if self.conf_store is None:
136
self.conf_store = _mod_config.BranchStore(self)
137
return self.conf_store
139
def _uncommitted_branch(self):
140
"""Return the branch that may contain uncommitted changes."""
141
master = self.get_master_branch()
142
if master is not None:
147
def store_uncommitted(self, creator):
148
"""Store uncommitted changes from a ShelfCreator.
150
:param creator: The ShelfCreator containing uncommitted changes, or
151
None to delete any stored changes.
152
:raises: ChangesAlreadyStored if the branch already has changes.
154
branch = self._uncommitted_branch()
156
branch._transport.delete('stored-transform')
158
if branch._transport.has('stored-transform'):
159
raise errors.ChangesAlreadyStored
160
transform = BytesIO()
161
creator.write_shelf(transform)
163
branch._transport.put_file('stored-transform', transform)
165
def get_unshelver(self, tree):
166
"""Return a shelf.Unshelver for this branch and tree.
168
:param tree: The tree to use to construct the Unshelver.
169
:return: an Unshelver or None if no changes are stored.
171
branch = self._uncommitted_branch()
173
transform = branch._transport.get('stored-transform')
174
except errors.NoSuchFile:
176
return shelf.Unshelver.from_tree_and_shelf(tree, transform)
179
return self.control_files.is_locked()
181
def lock_write(self, token=None):
182
"""Lock the branch for write operations.
184
:param token: A token to permit reacquiring a previously held and
186
:return: A BranchWriteLockResult.
188
if not self.is_locked():
190
self.repository._warn_if_deprecated(self)
191
self.repository.lock_write()
196
return BranchWriteLockResult(
198
self.control_files.lock_write(token=token))
199
except BaseException:
201
self.repository.unlock()
205
"""Lock the branch for read operations.
207
:return: A breezy.lock.LogicalLockResult.
209
if not self.is_locked():
211
self.repository._warn_if_deprecated(self)
212
self.repository.lock_read()
217
self.control_files.lock_read()
218
return LogicalLockResult(self.unlock)
219
except BaseException:
221
self.repository.unlock()
224
@only_raises(errors.LockNotHeld, errors.LockBroken)
226
if self.control_files._lock_count == 1 and self.conf_store is not None:
227
self.conf_store.save_changes()
229
self.control_files.unlock()
231
if not self.control_files.is_locked():
232
self.repository.unlock()
233
# we just released the lock
234
self._clear_cached_state()
236
def peek_lock_mode(self):
237
if self.control_files._lock_count == 0:
240
return self.control_files._lock_mode
242
def get_physical_lock_status(self):
243
return self.control_files.get_physical_lock_status()
245
def set_last_revision_info(self, revno, revision_id):
246
if not revision_id or not isinstance(revision_id, bytes):
247
raise errors.InvalidRevisionId(
248
revision_id=revision_id, branch=self)
249
revision_id = _mod_revision.ensure_null(revision_id)
250
with self.lock_write():
251
old_revno, old_revid = self.last_revision_info()
252
if self.get_append_revisions_only():
253
self._check_history_violation(revision_id)
254
self._run_pre_change_branch_tip_hooks(revno, revision_id)
255
self._write_last_revision_info(revno, revision_id)
256
self._clear_cached_state()
257
self._last_revision_info_cache = revno, revision_id
258
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
260
def basis_tree(self):
261
"""See Branch.basis_tree."""
262
return self.repository.revision_tree(self.last_revision())
264
def _get_parent_location(self):
265
_locs = ['parent', 'pull', 'x-pull']
268
contents = self._transport.get_bytes(l)
269
except errors.NoSuchFile:
272
return contents.strip(b'\n').decode('utf-8')
275
def get_stacked_on_url(self):
276
raise UnstackableBranchFormat(self._format, self.user_url)
278
def set_push_location(self, location):
279
"""See Branch.set_push_location."""
280
self.get_config().set_user_option(
281
'push_location', location,
282
store=_mod_config.STORE_LOCATION_NORECURSE)
284
def _set_parent_location(self, url):
286
self._transport.delete('parent')
288
if isinstance(url, text_type):
289
url = url.encode('utf-8')
290
self._transport.put_bytes('parent', url + b'\n',
291
mode=self.controldir._get_file_mode())
294
"""If bound, unbind"""
295
with self.lock_write():
296
return self.set_bound_location(None)
298
def bind(self, other):
299
"""Bind this branch to the branch other.
301
This does not push or pull data between the branches, though it does
302
check for divergence to raise an error when the branches are not
303
either the same, or one a prefix of the other. That behaviour may not
304
be useful, so that check may be removed in future.
306
:param other: The branch to bind to
309
# TODO: jam 20051230 Consider checking if the target is bound
310
# It is debatable whether you should be able to bind to
311
# a branch which is itself bound.
312
# Committing is obviously forbidden,
313
# but binding itself may not be.
314
# Since we *have* to check at commit time, we don't
315
# *need* to check here
317
# we want to raise diverged if:
318
# last_rev is not in the other_last_rev history, AND
319
# other_last_rev is not in our history, and do it without pulling
321
with self.lock_write():
322
self.set_bound_location(other.base)
324
def get_bound_location(self):
326
return self._transport.get_bytes('bound')[:-1].decode('utf-8')
327
except errors.NoSuchFile:
330
def get_master_branch(self, possible_transports=None):
331
"""Return the branch we are bound to.
333
:return: Either a Branch, or None
335
with self.lock_read():
336
if self._master_branch_cache is None:
337
self._master_branch_cache = self._get_master_branch(
339
return self._master_branch_cache
341
def _get_master_branch(self, possible_transports):
342
bound_loc = self.get_bound_location()
346
return Branch.open(bound_loc,
347
possible_transports=possible_transports)
348
except (errors.NotBranchError, errors.ConnectionError) as e:
349
raise errors.BoundBranchConnectionFailure(
352
def set_bound_location(self, location):
353
"""Set the target where this branch is bound to.
355
:param location: URL to the target branch
357
with self.lock_write():
358
self._master_branch_cache = None
360
self._transport.put_bytes(
361
'bound', location.encode('utf-8') + b'\n',
362
mode=self.controldir._get_file_mode())
365
self._transport.delete('bound')
366
except errors.NoSuchFile:
370
def update(self, possible_transports=None):
371
"""Synchronise this branch with the master branch if any.
373
:return: None or the last_revision that was pivoted out during the
376
with self.lock_write():
377
master = self.get_master_branch(possible_transports)
378
if master is not None:
379
old_tip = _mod_revision.ensure_null(self.last_revision())
380
self.pull(master, overwrite=True)
381
if self.repository.get_graph().is_ancestor(
382
old_tip, _mod_revision.ensure_null(
383
self.last_revision())):
388
def _read_last_revision_info(self):
389
revision_string = self._transport.get_bytes('last-revision')
390
revno, revision_id = revision_string.rstrip(b'\n').split(b' ', 1)
391
revision_id = cache_utf8.get_cached_utf8(revision_id)
393
return revno, revision_id
395
def _write_last_revision_info(self, revno, revision_id):
396
"""Simply write out the revision id, with no checks.
398
Use set_last_revision_info to perform this safely.
400
Does not update the revision_history cache.
402
revision_id = _mod_revision.ensure_null(revision_id)
403
out_string = b'%d %s\n' % (revno, revision_id)
404
self._transport.put_bytes('last-revision', out_string,
405
mode=self.controldir._get_file_mode())
407
def update_feature_flags(self, updated_flags):
408
"""Update the feature flags for this branch.
410
:param updated_flags: Dictionary mapping feature names to necessities
411
A necessity can be None to indicate the feature should be removed
413
with self.lock_write():
414
self._format._update_feature_flags(updated_flags)
415
self.control_transport.put_bytes(
416
'format', self._format.as_string())
418
def _get_tags_bytes(self):
419
"""Get the bytes of a serialised tags dict.
421
Note that not all branches support tags, nor do all use the same tags
422
logic: this method is specific to BasicTags. Other tag implementations
423
may use the same method name and behave differently, safely, because
424
of the double-dispatch via
425
format.make_tags->tags_instance->get_tags_dict.
427
:return: The bytes of the tags file.
428
:seealso: Branch._set_tags_bytes.
430
with self.lock_read():
431
if self._tags_bytes is None:
432
self._tags_bytes = self._transport.get_bytes('tags')
433
return self._tags_bytes
435
def _set_tags_bytes(self, bytes):
436
"""Mirror method for _get_tags_bytes.
438
:seealso: Branch._get_tags_bytes.
440
with self.lock_write():
441
self._tags_bytes = bytes
442
return self._transport.put_bytes('tags', bytes)
444
def _clear_cached_state(self):
445
super(BzrBranch, self)._clear_cached_state()
446
self._tags_bytes = None
448
def reconcile(self, thorough=True):
449
"""Make sure the data stored in this branch is consistent."""
450
from .reconcile import BranchReconciler
451
with self.lock_write():
452
reconciler = BranchReconciler(self, thorough=thorough)
453
return reconciler.reconcile()
455
def set_reference_info(self, file_id, branch_location, path=None):
456
"""Set the branch location to use for a tree reference."""
457
raise errors.UnsupportedOperation(self.set_reference_info, self)
459
def get_reference_info(self, file_id, path=None):
460
"""Get the tree_path and branch_location for a tree reference."""
461
raise errors.UnsupportedOperation(self.get_reference_info, self)
463
def reference_parent(self, file_id, path, possible_transports=None):
464
"""Return the parent branch for a tree-reference.
466
:param path: The path of the nested tree in the tree
467
:return: A branch associated with the nested tree
470
branch_location = self.get_reference_info(file_id)[0]
471
except errors.UnsupportedOperation:
472
branch_location = None
473
if branch_location is None:
475
return Branch.open_from_transport(
476
self.controldir.root_transport.clone(path),
477
possible_transports=possible_transports)
478
except errors.NotBranchError:
482
urlutils.strip_segment_parameters(self.user_url), branch_location),
483
possible_transports=possible_transports)
486
class BzrBranch8(BzrBranch):
487
"""A branch that stores tree-reference locations."""
489
def _open_hook(self, possible_transports=None):
490
if self._ignore_fallbacks:
492
if possible_transports is None:
493
possible_transports = [self.controldir.root_transport]
495
url = self.get_stacked_on_url()
496
except (errors.UnstackableRepositoryFormat, errors.NotStacked,
497
UnstackableBranchFormat):
500
for hook in Branch.hooks['transform_fallback_location']:
501
url = hook(self, url)
503
hook_name = Branch.hooks.get_hook_name(hook)
504
raise AssertionError(
505
"'transform_fallback_location' hook %s returned "
506
"None, not a URL." % hook_name)
507
self._activate_fallback_location(
508
url, possible_transports=possible_transports)
510
def __init__(self, *args, **kwargs):
511
self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
512
super(BzrBranch8, self).__init__(*args, **kwargs)
513
self._last_revision_info_cache = None
514
self._reference_info = None
516
def _clear_cached_state(self):
517
super(BzrBranch8, self)._clear_cached_state()
518
self._last_revision_info_cache = None
519
self._reference_info = None
521
def _check_history_violation(self, revision_id):
522
current_revid = self.last_revision()
523
last_revision = _mod_revision.ensure_null(current_revid)
524
if _mod_revision.is_null(last_revision):
526
graph = self.repository.get_graph()
527
for lh_ancestor in graph.iter_lefthand_ancestry(revision_id):
528
if lh_ancestor == current_revid:
530
raise errors.AppendRevisionsOnlyViolation(self.user_url)
532
def _gen_revision_history(self):
533
"""Generate the revision history from last revision
535
last_revno, last_revision = self.last_revision_info()
536
self._extend_partial_history(stop_index=last_revno - 1)
537
return list(reversed(self._partial_revision_history_cache))
539
def _set_parent_location(self, url):
540
"""Set the parent branch"""
541
with self.lock_write():
542
self._set_config_location(
543
'parent_location', url, make_relative=True)
545
def _get_parent_location(self):
546
"""Set the parent branch"""
547
with self.lock_read():
548
return self._get_config_location('parent_location')
550
def _set_all_reference_info(self, info_dict):
551
"""Replace all reference info stored in a branch.
553
:param info_dict: A dict of {file_id: (branch_location, tree_path)}
556
writer = rio.RioWriter(s)
557
for file_id, (branch_location, tree_path) in viewitems(info_dict):
558
stanza = rio.Stanza(file_id=file_id,
559
branch_location=branch_location)
560
if tree_path is not None:
561
stanza.add('tree_path', tree_path)
562
writer.write_stanza(stanza)
563
with self.lock_write():
564
self._transport.put_bytes('references', s.getvalue())
565
self._reference_info = info_dict
567
def _get_all_reference_info(self):
568
"""Return all the reference info stored in a branch.
570
:return: A dict of {tree_path: (branch_location, file_id)}
572
with self.lock_read():
573
if self._reference_info is not None:
574
return self._reference_info
576
with self._transport.get('references') as rio_file:
577
stanzas = rio.read_stanzas(rio_file)
579
s['file_id'].encode('utf-8'): (
580
s['branch_location'],
581
s['tree_path'] if 'tree_path' in s else None)
583
except errors.NoSuchFile:
585
self._reference_info = info_dict
588
def set_reference_info(self, file_id, branch_location, tree_path=None):
589
"""Set the branch location to use for a tree reference.
591
:param branch_location: The location of the branch to retrieve tree
593
:param file_id: The file-id of the tree reference.
594
:param tree_path: The path of the tree reference in the tree.
596
info_dict = self._get_all_reference_info()
597
info_dict[file_id] = (branch_location, tree_path)
598
if branch_location is None:
599
del info_dict[file_id]
600
self._set_all_reference_info(info_dict)
602
def get_reference_info(self, file_id):
603
"""Get the tree_path and branch_location for a tree reference.
605
:return: a tuple of (branch_location, tree_path)
607
return self._get_all_reference_info().get(file_id, (None, None))
609
def set_push_location(self, location):
610
"""See Branch.set_push_location."""
611
self._set_config_location('push_location', location)
613
def set_bound_location(self, location):
614
"""See Branch.set_push_location."""
615
self._master_branch_cache = None
616
conf = self.get_config_stack()
618
if not conf.get('bound'):
621
conf.set('bound', 'False')
624
self._set_config_location('bound_location', location,
626
conf.set('bound', 'True')
629
def _get_bound_location(self, bound):
630
"""Return the bound location in the config file.
632
Return None if the bound parameter does not match"""
633
conf = self.get_config_stack()
634
if conf.get('bound') != bound:
636
return self._get_config_location('bound_location', config=conf)
638
def get_bound_location(self):
639
"""See Branch.get_bound_location."""
640
return self._get_bound_location(True)
642
def get_old_bound_location(self):
643
"""See Branch.get_old_bound_location"""
644
return self._get_bound_location(False)
646
def get_stacked_on_url(self):
647
# you can always ask for the URL; but you might not be able to use it
648
# if the repo can't support stacking.
649
# self._check_stackable_repo()
650
# stacked_on_location is only ever defined in branch.conf, so don't
651
# waste effort reading the whole stack of config files.
652
conf = _mod_config.BranchOnlyStack(self)
653
stacked_url = self._get_config_location('stacked_on_location',
655
if stacked_url is None:
656
raise errors.NotStacked(self)
657
# TODO(jelmer): Clean this up for pad.lv/1696545
658
if sys.version_info[0] == 2:
659
return stacked_url.encode('utf-8')
663
def get_rev_id(self, revno, history=None):
664
"""Find the revision id of the specified revno."""
666
return _mod_revision.NULL_REVISION
668
with self.lock_read():
669
last_revno, last_revision_id = self.last_revision_info()
670
if revno <= 0 or revno > last_revno:
671
raise errors.RevnoOutOfBounds(revno, (0, last_revno))
673
if history is not None:
674
return history[revno - 1]
676
index = last_revno - revno
677
if len(self._partial_revision_history_cache) <= index:
678
self._extend_partial_history(stop_index=index)
679
if len(self._partial_revision_history_cache) > index:
680
return self._partial_revision_history_cache[index]
682
raise errors.NoSuchRevision(self, revno)
684
def revision_id_to_revno(self, revision_id):
685
"""Given a revision id, return its revno"""
686
if _mod_revision.is_null(revision_id):
688
with self.lock_read():
690
index = self._partial_revision_history_cache.index(revision_id)
693
self._extend_partial_history(stop_revision=revision_id)
694
except errors.RevisionNotPresent as e:
695
raise errors.GhostRevisionsHaveNoRevno(
696
revision_id, e.revision_id)
697
index = len(self._partial_revision_history_cache) - 1
699
raise errors.NoSuchRevision(self, revision_id)
700
if self._partial_revision_history_cache[index] != revision_id:
701
raise errors.NoSuchRevision(self, revision_id)
702
return self.revno() - index
705
class BzrBranch7(BzrBranch8):
706
"""A branch with support for a fallback repository."""
708
def set_reference_info(self, file_id, branch_location, tree_path=None):
709
super(BzrBranch7, self).set_reference_info(
710
file_id, branch_location, tree_path)
711
format_string = BzrBranchFormat8.get_format_string()
712
mutter('Upgrading branch to format %r', format_string)
713
self._transport.put_bytes('format', format_string)
716
class BzrBranch6(BzrBranch7):
717
"""See BzrBranchFormat6 for the capabilities of this branch.
719
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
723
def get_stacked_on_url(self):
724
raise UnstackableBranchFormat(self._format, self.user_url)
727
class BranchFormatMetadir(bzrdir.BzrFormat, BranchFormat):
728
"""Base class for branch formats that live in meta directories.
732
BranchFormat.__init__(self)
733
bzrdir.BzrFormat.__init__(self)
736
def find_format(klass, controldir, name=None):
737
"""Return the format for the branch object in controldir."""
739
transport = controldir.get_branch_transport(None, name=name)
740
except errors.NoSuchFile:
741
raise errors.NotBranchError(path=name, controldir=controldir)
743
format_string = transport.get_bytes("format")
744
except errors.NoSuchFile:
745
raise errors.NotBranchError(
746
path=transport.base, controldir=controldir)
747
return klass._find_format(format_registry, 'branch', format_string)
749
def _branch_class(self):
750
"""What class to instantiate on open calls."""
751
raise NotImplementedError(self._branch_class)
753
def _get_initial_config(self, append_revisions_only=None):
754
if append_revisions_only:
755
return b"append_revisions_only = True\n"
757
# Avoid writing anything if append_revisions_only is disabled,
758
# as that is the default.
761
def _initialize_helper(self, a_controldir, utf8_files, name=None,
763
"""Initialize a branch in a control dir, with specified files
765
:param a_controldir: The bzrdir to initialize the branch in
766
:param utf8_files: The files to create as a list of
767
(filename, content) tuples
768
:param name: Name of colocated branch to create, if any
769
:return: a branch in this format
772
name = a_controldir._get_selected_branch()
773
mutter('creating branch %r in %s', self, a_controldir.user_url)
774
branch_transport = a_controldir.get_branch_transport(self, name=name)
775
control_files = lockable_files.LockableFiles(branch_transport,
776
'lock', lockdir.LockDir)
777
control_files.create_lock()
778
control_files.lock_write()
780
utf8_files += [('format', self.as_string())]
781
for (filename, content) in utf8_files:
782
branch_transport.put_bytes(
784
mode=a_controldir._get_file_mode())
786
control_files.unlock()
787
branch = self.open(a_controldir, name, _found=True,
788
found_repository=repository)
789
self._run_post_branch_init_hooks(a_controldir, name, branch)
792
def open(self, a_controldir, name=None, _found=False,
793
ignore_fallbacks=False, found_repository=None,
794
possible_transports=None):
795
"""See BranchFormat.open()."""
797
name = a_controldir._get_selected_branch()
799
format = BranchFormatMetadir.find_format(a_controldir, name=name)
800
if format.__class__ != self.__class__:
801
raise AssertionError("wrong format %r found for %r" %
803
transport = a_controldir.get_branch_transport(None, name=name)
805
control_files = lockable_files.LockableFiles(transport, 'lock',
807
if found_repository is None:
808
found_repository = a_controldir.find_repository()
809
return self._branch_class()(
810
_format=self, _control_files=control_files, name=name,
811
a_controldir=a_controldir, _repository=found_repository,
812
ignore_fallbacks=ignore_fallbacks,
813
possible_transports=possible_transports)
814
except errors.NoSuchFile:
815
raise errors.NotBranchError(
816
path=transport.base, controldir=a_controldir)
819
def _matchingcontroldir(self):
820
ret = bzrdir.BzrDirMetaFormat1()
821
ret.set_branch_format(self)
824
def supports_tags(self):
827
def supports_leaving_lock(self):
830
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
832
BranchFormat.check_support_status(
833
self, allow_unsupported=allow_unsupported,
834
recommend_upgrade=recommend_upgrade, basedir=basedir)
835
bzrdir.BzrFormat.check_support_status(
836
self, allow_unsupported=allow_unsupported,
837
recommend_upgrade=recommend_upgrade, basedir=basedir)
840
class BzrBranchFormat6(BranchFormatMetadir):
841
"""Branch format with last-revision and tags.
843
Unlike previous formats, this has no explicit revision history. Instead,
844
this just stores the last-revision, and the left-hand history leading
845
up to there is the history.
847
This format was introduced in bzr 0.15
848
and became the default in 0.91.
851
def _branch_class(self):
855
def get_format_string(cls):
856
"""See BranchFormat.get_format_string()."""
857
return b"Bazaar Branch Format 6 (bzr 0.15)\n"
859
def get_format_description(self):
860
"""See BranchFormat.get_format_description()."""
861
return "Branch format 6"
863
def initialize(self, a_controldir, name=None, repository=None,
864
append_revisions_only=None):
865
"""Create a branch of this format in a_controldir."""
867
('last-revision', b'0 null:\n'),
868
('branch.conf', self._get_initial_config(append_revisions_only)),
871
return self._initialize_helper(
872
a_controldir, utf8_files, name, repository)
874
def make_tags(self, branch):
875
"""See breezy.branch.BranchFormat.make_tags()."""
876
return _mod_tag.BasicTags(branch)
878
def supports_set_append_revisions_only(self):
881
supports_reference_locations = True
884
class BzrBranchFormat8(BranchFormatMetadir):
885
"""Metadir format supporting storing locations of subtree branches."""
887
def _branch_class(self):
891
def get_format_string(cls):
892
"""See BranchFormat.get_format_string()."""
893
return b"Bazaar Branch Format 8 (needs bzr 1.15)\n"
895
def get_format_description(self):
896
"""See BranchFormat.get_format_description()."""
897
return "Branch format 8"
899
def initialize(self, a_controldir, name=None, repository=None,
900
append_revisions_only=None):
901
"""Create a branch of this format in a_controldir."""
902
utf8_files = [('last-revision', b'0 null:\n'),
904
self._get_initial_config(append_revisions_only)),
908
return self._initialize_helper(
909
a_controldir, utf8_files, name, repository)
911
def make_tags(self, branch):
912
"""See breezy.branch.BranchFormat.make_tags()."""
913
return _mod_tag.BasicTags(branch)
915
def supports_set_append_revisions_only(self):
918
def supports_stacking(self):
921
supports_reference_locations = True
924
class BzrBranchFormat7(BranchFormatMetadir):
925
"""Branch format with last-revision, tags, and a stacked location pointer.
927
The stacked location pointer is passed down to the repository and requires
928
a repository format with supports_external_lookups = True.
930
This format was introduced in bzr 1.6.
933
def initialize(self, a_controldir, name=None, repository=None,
934
append_revisions_only=None):
935
"""Create a branch of this format in a_controldir."""
936
utf8_files = [('last-revision', b'0 null:\n'),
938
self._get_initial_config(append_revisions_only)),
941
return self._initialize_helper(
942
a_controldir, utf8_files, name, repository)
944
def _branch_class(self):
948
def get_format_string(cls):
949
"""See BranchFormat.get_format_string()."""
950
return b"Bazaar Branch Format 7 (needs bzr 1.6)\n"
952
def get_format_description(self):
953
"""See BranchFormat.get_format_description()."""
954
return "Branch format 7"
956
def supports_set_append_revisions_only(self):
959
def supports_stacking(self):
962
def make_tags(self, branch):
963
"""See breezy.branch.BranchFormat.make_tags()."""
964
return _mod_tag.BasicTags(branch)
966
# This is a white lie; as soon as you set a reference location, we upgrade
967
# you to BzrBranchFormat8.
968
supports_reference_locations = True
971
class BranchReferenceFormat(BranchFormatMetadir):
972
"""Bzr branch reference format.
974
Branch references are used in implementing checkouts, they
975
act as an alias to the real branch which is at some other url.
983
def get_format_string(cls):
984
"""See BranchFormat.get_format_string()."""
985
return b"Bazaar-NG Branch Reference Format 1\n"
987
def get_format_description(self):
988
"""See BranchFormat.get_format_description()."""
989
return "Checkout reference format 1"
991
def get_reference(self, a_controldir, name=None):
992
"""See BranchFormat.get_reference()."""
993
transport = a_controldir.get_branch_transport(None, name=name)
994
url = urlutils.strip_segment_parameters(a_controldir.user_url)
995
return urlutils.join(
996
url, transport.get_bytes('location').decode('utf-8'))
998
def _write_reference(self, a_controldir, transport, to_branch):
999
to_url = to_branch.user_url
1000
# Ideally, we'd write a relative path here for the benefit of colocated
1001
# branches - so that moving a control directory doesn't break
1002
# any references to colocated branches. Unfortunately, bzr
1003
# does not support relative URLs. See pad.lv/1803845 -- jelmer
1004
# to_url = urlutils.relative_url(
1005
# a_controldir.user_url, to_branch.user_url)
1006
transport.put_bytes('location', to_url.encode('utf-8'))
1008
def set_reference(self, a_controldir, name, to_branch):
1009
"""See BranchFormat.set_reference()."""
1010
transport = a_controldir.get_branch_transport(None, name=name)
1011
self._write_reference(a_controldir, transport, to_branch)
1013
def initialize(self, a_controldir, name=None, target_branch=None,
1014
repository=None, append_revisions_only=None):
1015
"""Create a branch of this format in a_controldir."""
1016
if target_branch is None:
1017
# this format does not implement branch itself, thus the implicit
1018
# creation contract must see it as uninitializable
1019
raise errors.UninitializableFormat(self)
1020
mutter('creating branch reference in %s', a_controldir.user_url)
1021
if a_controldir._format.fixed_components:
1022
raise errors.IncompatibleFormat(self, a_controldir._format)
1024
name = a_controldir._get_selected_branch()
1025
branch_transport = a_controldir.get_branch_transport(self, name=name)
1026
self._write_reference(a_controldir, branch_transport, target_branch)
1027
branch_transport.put_bytes('format', self.as_string())
1028
branch = self.open(a_controldir, name, _found=True,
1029
possible_transports=[target_branch.controldir.root_transport])
1030
self._run_post_branch_init_hooks(a_controldir, name, branch)
1033
def _make_reference_clone_function(format, a_branch):
1034
"""Create a clone() routine for a branch dynamically."""
1035
def clone(to_bzrdir, revision_id=None, repository_policy=None, name=None,
1037
"""See Branch.clone()."""
1038
return format.initialize(to_bzrdir, target_branch=a_branch, name=name)
1039
# cannot obey revision_id limits when cloning a reference ...
1040
# FIXME RBC 20060210 either nuke revision_id for clone, or
1041
# emit some sort of warning/error to the caller ?!
1044
def open(self, a_controldir, name=None, _found=False, location=None,
1045
possible_transports=None, ignore_fallbacks=False,
1046
found_repository=None):
1047
"""Return the branch that the branch reference in a_controldir points at.
1049
:param a_controldir: A BzrDir that contains a branch.
1050
:param name: Name of colocated branch to open, if any
1051
:param _found: a private parameter, do not use it. It is used to
1052
indicate if format probing has already be done.
1053
:param ignore_fallbacks: when set, no fallback branches will be opened
1054
(if there are any). Default is to open fallbacks.
1055
:param location: The location of the referenced branch. If
1056
unspecified, this will be determined from the branch reference in
1058
:param possible_transports: An optional reusable transports list.
1061
name = a_controldir._get_selected_branch()
1063
format = BranchFormatMetadir.find_format(a_controldir, name=name)
1064
if format.__class__ != self.__class__:
1065
raise AssertionError("wrong format %r found for %r" %
1067
if location is None:
1068
location = self.get_reference(a_controldir, name)
1069
real_bzrdir = controldir.ControlDir.open(
1070
location, possible_transports=possible_transports)
1071
result = real_bzrdir.open_branch(
1072
ignore_fallbacks=ignore_fallbacks,
1073
possible_transports=possible_transports)
1074
# this changes the behaviour of result.clone to create a new reference
1075
# rather than a copy of the content of the branch.
1076
# I did not use a proxy object because that needs much more extensive
1077
# testing, and we are only changing one behaviour at the moment.
1078
# If we decide to alter more behaviours - i.e. the implicit nickname
1079
# then this should be refactored to introduce a tested proxy branch
1080
# and a subclass of that for use in overriding clone() and ....
1082
result.clone = self._make_reference_clone_function(result)
1086
class Converter5to6(object):
1087
"""Perform an in-place upgrade of format 5 to format 6"""
1089
def convert(self, branch):
1090
# Data for 5 and 6 can peacefully coexist.
1091
format = BzrBranchFormat6()
1092
new_branch = format.open(branch.controldir, _found=True)
1094
# Copy source data into target
1095
new_branch._write_last_revision_info(*branch.last_revision_info())
1096
with new_branch.lock_write():
1097
new_branch.set_parent(branch.get_parent())
1098
new_branch.set_bound_location(branch.get_bound_location())
1099
new_branch.set_push_location(branch.get_push_location())
1101
# New branch has no tags by default
1102
new_branch.tags._set_tag_dict({})
1104
# Copying done; now update target format
1105
new_branch._transport.put_bytes(
1106
'format', format.as_string(),
1107
mode=new_branch.controldir._get_file_mode())
1109
# Clean up old files
1110
new_branch._transport.delete('revision-history')
1111
with branch.lock_write():
1113
branch.set_parent(None)
1114
except errors.NoSuchFile:
1116
branch.set_bound_location(None)
1119
class Converter6to7(object):
1120
"""Perform an in-place upgrade of format 6 to format 7"""
1122
def convert(self, branch):
1123
format = BzrBranchFormat7()
1124
branch._set_config_location('stacked_on_location', '')
1125
# update target format
1126
branch._transport.put_bytes('format', format.as_string())
1129
class Converter7to8(object):
1130
"""Perform an in-place upgrade of format 7 to format 8"""
1132
def convert(self, branch):
1133
format = BzrBranchFormat8()
1134
branch._transport.put_bytes('references', b'')
1135
# update target format
1136
branch._transport.put_bytes('format', format.as_string())