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 io import BytesIO
21
from ..lazy_import import lazy_import
22
lazy_import(globals(), """
25
config as _mod_config,
31
from breezy.bzr import (
40
revision as _mod_revision,
43
from ..branch import (
46
BranchWriteLockResult,
48
UnstackableBranchFormat,
50
from ..decorators import (
53
from ..lock import _RelockDebugMixin, LogicalLockResult
59
class BzrBranch(Branch, _RelockDebugMixin):
60
"""A branch stored in the actual filesystem.
62
Note that it's "local" in the context of the filesystem; it doesn't
63
really matter if it's on an nfs/smb/afs/coda/... share, as long as
64
it's writable, and can be accessed via the normal filesystem API.
66
:ivar _transport: Transport for file operations on this branch's
67
control files, typically pointing to the .bzr/branch directory.
68
:ivar repository: Repository for this branch.
69
:ivar base: The url of the base directory for this branch; the one
70
containing the .bzr directory.
71
:ivar name: Optional colocated branch name as it exists in the control
75
def __init__(self, _format=None,
76
_control_files=None, a_controldir=None, name=None,
77
_repository=None, ignore_fallbacks=False,
78
possible_transports=None):
79
"""Create new branch object at a particular location."""
80
if a_controldir is None:
81
raise ValueError('a_controldir must be supplied')
83
raise ValueError('name must be supplied')
84
self.controldir = a_controldir
85
self._user_transport = self.controldir.transport.clone('..')
87
self._user_transport.set_segment_parameter(
88
"branch", urlutils.escape(name))
89
self._base = self._user_transport.base
91
self._format = _format
92
if _control_files is None:
93
raise ValueError('BzrBranch _control_files is None')
94
self.control_files = _control_files
95
self._transport = _control_files._transport
96
self.repository = _repository
97
self.conf_store = None
98
Branch.__init__(self, possible_transports)
99
self._tags_bytes = None
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))
193
except BaseException:
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)
213
except BaseException:
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
contents = self._transport.get_bytes(l)
263
except errors.NoSuchFile:
266
return contents.strip(b'\n').decode('utf-8')
269
def get_stacked_on_url(self):
270
raise UnstackableBranchFormat(self._format, self.user_url)
272
def set_push_location(self, location):
273
"""See Branch.set_push_location."""
274
self.get_config().set_user_option(
275
'push_location', location,
276
store=_mod_config.STORE_LOCATION_NORECURSE)
278
def _set_parent_location(self, url):
280
self._transport.delete('parent')
282
if isinstance(url, str):
283
url = url.encode('utf-8')
284
self._transport.put_bytes('parent', url + b'\n',
285
mode=self.controldir._get_file_mode())
288
"""If bound, unbind"""
289
with self.lock_write():
290
return self.set_bound_location(None)
292
def bind(self, other):
293
"""Bind this branch to the branch other.
295
This does not push or pull data between the branches, though it does
296
check for divergence to raise an error when the branches are not
297
either the same, or one a prefix of the other. That behaviour may not
298
be useful, so that check may be removed in future.
300
:param other: The branch to bind to
303
# TODO: jam 20051230 Consider checking if the target is bound
304
# It is debatable whether you should be able to bind to
305
# a branch which is itself bound.
306
# Committing is obviously forbidden,
307
# but binding itself may not be.
308
# Since we *have* to check at commit time, we don't
309
# *need* to check here
311
# we want to raise diverged if:
312
# last_rev is not in the other_last_rev history, AND
313
# other_last_rev is not in our history, and do it without pulling
315
with self.lock_write():
316
self.set_bound_location(other.base)
318
def get_bound_location(self):
320
return self._transport.get_bytes('bound')[:-1].decode('utf-8')
321
except errors.NoSuchFile:
324
def get_master_branch(self, possible_transports=None):
325
"""Return the branch we are bound to.
327
:return: Either a Branch, or None
329
with self.lock_read():
330
if self._master_branch_cache is None:
331
self._master_branch_cache = self._get_master_branch(
333
return self._master_branch_cache
335
def _get_master_branch(self, possible_transports):
336
bound_loc = self.get_bound_location()
340
return Branch.open(bound_loc,
341
possible_transports=possible_transports)
342
except (errors.NotBranchError, errors.ConnectionError) as e:
343
raise errors.BoundBranchConnectionFailure(
346
def set_bound_location(self, location):
347
"""Set the target where this branch is bound to.
349
:param location: URL to the target branch
351
with self.lock_write():
352
self._master_branch_cache = None
354
self._transport.put_bytes(
355
'bound', location.encode('utf-8') + b'\n',
356
mode=self.controldir._get_file_mode())
359
self._transport.delete('bound')
360
except errors.NoSuchFile:
364
def update(self, possible_transports=None):
365
"""Synchronise this branch with the master branch if any.
367
:return: None or the last_revision that was pivoted out during the
370
with self.lock_write():
371
master = self.get_master_branch(possible_transports)
372
if master is not None:
373
old_tip = _mod_revision.ensure_null(self.last_revision())
374
self.pull(master, overwrite=True)
375
if self.repository.get_graph().is_ancestor(
376
old_tip, _mod_revision.ensure_null(
377
self.last_revision())):
382
def _read_last_revision_info(self):
383
revision_string = self._transport.get_bytes('last-revision')
384
revno, revision_id = revision_string.rstrip(b'\n').split(b' ', 1)
385
revision_id = cache_utf8.get_cached_utf8(revision_id)
387
return revno, revision_id
389
def _write_last_revision_info(self, revno, revision_id):
390
"""Simply write out the revision id, with no checks.
392
Use set_last_revision_info to perform this safely.
394
Does not update the revision_history cache.
396
revision_id = _mod_revision.ensure_null(revision_id)
397
out_string = b'%d %s\n' % (revno, revision_id)
398
self._transport.put_bytes('last-revision', out_string,
399
mode=self.controldir._get_file_mode())
401
def update_feature_flags(self, updated_flags):
402
"""Update the feature flags for this branch.
404
:param updated_flags: Dictionary mapping feature names to necessities
405
A necessity can be None to indicate the feature should be removed
407
with self.lock_write():
408
self._format._update_feature_flags(updated_flags)
409
self.control_transport.put_bytes(
410
'format', self._format.as_string())
412
def _get_tags_bytes(self):
413
"""Get the bytes of a serialised tags dict.
415
Note that not all branches support tags, nor do all use the same tags
416
logic: this method is specific to BasicTags. Other tag implementations
417
may use the same method name and behave differently, safely, because
418
of the double-dispatch via
419
format.make_tags->tags_instance->get_tags_dict.
421
:return: The bytes of the tags file.
422
:seealso: Branch._set_tags_bytes.
424
with self.lock_read():
425
if self._tags_bytes is None:
426
self._tags_bytes = self._transport.get_bytes('tags')
427
return self._tags_bytes
429
def _set_tags_bytes(self, bytes):
430
"""Mirror method for _get_tags_bytes.
432
:seealso: Branch._get_tags_bytes.
434
with self.lock_write():
435
self._tags_bytes = bytes
436
return self._transport.put_bytes('tags', bytes)
438
def _clear_cached_state(self):
439
super(BzrBranch, self)._clear_cached_state()
440
self._tags_bytes = None
442
def reconcile(self, thorough=True):
443
"""Make sure the data stored in this branch is consistent."""
444
from .reconcile import BranchReconciler
445
with self.lock_write():
446
reconciler = BranchReconciler(self, thorough=thorough)
447
return reconciler.reconcile()
449
def set_reference_info(self, file_id, branch_location, path=None):
450
"""Set the branch location to use for a tree reference."""
451
raise errors.UnsupportedOperation(self.set_reference_info, self)
453
def get_reference_info(self, file_id, path=None):
454
"""Get the tree_path and branch_location for a tree reference."""
455
raise errors.UnsupportedOperation(self.get_reference_info, self)
457
def reference_parent(self, file_id, path, possible_transports=None):
458
"""Return the parent branch for a tree-reference.
460
:param path: The path of the nested tree in the tree
461
:return: A branch associated with the nested tree
464
branch_location = self.get_reference_info(file_id)[0]
465
except errors.UnsupportedOperation:
466
branch_location = None
467
if branch_location is None:
469
return Branch.open_from_transport(
470
self.controldir.root_transport.clone(path),
471
possible_transports=possible_transports)
472
except errors.NotBranchError:
476
urlutils.strip_segment_parameters(self.user_url), branch_location),
477
possible_transports=possible_transports)
480
class BzrBranch8(BzrBranch):
481
"""A branch that stores tree-reference locations."""
483
def _open_hook(self, possible_transports=None):
484
if self._ignore_fallbacks:
486
if possible_transports is None:
487
possible_transports = [self.controldir.root_transport]
489
url = self.get_stacked_on_url()
490
except (errors.UnstackableRepositoryFormat, errors.NotStacked,
491
UnstackableBranchFormat):
494
for hook in Branch.hooks['transform_fallback_location']:
495
url = hook(self, url)
497
hook_name = Branch.hooks.get_hook_name(hook)
498
raise AssertionError(
499
"'transform_fallback_location' hook %s returned "
500
"None, not a URL." % hook_name)
501
self._activate_fallback_location(
502
url, possible_transports=possible_transports)
504
def __init__(self, *args, **kwargs):
505
self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
506
super(BzrBranch8, self).__init__(*args, **kwargs)
507
self._last_revision_info_cache = None
508
self._reference_info = None
510
def _clear_cached_state(self):
511
super(BzrBranch8, self)._clear_cached_state()
512
self._last_revision_info_cache = None
513
self._reference_info = None
515
def _check_history_violation(self, revision_id):
516
current_revid = self.last_revision()
517
last_revision = _mod_revision.ensure_null(current_revid)
518
if _mod_revision.is_null(last_revision):
520
graph = self.repository.get_graph()
521
for lh_ancestor in graph.iter_lefthand_ancestry(revision_id):
522
if lh_ancestor == current_revid:
524
raise errors.AppendRevisionsOnlyViolation(self.user_url)
526
def _gen_revision_history(self):
527
"""Generate the revision history from last revision
529
last_revno, last_revision = self.last_revision_info()
530
self._extend_partial_history(stop_index=last_revno - 1)
531
return list(reversed(self._partial_revision_history_cache))
533
def _set_parent_location(self, url):
534
"""Set the parent branch"""
535
with self.lock_write():
536
self._set_config_location(
537
'parent_location', url, make_relative=True)
539
def _get_parent_location(self):
540
"""Set the parent branch"""
541
with self.lock_read():
542
return self._get_config_location('parent_location')
544
def _set_all_reference_info(self, info_dict):
545
"""Replace all reference info stored in a branch.
547
:param info_dict: A dict of {file_id: (branch_location, tree_path)}
550
writer = rio.RioWriter(s)
551
for file_id, (branch_location, tree_path) in info_dict.items():
552
stanza = rio.Stanza(file_id=file_id,
553
branch_location=branch_location)
554
if tree_path is not None:
555
stanza.add('tree_path', tree_path)
556
writer.write_stanza(stanza)
557
with self.lock_write():
558
self._transport.put_bytes('references', s.getvalue())
559
self._reference_info = info_dict
561
def _get_all_reference_info(self):
562
"""Return all the reference info stored in a branch.
564
:return: A dict of {tree_path: (branch_location, file_id)}
566
with self.lock_read():
567
if self._reference_info is not None:
568
return self._reference_info
570
with self._transport.get('references') as rio_file:
571
stanzas = rio.read_stanzas(rio_file)
573
s['file_id'].encode('utf-8'): (
574
s['branch_location'],
575
s['tree_path'] if 'tree_path' in s else None)
577
except errors.NoSuchFile:
579
self._reference_info = info_dict
582
def set_reference_info(self, file_id, branch_location, tree_path=None):
583
"""Set the branch location to use for a tree reference.
585
:param branch_location: The location of the branch to retrieve tree
587
:param file_id: The file-id of the tree reference.
588
:param tree_path: The path of the tree reference in the tree.
590
info_dict = self._get_all_reference_info()
591
info_dict[file_id] = (branch_location, tree_path)
592
if branch_location is None:
593
del info_dict[file_id]
594
self._set_all_reference_info(info_dict)
596
def get_reference_info(self, file_id):
597
"""Get the tree_path and branch_location for a tree reference.
599
:return: a tuple of (branch_location, tree_path)
601
return self._get_all_reference_info().get(file_id, (None, None))
603
def set_push_location(self, location):
604
"""See Branch.set_push_location."""
605
self._set_config_location('push_location', location)
607
def set_bound_location(self, location):
608
"""See Branch.set_push_location."""
609
self._master_branch_cache = None
610
conf = self.get_config_stack()
612
if not conf.get('bound'):
615
conf.set('bound', 'False')
618
self._set_config_location('bound_location', location,
620
conf.set('bound', 'True')
623
def _get_bound_location(self, bound):
624
"""Return the bound location in the config file.
626
Return None if the bound parameter does not match"""
627
conf = self.get_config_stack()
628
if conf.get('bound') != bound:
630
return self._get_config_location('bound_location', config=conf)
632
def get_bound_location(self):
633
"""See Branch.get_bound_location."""
634
return self._get_bound_location(True)
636
def get_old_bound_location(self):
637
"""See Branch.get_old_bound_location"""
638
return self._get_bound_location(False)
640
def get_stacked_on_url(self):
641
# you can always ask for the URL; but you might not be able to use it
642
# if the repo can't support stacking.
643
# self._check_stackable_repo()
644
# stacked_on_location is only ever defined in branch.conf, so don't
645
# waste effort reading the whole stack of config files.
646
conf = _mod_config.BranchOnlyStack(self)
647
stacked_url = self._get_config_location('stacked_on_location',
649
if stacked_url is None:
650
raise errors.NotStacked(self)
651
# TODO(jelmer): Clean this up for pad.lv/1696545
654
def get_rev_id(self, revno, history=None):
655
"""Find the revision id of the specified revno."""
657
return _mod_revision.NULL_REVISION
659
with self.lock_read():
660
last_revno, last_revision_id = self.last_revision_info()
661
if revno <= 0 or revno > last_revno:
662
raise errors.RevnoOutOfBounds(revno, (0, last_revno))
664
if history is not None:
665
return history[revno - 1]
667
index = last_revno - revno
668
if len(self._partial_revision_history_cache) <= index:
669
self._extend_partial_history(stop_index=index)
670
if len(self._partial_revision_history_cache) > index:
671
return self._partial_revision_history_cache[index]
673
raise errors.NoSuchRevision(self, revno)
675
def revision_id_to_revno(self, revision_id):
676
"""Given a revision id, return its revno"""
677
if _mod_revision.is_null(revision_id):
679
with self.lock_read():
681
index = self._partial_revision_history_cache.index(revision_id)
684
self._extend_partial_history(stop_revision=revision_id)
685
except errors.RevisionNotPresent as e:
686
raise errors.GhostRevisionsHaveNoRevno(
687
revision_id, e.revision_id)
688
index = len(self._partial_revision_history_cache) - 1
690
raise errors.NoSuchRevision(self, revision_id)
691
if self._partial_revision_history_cache[index] != revision_id:
692
raise errors.NoSuchRevision(self, revision_id)
693
return self.revno() - index
696
class BzrBranch7(BzrBranch8):
697
"""A branch with support for a fallback repository."""
699
def set_reference_info(self, file_id, branch_location, tree_path=None):
700
super(BzrBranch7, self).set_reference_info(
701
file_id, branch_location, tree_path)
702
format_string = BzrBranchFormat8.get_format_string()
703
mutter('Upgrading branch to format %r', format_string)
704
self._transport.put_bytes('format', format_string)
707
class BzrBranch6(BzrBranch7):
708
"""See BzrBranchFormat6 for the capabilities of this branch.
710
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
714
def get_stacked_on_url(self):
715
raise UnstackableBranchFormat(self._format, self.user_url)
718
class BranchFormatMetadir(bzrdir.BzrFormat, BranchFormat):
719
"""Base class for branch formats that live in meta directories.
723
BranchFormat.__init__(self)
724
bzrdir.BzrFormat.__init__(self)
727
def find_format(klass, controldir, name=None):
728
"""Return the format for the branch object in controldir."""
730
transport = controldir.get_branch_transport(None, name=name)
731
except errors.NoSuchFile:
732
raise errors.NotBranchError(path=name, controldir=controldir)
734
format_string = transport.get_bytes("format")
735
except errors.NoSuchFile:
736
raise errors.NotBranchError(
737
path=transport.base, controldir=controldir)
738
return klass._find_format(format_registry, 'branch', format_string)
740
def _branch_class(self):
741
"""What class to instantiate on open calls."""
742
raise NotImplementedError(self._branch_class)
744
def _get_initial_config(self, append_revisions_only=None):
745
if append_revisions_only:
746
return b"append_revisions_only = True\n"
748
# Avoid writing anything if append_revisions_only is disabled,
749
# as that is the default.
752
def _initialize_helper(self, a_controldir, utf8_files, name=None,
754
"""Initialize a branch in a control dir, with specified files
756
:param a_controldir: The bzrdir to initialize the branch in
757
:param utf8_files: The files to create as a list of
758
(filename, content) tuples
759
:param name: Name of colocated branch to create, if any
760
:return: a branch in this format
763
name = a_controldir._get_selected_branch()
764
mutter('creating branch %r in %s', self, a_controldir.user_url)
765
branch_transport = a_controldir.get_branch_transport(self, name=name)
766
control_files = lockable_files.LockableFiles(branch_transport,
767
'lock', lockdir.LockDir)
768
control_files.create_lock()
769
control_files.lock_write()
771
utf8_files += [('format', self.as_string())]
772
for (filename, content) in utf8_files:
773
branch_transport.put_bytes(
775
mode=a_controldir._get_file_mode())
777
control_files.unlock()
778
branch = self.open(a_controldir, name, _found=True,
779
found_repository=repository)
780
self._run_post_branch_init_hooks(a_controldir, name, branch)
783
def open(self, a_controldir, name=None, _found=False,
784
ignore_fallbacks=False, found_repository=None,
785
possible_transports=None):
786
"""See BranchFormat.open()."""
788
name = a_controldir._get_selected_branch()
790
format = BranchFormatMetadir.find_format(a_controldir, name=name)
791
if format.__class__ != self.__class__:
792
raise AssertionError("wrong format %r found for %r" %
794
transport = a_controldir.get_branch_transport(None, name=name)
796
control_files = lockable_files.LockableFiles(transport, 'lock',
798
if found_repository is None:
799
found_repository = a_controldir.find_repository()
800
return self._branch_class()(
801
_format=self, _control_files=control_files, name=name,
802
a_controldir=a_controldir, _repository=found_repository,
803
ignore_fallbacks=ignore_fallbacks,
804
possible_transports=possible_transports)
805
except errors.NoSuchFile:
806
raise errors.NotBranchError(
807
path=transport.base, controldir=a_controldir)
810
def _matchingcontroldir(self):
811
ret = bzrdir.BzrDirMetaFormat1()
812
ret.set_branch_format(self)
815
def supports_tags(self):
818
def supports_leaving_lock(self):
821
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
823
BranchFormat.check_support_status(
824
self, allow_unsupported=allow_unsupported,
825
recommend_upgrade=recommend_upgrade, basedir=basedir)
826
bzrdir.BzrFormat.check_support_status(
827
self, allow_unsupported=allow_unsupported,
828
recommend_upgrade=recommend_upgrade, basedir=basedir)
831
class BzrBranchFormat6(BranchFormatMetadir):
832
"""Branch format with last-revision and tags.
834
Unlike previous formats, this has no explicit revision history. Instead,
835
this just stores the last-revision, and the left-hand history leading
836
up to there is the history.
838
This format was introduced in bzr 0.15
839
and became the default in 0.91.
842
def _branch_class(self):
846
def get_format_string(cls):
847
"""See BranchFormat.get_format_string()."""
848
return b"Bazaar Branch Format 6 (bzr 0.15)\n"
850
def get_format_description(self):
851
"""See BranchFormat.get_format_description()."""
852
return "Branch format 6"
854
def initialize(self, a_controldir, name=None, repository=None,
855
append_revisions_only=None):
856
"""Create a branch of this format in a_controldir."""
858
('last-revision', b'0 null:\n'),
859
('branch.conf', self._get_initial_config(append_revisions_only)),
862
return self._initialize_helper(
863
a_controldir, utf8_files, name, repository)
865
def make_tags(self, branch):
866
"""See breezy.branch.BranchFormat.make_tags()."""
867
return _mod_tag.BasicTags(branch)
869
def supports_set_append_revisions_only(self):
872
supports_reference_locations = True
875
class BzrBranchFormat8(BranchFormatMetadir):
876
"""Metadir format supporting storing locations of subtree branches."""
878
def _branch_class(self):
882
def get_format_string(cls):
883
"""See BranchFormat.get_format_string()."""
884
return b"Bazaar Branch Format 8 (needs bzr 1.15)\n"
886
def get_format_description(self):
887
"""See BranchFormat.get_format_description()."""
888
return "Branch format 8"
890
def initialize(self, a_controldir, name=None, repository=None,
891
append_revisions_only=None):
892
"""Create a branch of this format in a_controldir."""
893
utf8_files = [('last-revision', b'0 null:\n'),
895
self._get_initial_config(append_revisions_only)),
899
return self._initialize_helper(
900
a_controldir, utf8_files, name, repository)
902
def make_tags(self, branch):
903
"""See breezy.branch.BranchFormat.make_tags()."""
904
return _mod_tag.BasicTags(branch)
906
def supports_set_append_revisions_only(self):
909
def supports_stacking(self):
912
supports_reference_locations = True
915
class BzrBranchFormat7(BranchFormatMetadir):
916
"""Branch format with last-revision, tags, and a stacked location pointer.
918
The stacked location pointer is passed down to the repository and requires
919
a repository format with supports_external_lookups = True.
921
This format was introduced in bzr 1.6.
924
def initialize(self, a_controldir, name=None, repository=None,
925
append_revisions_only=None):
926
"""Create a branch of this format in a_controldir."""
927
utf8_files = [('last-revision', b'0 null:\n'),
929
self._get_initial_config(append_revisions_only)),
932
return self._initialize_helper(
933
a_controldir, utf8_files, name, repository)
935
def _branch_class(self):
939
def get_format_string(cls):
940
"""See BranchFormat.get_format_string()."""
941
return b"Bazaar Branch Format 7 (needs bzr 1.6)\n"
943
def get_format_description(self):
944
"""See BranchFormat.get_format_description()."""
945
return "Branch format 7"
947
def supports_set_append_revisions_only(self):
950
def supports_stacking(self):
953
def make_tags(self, branch):
954
"""See breezy.branch.BranchFormat.make_tags()."""
955
return _mod_tag.BasicTags(branch)
957
# This is a white lie; as soon as you set a reference location, we upgrade
958
# you to BzrBranchFormat8.
959
supports_reference_locations = True
962
class BranchReferenceFormat(BranchFormatMetadir):
963
"""Bzr branch reference format.
965
Branch references are used in implementing checkouts, they
966
act as an alias to the real branch which is at some other url.
974
def get_format_string(cls):
975
"""See BranchFormat.get_format_string()."""
976
return b"Bazaar-NG Branch Reference Format 1\n"
978
def get_format_description(self):
979
"""See BranchFormat.get_format_description()."""
980
return "Checkout reference format 1"
982
def get_reference(self, a_controldir, name=None):
983
"""See BranchFormat.get_reference()."""
984
transport = a_controldir.get_branch_transport(None, name=name)
985
url = urlutils.strip_segment_parameters(a_controldir.user_url)
986
return urlutils.join(
987
url, transport.get_bytes('location').decode('utf-8'))
989
def _write_reference(self, a_controldir, transport, to_branch):
990
to_url = to_branch.user_url
991
# Ideally, we'd write a relative path here for the benefit of colocated
992
# branches - so that moving a control directory doesn't break
993
# any references to colocated branches. Unfortunately, bzr
994
# does not support relative URLs. See pad.lv/1803845 -- jelmer
995
# to_url = urlutils.relative_url(
996
# a_controldir.user_url, to_branch.user_url)
997
transport.put_bytes('location', to_url.encode('utf-8'))
999
def set_reference(self, a_controldir, name, to_branch):
1000
"""See BranchFormat.set_reference()."""
1001
transport = a_controldir.get_branch_transport(None, name=name)
1002
self._write_reference(a_controldir, transport, to_branch)
1004
def initialize(self, a_controldir, name=None, target_branch=None,
1005
repository=None, append_revisions_only=None):
1006
"""Create a branch of this format in a_controldir."""
1007
if target_branch is None:
1008
# this format does not implement branch itself, thus the implicit
1009
# creation contract must see it as uninitializable
1010
raise errors.UninitializableFormat(self)
1011
mutter('creating branch reference in %s', a_controldir.user_url)
1012
if a_controldir._format.fixed_components:
1013
raise errors.IncompatibleFormat(self, a_controldir._format)
1015
name = a_controldir._get_selected_branch()
1016
branch_transport = a_controldir.get_branch_transport(self, name=name)
1017
self._write_reference(a_controldir, branch_transport, target_branch)
1018
branch_transport.put_bytes('format', self.as_string())
1019
branch = self.open(a_controldir, name, _found=True,
1020
possible_transports=[target_branch.controldir.root_transport])
1021
self._run_post_branch_init_hooks(a_controldir, name, branch)
1024
def _make_reference_clone_function(format, a_branch):
1025
"""Create a clone() routine for a branch dynamically."""
1026
def clone(to_bzrdir, revision_id=None, repository_policy=None, name=None,
1028
"""See Branch.clone()."""
1029
return format.initialize(to_bzrdir, target_branch=a_branch, name=name)
1030
# cannot obey revision_id limits when cloning a reference ...
1031
# FIXME RBC 20060210 either nuke revision_id for clone, or
1032
# emit some sort of warning/error to the caller ?!
1035
def open(self, a_controldir, name=None, _found=False, location=None,
1036
possible_transports=None, ignore_fallbacks=False,
1037
found_repository=None):
1038
"""Return the branch that the branch reference in a_controldir points at.
1040
:param a_controldir: A BzrDir that contains a branch.
1041
:param name: Name of colocated branch to open, if any
1042
:param _found: a private parameter, do not use it. It is used to
1043
indicate if format probing has already be done.
1044
:param ignore_fallbacks: when set, no fallback branches will be opened
1045
(if there are any). Default is to open fallbacks.
1046
:param location: The location of the referenced branch. If
1047
unspecified, this will be determined from the branch reference in
1049
:param possible_transports: An optional reusable transports list.
1052
name = a_controldir._get_selected_branch()
1054
format = BranchFormatMetadir.find_format(a_controldir, name=name)
1055
if format.__class__ != self.__class__:
1056
raise AssertionError("wrong format %r found for %r" %
1058
if location is None:
1059
location = self.get_reference(a_controldir, name)
1060
real_bzrdir = controldir.ControlDir.open(
1061
location, possible_transports=possible_transports)
1062
result = real_bzrdir.open_branch(
1063
ignore_fallbacks=ignore_fallbacks,
1064
possible_transports=possible_transports)
1065
# this changes the behaviour of result.clone to create a new reference
1066
# rather than a copy of the content of the branch.
1067
# I did not use a proxy object because that needs much more extensive
1068
# testing, and we are only changing one behaviour at the moment.
1069
# If we decide to alter more behaviours - i.e. the implicit nickname
1070
# then this should be refactored to introduce a tested proxy branch
1071
# and a subclass of that for use in overriding clone() and ....
1073
result.clone = self._make_reference_clone_function(result)
1077
class Converter5to6(object):
1078
"""Perform an in-place upgrade of format 5 to format 6"""
1080
def convert(self, branch):
1081
# Data for 5 and 6 can peacefully coexist.
1082
format = BzrBranchFormat6()
1083
new_branch = format.open(branch.controldir, _found=True)
1085
# Copy source data into target
1086
new_branch._write_last_revision_info(*branch.last_revision_info())
1087
with new_branch.lock_write():
1088
new_branch.set_parent(branch.get_parent())
1089
new_branch.set_bound_location(branch.get_bound_location())
1090
new_branch.set_push_location(branch.get_push_location())
1092
# New branch has no tags by default
1093
new_branch.tags._set_tag_dict({})
1095
# Copying done; now update target format
1096
new_branch._transport.put_bytes(
1097
'format', format.as_string(),
1098
mode=new_branch.controldir._get_file_mode())
1100
# Clean up old files
1101
new_branch._transport.delete('revision-history')
1102
with branch.lock_write():
1104
branch.set_parent(None)
1105
except errors.NoSuchFile:
1107
branch.set_bound_location(None)
1110
class Converter6to7(object):
1111
"""Perform an in-place upgrade of format 6 to format 7"""
1113
def convert(self, branch):
1114
format = BzrBranchFormat7()
1115
branch._set_config_location('stacked_on_location', '')
1116
# update target format
1117
branch._transport.put_bytes('format', format.as_string())
1120
class Converter7to8(object):
1121
"""Perform an in-place upgrade of format 7 to format 8"""
1123
def convert(self, branch):
1124
format = BzrBranchFormat8()
1125
branch._transport.put_bytes('references', b'')
1126
# update target format
1127
branch._transport.put_bytes('format', format.as_string())