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))
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)
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
return self._transport.get_bytes(l).strip(b'\n').decode('utf-8')
267
except errors.NoSuchFile:
271
def get_stacked_on_url(self):
272
raise UnstackableBranchFormat(self._format, self.user_url)
274
def set_push_location(self, location):
275
"""See Branch.set_push_location."""
276
self.get_config().set_user_option(
277
'push_location', location,
278
store=_mod_config.STORE_LOCATION_NORECURSE)
280
def _set_parent_location(self, url):
282
self._transport.delete('parent')
284
if isinstance(url, text_type):
285
url = url.encode('utf-8')
286
self._transport.put_bytes('parent', url + b'\n',
287
mode=self.controldir._get_file_mode())
290
"""If bound, unbind"""
291
with self.lock_write():
292
return self.set_bound_location(None)
294
def bind(self, other):
295
"""Bind this branch to the branch other.
297
This does not push or pull data between the branches, though it does
298
check for divergence to raise an error when the branches are not
299
either the same, or one a prefix of the other. That behaviour may not
300
be useful, so that check may be removed in future.
302
:param other: The branch to bind to
305
# TODO: jam 20051230 Consider checking if the target is bound
306
# It is debatable whether you should be able to bind to
307
# a branch which is itself bound.
308
# Committing is obviously forbidden,
309
# but binding itself may not be.
310
# Since we *have* to check at commit time, we don't
311
# *need* to check here
313
# we want to raise diverged if:
314
# last_rev is not in the other_last_rev history, AND
315
# other_last_rev is not in our history, and do it without pulling
317
with self.lock_write():
318
self.set_bound_location(other.base)
320
def get_bound_location(self):
322
return self._transport.get_bytes('bound')[:-1].decode('utf-8')
323
except errors.NoSuchFile:
326
def get_master_branch(self, possible_transports=None):
327
"""Return the branch we are bound to.
329
:return: Either a Branch, or None
331
with self.lock_read():
332
if self._master_branch_cache is None:
333
self._master_branch_cache = self._get_master_branch(
335
return self._master_branch_cache
337
def _get_master_branch(self, possible_transports):
338
bound_loc = self.get_bound_location()
342
return Branch.open(bound_loc,
343
possible_transports=possible_transports)
344
except (errors.NotBranchError, errors.ConnectionError) as e:
345
raise errors.BoundBranchConnectionFailure(
348
def set_bound_location(self, location):
349
"""Set the target where this branch is bound to.
351
:param location: URL to the target branch
353
with self.lock_write():
354
self._master_branch_cache = None
356
self._transport.put_bytes('bound', location.encode('utf-8')+b'\n',
357
mode=self.controldir._get_file_mode())
360
self._transport.delete('bound')
361
except errors.NoSuchFile:
365
def update(self, possible_transports=None):
366
"""Synchronise this branch with the master branch if any.
368
:return: None or the last_revision that was pivoted out during the
371
with self.lock_write():
372
master = self.get_master_branch(possible_transports)
373
if master is not None:
374
old_tip = _mod_revision.ensure_null(self.last_revision())
375
self.pull(master, overwrite=True)
376
if self.repository.get_graph().is_ancestor(old_tip,
377
_mod_revision.ensure_null(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('format', self._format.as_string())
411
def _get_tags_bytes(self):
412
"""Get the bytes of a serialised tags dict.
414
Note that not all branches support tags, nor do all use the same tags
415
logic: this method is specific to BasicTags. Other tag implementations
416
may use the same method name and behave differently, safely, because
417
of the double-dispatch via
418
format.make_tags->tags_instance->get_tags_dict.
420
:return: The bytes of the tags file.
421
:seealso: Branch._set_tags_bytes.
423
with self.lock_read():
424
if self._tags_bytes is None:
425
self._tags_bytes = self._transport.get_bytes('tags')
426
return self._tags_bytes
428
def _set_tags_bytes(self, bytes):
429
"""Mirror method for _get_tags_bytes.
431
:seealso: Branch._get_tags_bytes.
433
with self.lock_write():
434
self._tags_bytes = bytes
435
return self._transport.put_bytes('tags', bytes)
437
def _clear_cached_state(self):
438
super(BzrBranch, self)._clear_cached_state()
439
self._tags_bytes = None
442
class BzrBranch8(BzrBranch):
443
"""A branch that stores tree-reference locations."""
445
def _open_hook(self, possible_transports=None):
446
if self._ignore_fallbacks:
448
if possible_transports is None:
449
possible_transports = [self.controldir.root_transport]
451
url = self.get_stacked_on_url()
452
except (errors.UnstackableRepositoryFormat, errors.NotStacked,
453
UnstackableBranchFormat):
456
for hook in Branch.hooks['transform_fallback_location']:
457
url = hook(self, url)
459
hook_name = Branch.hooks.get_hook_name(hook)
460
raise AssertionError(
461
"'transform_fallback_location' hook %s returned "
462
"None, not a URL." % hook_name)
463
self._activate_fallback_location(url,
464
possible_transports=possible_transports)
466
def __init__(self, *args, **kwargs):
467
self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
468
super(BzrBranch8, self).__init__(*args, **kwargs)
469
self._last_revision_info_cache = None
470
self._reference_info = None
472
def _clear_cached_state(self):
473
super(BzrBranch8, self)._clear_cached_state()
474
self._last_revision_info_cache = None
475
self._reference_info = None
477
def _check_history_violation(self, revision_id):
478
current_revid = self.last_revision()
479
last_revision = _mod_revision.ensure_null(current_revid)
480
if _mod_revision.is_null(last_revision):
482
graph = self.repository.get_graph()
483
for lh_ancestor in graph.iter_lefthand_ancestry(revision_id):
484
if lh_ancestor == current_revid:
486
raise errors.AppendRevisionsOnlyViolation(self.user_url)
488
def _gen_revision_history(self):
489
"""Generate the revision history from last revision
491
last_revno, last_revision = self.last_revision_info()
492
self._extend_partial_history(stop_index=last_revno-1)
493
return list(reversed(self._partial_revision_history_cache))
495
def _set_parent_location(self, url):
496
"""Set the parent branch"""
497
with self.lock_write():
498
self._set_config_location('parent_location', url, make_relative=True)
500
def _get_parent_location(self):
501
"""Set the parent branch"""
502
with self.lock_read():
503
return self._get_config_location('parent_location')
505
def _set_all_reference_info(self, info_dict):
506
"""Replace all reference info stored in a branch.
508
:param info_dict: A dict of {file_id: (tree_path, branch_location)}
511
writer = rio.RioWriter(s)
512
for tree_path, ( branch_location, file_id) in viewitems(info_dict):
513
stanza = rio.Stanza(tree_path=tree_path,
514
branch_location=branch_location)
515
if file_id is not None:
516
stanza.add('file_id', file_id)
517
writer.write_stanza(stanza)
518
with self.lock_write():
519
self._transport.put_bytes('references', s.getvalue())
520
self._reference_info = info_dict
522
def _get_all_reference_info(self):
523
"""Return all the reference info stored in a branch.
525
:return: A dict of {tree_path: (branch_location, file_id)}
527
with self.lock_read():
528
if self._reference_info is not None:
529
return self._reference_info
530
with self._transport.get('references') as rio_file:
531
stanzas = rio.read_stanzas(rio_file)
534
s['branch_location'],
535
s['file_id'].encode('ascii') if 'file_id' in s else None)
537
self._reference_info = info_dict
540
def set_reference_info(self, tree_path, branch_location, file_id=None):
541
"""Set the branch location to use for a tree reference.
543
:param tree_path: The path of the tree reference in the tree.
544
:param branch_location: The location of the branch to retrieve tree
546
:param file_id: The file-id of the tree reference.
548
info_dict = self._get_all_reference_info()
549
info_dict[tree_path] = (branch_location, file_id)
550
if branch_location is None:
551
del info_dict[tree_path]
552
self._set_all_reference_info(info_dict)
554
def get_reference_info(self, path):
555
"""Get the tree_path and branch_location for a tree reference.
557
:return: a tuple of (branch_location, file_id)
559
return self._get_all_reference_info().get(path, (None, None))
561
def reference_parent(self, path, file_id=None, possible_transports=None):
562
"""Return the parent branch for a tree-reference file_id.
564
:param file_id: The file_id of the tree reference
565
:param path: The path of the file_id in the tree
566
:return: A branch associated with the file_id
568
branch_location = self.get_reference_info(path)[0]
569
if branch_location is None:
570
return Branch.reference_parent(self, path, file_id,
572
branch_location = urlutils.join(self.user_url, branch_location)
573
return Branch.open(branch_location,
574
possible_transports=possible_transports)
576
def set_push_location(self, location):
577
"""See Branch.set_push_location."""
578
self._set_config_location('push_location', location)
580
def set_bound_location(self, location):
581
"""See Branch.set_push_location."""
582
self._master_branch_cache = None
584
conf = self.get_config_stack()
586
if not conf.get('bound'):
589
conf.set('bound', 'False')
592
self._set_config_location('bound_location', location,
594
conf.set('bound', 'True')
597
def _get_bound_location(self, bound):
598
"""Return the bound location in the config file.
600
Return None if the bound parameter does not match"""
601
conf = self.get_config_stack()
602
if conf.get('bound') != bound:
604
return self._get_config_location('bound_location', config=conf)
606
def get_bound_location(self):
607
"""See Branch.get_bound_location."""
608
return self._get_bound_location(True)
610
def get_old_bound_location(self):
611
"""See Branch.get_old_bound_location"""
612
return self._get_bound_location(False)
614
def get_stacked_on_url(self):
615
# you can always ask for the URL; but you might not be able to use it
616
# if the repo can't support stacking.
617
## self._check_stackable_repo()
618
# stacked_on_location is only ever defined in branch.conf, so don't
619
# waste effort reading the whole stack of config files.
620
conf = _mod_config.BranchOnlyStack(self)
621
stacked_url = self._get_config_location('stacked_on_location',
623
if stacked_url is None:
624
raise errors.NotStacked(self)
625
# TODO(jelmer): Clean this up for pad.lv/1696545
626
if sys.version_info[0] == 2:
627
return stacked_url.encode('utf-8')
631
def get_rev_id(self, revno, history=None):
632
"""Find the revision id of the specified revno."""
634
return _mod_revision.NULL_REVISION
636
with self.lock_read():
637
last_revno, last_revision_id = self.last_revision_info()
638
if revno <= 0 or revno > last_revno:
639
raise errors.NoSuchRevision(self, revno)
641
if history is not None:
642
return history[revno - 1]
644
index = last_revno - revno
645
if len(self._partial_revision_history_cache) <= index:
646
self._extend_partial_history(stop_index=index)
647
if len(self._partial_revision_history_cache) > index:
648
return self._partial_revision_history_cache[index]
650
raise errors.NoSuchRevision(self, revno)
652
def revision_id_to_revno(self, revision_id):
653
"""Given a revision id, return its revno"""
654
if _mod_revision.is_null(revision_id):
656
with self.lock_read():
658
index = self._partial_revision_history_cache.index(revision_id)
661
self._extend_partial_history(stop_revision=revision_id)
662
except errors.RevisionNotPresent as e:
663
raise errors.GhostRevisionsHaveNoRevno(
664
revision_id, e.revision_id)
665
index = len(self._partial_revision_history_cache) - 1
667
raise errors.NoSuchRevision(self, revision_id)
668
if self._partial_revision_history_cache[index] != revision_id:
669
raise errors.NoSuchRevision(self, revision_id)
670
return self.revno() - index
673
class BzrBranch7(BzrBranch8):
674
"""A branch with support for a fallback repository."""
676
def set_reference_info(self, tree_path, branch_location, file_id=None):
677
Branch.set_reference_info(self, file_id, tree_path, branch_location)
679
def get_reference_info(self, path):
680
Branch.get_reference_info(self, path)
682
def reference_parent(self, path, file_id=None, possible_transports=None):
683
return Branch.reference_parent(self, path, file_id, possible_transports)
686
class BzrBranch6(BzrBranch7):
687
"""See BzrBranchFormat6 for the capabilities of this branch.
689
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
693
def get_stacked_on_url(self):
694
raise UnstackableBranchFormat(self._format, self.user_url)
697
class BranchFormatMetadir(bzrdir.BzrFormat, BranchFormat):
698
"""Base class for branch formats that live in meta directories.
702
BranchFormat.__init__(self)
703
bzrdir.BzrFormat.__init__(self)
706
def find_format(klass, controldir, name=None):
707
"""Return the format for the branch object in controldir."""
709
transport = controldir.get_branch_transport(None, name=name)
710
except errors.NoSuchFile:
711
raise errors.NotBranchError(path=name, controldir=controldir)
713
format_string = transport.get_bytes("format")
714
except errors.NoSuchFile:
715
raise errors.NotBranchError(
716
path=transport.base, controldir=controldir)
717
return klass._find_format(format_registry, 'branch', format_string)
719
def _branch_class(self):
720
"""What class to instantiate on open calls."""
721
raise NotImplementedError(self._branch_class)
723
def _get_initial_config(self, append_revisions_only=None):
724
if append_revisions_only:
725
return b"append_revisions_only = True\n"
727
# Avoid writing anything if append_revisions_only is disabled,
728
# as that is the default.
731
def _initialize_helper(self, a_controldir, utf8_files, name=None,
733
"""Initialize a branch in a control dir, with specified files
735
:param a_controldir: The bzrdir to initialize the branch in
736
:param utf8_files: The files to create as a list of
737
(filename, content) tuples
738
:param name: Name of colocated branch to create, if any
739
:return: a branch in this format
742
name = a_controldir._get_selected_branch()
743
mutter('creating branch %r in %s', self, a_controldir.user_url)
744
branch_transport = a_controldir.get_branch_transport(self, name=name)
745
control_files = lockable_files.LockableFiles(branch_transport,
746
'lock', lockdir.LockDir)
747
control_files.create_lock()
748
control_files.lock_write()
750
utf8_files += [('format', self.as_string())]
751
for (filename, content) in utf8_files:
752
branch_transport.put_bytes(
754
mode=a_controldir._get_file_mode())
756
control_files.unlock()
757
branch = self.open(a_controldir, name, _found=True,
758
found_repository=repository)
759
self._run_post_branch_init_hooks(a_controldir, name, branch)
762
def open(self, a_controldir, name=None, _found=False, ignore_fallbacks=False,
763
found_repository=None, possible_transports=None):
764
"""See BranchFormat.open()."""
766
name = a_controldir._get_selected_branch()
768
format = BranchFormatMetadir.find_format(a_controldir, name=name)
769
if format.__class__ != self.__class__:
770
raise AssertionError("wrong format %r found for %r" %
772
transport = a_controldir.get_branch_transport(None, name=name)
774
control_files = lockable_files.LockableFiles(transport, 'lock',
776
if found_repository is None:
777
found_repository = a_controldir.find_repository()
778
return self._branch_class()(_format=self,
779
_control_files=control_files,
781
a_controldir=a_controldir,
782
_repository=found_repository,
783
ignore_fallbacks=ignore_fallbacks,
784
possible_transports=possible_transports)
785
except errors.NoSuchFile:
786
raise errors.NotBranchError(path=transport.base, controldir=a_controldir)
789
def _matchingcontroldir(self):
790
ret = bzrdir.BzrDirMetaFormat1()
791
ret.set_branch_format(self)
794
def supports_tags(self):
797
def supports_leaving_lock(self):
800
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
802
BranchFormat.check_support_status(self,
803
allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
805
bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
806
recommend_upgrade=recommend_upgrade, basedir=basedir)
809
class BzrBranchFormat6(BranchFormatMetadir):
810
"""Branch format with last-revision and tags.
812
Unlike previous formats, this has no explicit revision history. Instead,
813
this just stores the last-revision, and the left-hand history leading
814
up to there is the history.
816
This format was introduced in bzr 0.15
817
and became the default in 0.91.
820
def _branch_class(self):
824
def get_format_string(cls):
825
"""See BranchFormat.get_format_string()."""
826
return b"Bazaar Branch Format 6 (bzr 0.15)\n"
828
def get_format_description(self):
829
"""See BranchFormat.get_format_description()."""
830
return "Branch format 6"
832
def initialize(self, a_controldir, name=None, repository=None,
833
append_revisions_only=None):
834
"""Create a branch of this format in a_controldir."""
835
utf8_files = [('last-revision', b'0 null:\n'),
837
self._get_initial_config(append_revisions_only)),
840
return self._initialize_helper(a_controldir, utf8_files, name, repository)
842
def make_tags(self, branch):
843
"""See breezy.branch.BranchFormat.make_tags()."""
844
return _mod_tag.BasicTags(branch)
846
def supports_set_append_revisions_only(self):
850
class BzrBranchFormat8(BranchFormatMetadir):
851
"""Metadir format supporting storing locations of subtree branches."""
853
def _branch_class(self):
857
def get_format_string(cls):
858
"""See BranchFormat.get_format_string()."""
859
return b"Bazaar Branch Format 8 (needs bzr 1.15)\n"
861
def get_format_description(self):
862
"""See BranchFormat.get_format_description()."""
863
return "Branch format 8"
865
def initialize(self, a_controldir, name=None, repository=None,
866
append_revisions_only=None):
867
"""Create a branch of this format in a_controldir."""
868
utf8_files = [('last-revision', b'0 null:\n'),
870
self._get_initial_config(append_revisions_only)),
874
return self._initialize_helper(a_controldir, utf8_files, name, repository)
876
def make_tags(self, branch):
877
"""See breezy.branch.BranchFormat.make_tags()."""
878
return _mod_tag.BasicTags(branch)
880
def supports_set_append_revisions_only(self):
883
def supports_stacking(self):
886
supports_reference_locations = True
889
class BzrBranchFormat7(BranchFormatMetadir):
890
"""Branch format with last-revision, tags, and a stacked location pointer.
892
The stacked location pointer is passed down to the repository and requires
893
a repository format with supports_external_lookups = True.
895
This format was introduced in bzr 1.6.
898
def initialize(self, a_controldir, name=None, repository=None,
899
append_revisions_only=None):
900
"""Create a branch of this format in a_controldir."""
901
utf8_files = [('last-revision', b'0 null:\n'),
903
self._get_initial_config(append_revisions_only)),
906
return self._initialize_helper(a_controldir, utf8_files, name, repository)
908
def _branch_class(self):
912
def get_format_string(cls):
913
"""See BranchFormat.get_format_string()."""
914
return b"Bazaar Branch Format 7 (needs bzr 1.6)\n"
916
def get_format_description(self):
917
"""See BranchFormat.get_format_description()."""
918
return "Branch format 7"
920
def supports_set_append_revisions_only(self):
923
def supports_stacking(self):
926
def make_tags(self, branch):
927
"""See breezy.branch.BranchFormat.make_tags()."""
928
return _mod_tag.BasicTags(branch)
930
supports_reference_locations = False
933
class BranchReferenceFormat(BranchFormatMetadir):
934
"""Bzr branch reference format.
936
Branch references are used in implementing checkouts, they
937
act as an alias to the real branch which is at some other url.
945
def get_format_string(cls):
946
"""See BranchFormat.get_format_string()."""
947
return b"Bazaar-NG Branch Reference Format 1\n"
949
def get_format_description(self):
950
"""See BranchFormat.get_format_description()."""
951
return "Checkout reference format 1"
953
def get_reference(self, a_controldir, name=None):
954
"""See BranchFormat.get_reference()."""
955
transport = a_controldir.get_branch_transport(None, name=name)
956
url = urlutils.split_segment_parameters(a_controldir.user_url)[0]
957
return urlutils.join(url, transport.get_bytes('location').decode('utf-8'))
959
def _write_reference(self, a_controldir, transport, to_branch):
960
to_url = to_branch.user_url
961
if a_controldir.control_url == to_branch.controldir.control_url:
962
# Write relative paths for colocated branches, but absolute
963
# paths for everything else. This is for the benefit
964
# of older bzr versions that don't support relative paths.
965
to_url = urlutils.relative_url(a_controldir.user_url, to_branch.user_url)
966
transport.put_bytes('location', to_url.encode('utf-8'))
968
def set_reference(self, a_controldir, name, to_branch):
969
"""See BranchFormat.set_reference()."""
970
transport = a_controldir.get_branch_transport(None, name=name)
971
self._write_reference(a_controldir, transport, to_branch)
973
def initialize(self, a_controldir, name=None, target_branch=None,
974
repository=None, append_revisions_only=None):
975
"""Create a branch of this format in a_controldir."""
976
if target_branch is None:
977
# this format does not implement branch itself, thus the implicit
978
# creation contract must see it as uninitializable
979
raise errors.UninitializableFormat(self)
980
mutter('creating branch reference in %s', a_controldir.user_url)
981
if a_controldir._format.fixed_components:
982
raise errors.IncompatibleFormat(self, a_controldir._format)
984
name = a_controldir._get_selected_branch()
985
branch_transport = a_controldir.get_branch_transport(self, name=name)
986
self._write_reference(a_controldir, branch_transport, target_branch)
987
branch_transport.put_bytes('format', self.as_string())
988
branch = self.open(a_controldir, name, _found=True,
989
possible_transports=[target_branch.controldir.root_transport])
990
self._run_post_branch_init_hooks(a_controldir, name, branch)
993
def _make_reference_clone_function(format, a_branch):
994
"""Create a clone() routine for a branch dynamically."""
995
def clone(to_bzrdir, revision_id=None,
996
repository_policy=None):
997
"""See Branch.clone()."""
998
return format.initialize(to_bzrdir, target_branch=a_branch)
999
# cannot obey revision_id limits when cloning a reference ...
1000
# FIXME RBC 20060210 either nuke revision_id for clone, or
1001
# emit some sort of warning/error to the caller ?!
1004
def open(self, a_controldir, name=None, _found=False, location=None,
1005
possible_transports=None, ignore_fallbacks=False,
1006
found_repository=None):
1007
"""Return the branch that the branch reference in a_controldir points at.
1009
:param a_controldir: A BzrDir that contains a branch.
1010
:param name: Name of colocated branch to open, if any
1011
:param _found: a private parameter, do not use it. It is used to
1012
indicate if format probing has already be done.
1013
:param ignore_fallbacks: when set, no fallback branches will be opened
1014
(if there are any). Default is to open fallbacks.
1015
:param location: The location of the referenced branch. If
1016
unspecified, this will be determined from the branch reference in
1018
:param possible_transports: An optional reusable transports list.
1021
name = a_controldir._get_selected_branch()
1023
format = BranchFormatMetadir.find_format(a_controldir, name=name)
1024
if format.__class__ != self.__class__:
1025
raise AssertionError("wrong format %r found for %r" %
1027
if location is None:
1028
location = self.get_reference(a_controldir, name)
1029
real_bzrdir = controldir.ControlDir.open(
1030
location, possible_transports=possible_transports)
1031
result = real_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks,
1032
possible_transports=possible_transports)
1033
# this changes the behaviour of result.clone to create a new reference
1034
# rather than a copy of the content of the branch.
1035
# I did not use a proxy object because that needs much more extensive
1036
# testing, and we are only changing one behaviour at the moment.
1037
# If we decide to alter more behaviours - i.e. the implicit nickname
1038
# then this should be refactored to introduce a tested proxy branch
1039
# and a subclass of that for use in overriding clone() and ....
1041
result.clone = self._make_reference_clone_function(result)
1045
class Converter5to6(object):
1046
"""Perform an in-place upgrade of format 5 to format 6"""
1048
def convert(self, branch):
1049
# Data for 5 and 6 can peacefully coexist.
1050
format = BzrBranchFormat6()
1051
new_branch = format.open(branch.controldir, _found=True)
1053
# Copy source data into target
1054
new_branch._write_last_revision_info(*branch.last_revision_info())
1055
with new_branch.lock_write():
1056
new_branch.set_parent(branch.get_parent())
1057
new_branch.set_bound_location(branch.get_bound_location())
1058
new_branch.set_push_location(branch.get_push_location())
1060
# New branch has no tags by default
1061
new_branch.tags._set_tag_dict({})
1063
# Copying done; now update target format
1064
new_branch._transport.put_bytes('format',
1066
mode=new_branch.controldir._get_file_mode())
1068
# Clean up old files
1069
new_branch._transport.delete('revision-history')
1070
with branch.lock_write():
1072
branch.set_parent(None)
1073
except errors.NoSuchFile:
1075
branch.set_bound_location(None)
1078
class Converter6to7(object):
1079
"""Perform an in-place upgrade of format 6 to format 7"""
1081
def convert(self, branch):
1082
format = BzrBranchFormat7()
1083
branch._set_config_location('stacked_on_location', '')
1084
# update target format
1085
branch._transport.put_bytes('format', format.as_string())
1088
class Converter7to8(object):
1089
"""Perform an in-place upgrade of format 7 to format 8"""
1091
def convert(self, branch):
1092
format = BzrBranchFormat8()
1093
branch._transport.put_bytes('references', b'')
1094
# update target format
1095
branch._transport.put_bytes('format', format.as_string())