1
# Copyright (C) 2005-2012 Canonical Ltd
2
# Copyright (C) 2017 Breezy Developers
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
from __future__ import absolute_import
22
from ..lazy_import import lazy_import
23
lazy_import(globals(), """
26
config as _mod_config,
39
revision as _mod_revision,
42
from ..branch import (
45
BranchWriteLockResult,
47
UnstackableBranchFormat,
49
from ..decorators import (
52
from ..lock import _RelockDebugMixin, LogicalLockResult
53
from ..sixish import (
63
class BzrBranch(Branch, _RelockDebugMixin):
64
"""A branch stored in the actual filesystem.
66
Note that it's "local" in the context of the filesystem; it doesn't
67
really matter if it's on an nfs/smb/afs/coda/... share, as long as
68
it's writable, and can be accessed via the normal filesystem API.
70
:ivar _transport: Transport for file operations on this branch's
71
control files, typically pointing to the .bzr/branch directory.
72
:ivar repository: Repository for this branch.
73
:ivar base: The url of the base directory for this branch; the one
74
containing the .bzr directory.
75
:ivar name: Optional colocated branch name as it exists in the control
79
def __init__(self, _format=None,
80
_control_files=None, a_controldir=None, name=None,
81
_repository=None, ignore_fallbacks=False,
82
possible_transports=None):
83
"""Create new branch object at a particular location."""
84
if a_controldir is None:
85
raise ValueError('a_controldir must be supplied')
87
raise ValueError('name must be supplied')
88
self.controldir = a_controldir
89
self._user_transport = self.controldir.transport.clone('..')
91
self._user_transport.set_segment_parameter(
92
"branch", urlutils.escape(name))
93
self._base = self._user_transport.base
95
self._format = _format
96
if _control_files is None:
97
raise ValueError('BzrBranch _control_files is None')
98
self.control_files = _control_files
99
self._transport = _control_files._transport
100
self.repository = _repository
101
self.conf_store = None
102
Branch.__init__(self, possible_transports)
103
self._tags_bytes = None
106
return '%s(%s)' % (self.__class__.__name__, self.user_url)
111
"""Returns the directory containing the control directory."""
114
base = property(_get_base, doc="The URL for the root of this branch.")
117
def user_transport(self):
118
return self._user_transport
120
def _get_config(self):
121
"""Get the concrete config for just the config in this branch.
123
This is not intended for client use; see Branch.get_config for the
128
:return: An object supporting get_option and set_option.
130
return _mod_config.TransportConfig(self._transport, 'branch.conf')
132
def _get_config_store(self):
133
if self.conf_store is None:
134
self.conf_store = _mod_config.BranchStore(self)
135
return self.conf_store
137
def _uncommitted_branch(self):
138
"""Return the branch that may contain uncommitted changes."""
139
master = self.get_master_branch()
140
if master is not None:
145
def store_uncommitted(self, creator):
146
"""Store uncommitted changes from a ShelfCreator.
148
:param creator: The ShelfCreator containing uncommitted changes, or
149
None to delete any stored changes.
150
:raises: ChangesAlreadyStored if the branch already has changes.
152
branch = self._uncommitted_branch()
154
branch._transport.delete('stored-transform')
156
if branch._transport.has('stored-transform'):
157
raise errors.ChangesAlreadyStored
158
transform = BytesIO()
159
creator.write_shelf(transform)
161
branch._transport.put_file('stored-transform', transform)
163
def get_unshelver(self, tree):
164
"""Return a shelf.Unshelver for this branch and tree.
166
:param tree: The tree to use to construct the Unshelver.
167
:return: an Unshelver or None if no changes are stored.
169
branch = self._uncommitted_branch()
171
transform = branch._transport.get('stored-transform')
172
except errors.NoSuchFile:
174
return shelf.Unshelver.from_tree_and_shelf(tree, transform)
177
return self.control_files.is_locked()
179
def lock_write(self, token=None):
180
"""Lock the branch for write operations.
182
:param token: A token to permit reacquiring a previously held and
184
:return: A BranchWriteLockResult.
186
if not self.is_locked():
188
self.repository._warn_if_deprecated(self)
189
self.repository.lock_write()
194
return BranchWriteLockResult(
196
self.control_files.lock_write(token=token))
197
except BaseException:
199
self.repository.unlock()
203
"""Lock the branch for read operations.
205
:return: A breezy.lock.LogicalLockResult.
207
if not self.is_locked():
209
self.repository._warn_if_deprecated(self)
210
self.repository.lock_read()
215
self.control_files.lock_read()
216
return LogicalLockResult(self.unlock)
217
except BaseException:
219
self.repository.unlock()
222
@only_raises(errors.LockNotHeld, errors.LockBroken)
224
if self.control_files._lock_count == 1 and self.conf_store is not None:
225
self.conf_store.save_changes()
227
self.control_files.unlock()
229
if not self.control_files.is_locked():
230
self.repository.unlock()
231
# we just released the lock
232
self._clear_cached_state()
234
def peek_lock_mode(self):
235
if self.control_files._lock_count == 0:
238
return self.control_files._lock_mode
240
def get_physical_lock_status(self):
241
return self.control_files.get_physical_lock_status()
243
def set_last_revision_info(self, revno, revision_id):
244
if not revision_id or not isinstance(revision_id, bytes):
245
raise errors.InvalidRevisionId(
246
revision_id=revision_id, branch=self)
247
revision_id = _mod_revision.ensure_null(revision_id)
248
with self.lock_write():
249
old_revno, old_revid = self.last_revision_info()
250
if self.get_append_revisions_only():
251
self._check_history_violation(revision_id)
252
self._run_pre_change_branch_tip_hooks(revno, revision_id)
253
self._write_last_revision_info(revno, revision_id)
254
self._clear_cached_state()
255
self._last_revision_info_cache = revno, revision_id
256
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
258
def basis_tree(self):
259
"""See Branch.basis_tree."""
260
return self.repository.revision_tree(self.last_revision())
262
def _get_parent_location(self):
263
_locs = ['parent', 'pull', 'x-pull']
266
contents = self._transport.get_bytes(l)
267
except errors.NoSuchFile:
270
return contents.strip(b'\n').decode('utf-8')
273
def get_stacked_on_url(self):
274
raise UnstackableBranchFormat(self._format, self.user_url)
276
def set_push_location(self, location):
277
"""See Branch.set_push_location."""
278
self.get_config().set_user_option(
279
'push_location', location,
280
store=_mod_config.STORE_LOCATION_NORECURSE)
282
def _set_parent_location(self, url):
284
self._transport.delete('parent')
286
if isinstance(url, text_type):
287
url = url.encode('utf-8')
288
self._transport.put_bytes('parent', url + b'\n',
289
mode=self.controldir._get_file_mode())
292
"""If bound, unbind"""
293
with self.lock_write():
294
return self.set_bound_location(None)
296
def bind(self, other):
297
"""Bind this branch to the branch other.
299
This does not push or pull data between the branches, though it does
300
check for divergence to raise an error when the branches are not
301
either the same, or one a prefix of the other. That behaviour may not
302
be useful, so that check may be removed in future.
304
:param other: The branch to bind to
307
# TODO: jam 20051230 Consider checking if the target is bound
308
# It is debatable whether you should be able to bind to
309
# a branch which is itself bound.
310
# Committing is obviously forbidden,
311
# but binding itself may not be.
312
# Since we *have* to check at commit time, we don't
313
# *need* to check here
315
# we want to raise diverged if:
316
# last_rev is not in the other_last_rev history, AND
317
# other_last_rev is not in our history, and do it without pulling
319
with self.lock_write():
320
self.set_bound_location(other.base)
322
def get_bound_location(self):
324
return self._transport.get_bytes('bound')[:-1].decode('utf-8')
325
except errors.NoSuchFile:
328
def get_master_branch(self, possible_transports=None):
329
"""Return the branch we are bound to.
331
:return: Either a Branch, or None
333
with self.lock_read():
334
if self._master_branch_cache is None:
335
self._master_branch_cache = self._get_master_branch(
337
return self._master_branch_cache
339
def _get_master_branch(self, possible_transports):
340
bound_loc = self.get_bound_location()
344
return Branch.open(bound_loc,
345
possible_transports=possible_transports)
346
except (errors.NotBranchError, errors.ConnectionError) as e:
347
raise errors.BoundBranchConnectionFailure(
350
def set_bound_location(self, location):
351
"""Set the target where this branch is bound to.
353
:param location: URL to the target branch
355
with self.lock_write():
356
self._master_branch_cache = None
358
self._transport.put_bytes(
359
'bound', location.encode('utf-8') + b'\n',
360
mode=self.controldir._get_file_mode())
363
self._transport.delete('bound')
364
except errors.NoSuchFile:
368
def update(self, possible_transports=None):
369
"""Synchronise this branch with the master branch if any.
371
:return: None or the last_revision that was pivoted out during the
374
with self.lock_write():
375
master = self.get_master_branch(possible_transports)
376
if master is not None:
377
old_tip = _mod_revision.ensure_null(self.last_revision())
378
self.pull(master, overwrite=True)
379
if self.repository.get_graph().is_ancestor(
380
old_tip, _mod_revision.ensure_null(
381
self.last_revision())):
386
def _read_last_revision_info(self):
387
revision_string = self._transport.get_bytes('last-revision')
388
revno, revision_id = revision_string.rstrip(b'\n').split(b' ', 1)
389
revision_id = cache_utf8.get_cached_utf8(revision_id)
391
return revno, revision_id
393
def _write_last_revision_info(self, revno, revision_id):
394
"""Simply write out the revision id, with no checks.
396
Use set_last_revision_info to perform this safely.
398
Does not update the revision_history cache.
400
revision_id = _mod_revision.ensure_null(revision_id)
401
out_string = b'%d %s\n' % (revno, revision_id)
402
self._transport.put_bytes('last-revision', out_string,
403
mode=self.controldir._get_file_mode())
405
def update_feature_flags(self, updated_flags):
406
"""Update the feature flags for this branch.
408
:param updated_flags: Dictionary mapping feature names to necessities
409
A necessity can be None to indicate the feature should be removed
411
with self.lock_write():
412
self._format._update_feature_flags(updated_flags)
413
self.control_transport.put_bytes(
414
'format', self._format.as_string())
416
def _get_tags_bytes(self):
417
"""Get the bytes of a serialised tags dict.
419
Note that not all branches support tags, nor do all use the same tags
420
logic: this method is specific to BasicTags. Other tag implementations
421
may use the same method name and behave differently, safely, because
422
of the double-dispatch via
423
format.make_tags->tags_instance->get_tags_dict.
425
:return: The bytes of the tags file.
426
:seealso: Branch._set_tags_bytes.
428
with self.lock_read():
429
if self._tags_bytes is None:
430
self._tags_bytes = self._transport.get_bytes('tags')
431
return self._tags_bytes
433
def _set_tags_bytes(self, bytes):
434
"""Mirror method for _get_tags_bytes.
436
:seealso: Branch._get_tags_bytes.
438
with self.lock_write():
439
self._tags_bytes = bytes
440
return self._transport.put_bytes('tags', bytes)
442
def _clear_cached_state(self):
443
super(BzrBranch, self)._clear_cached_state()
444
self._tags_bytes = None
447
class BzrBranch8(BzrBranch):
448
"""A branch that stores tree-reference locations."""
450
def _open_hook(self, possible_transports=None):
451
if self._ignore_fallbacks:
453
if possible_transports is None:
454
possible_transports = [self.controldir.root_transport]
456
url = self.get_stacked_on_url()
457
except (errors.UnstackableRepositoryFormat, errors.NotStacked,
458
UnstackableBranchFormat):
461
for hook in Branch.hooks['transform_fallback_location']:
462
url = hook(self, url)
464
hook_name = Branch.hooks.get_hook_name(hook)
465
raise AssertionError(
466
"'transform_fallback_location' hook %s returned "
467
"None, not a URL." % hook_name)
468
self._activate_fallback_location(
469
url, possible_transports=possible_transports)
471
def __init__(self, *args, **kwargs):
472
self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
473
super(BzrBranch8, self).__init__(*args, **kwargs)
474
self._last_revision_info_cache = None
475
self._reference_info = None
477
def _clear_cached_state(self):
478
super(BzrBranch8, self)._clear_cached_state()
479
self._last_revision_info_cache = None
480
self._reference_info = None
482
def _check_history_violation(self, revision_id):
483
current_revid = self.last_revision()
484
last_revision = _mod_revision.ensure_null(current_revid)
485
if _mod_revision.is_null(last_revision):
487
graph = self.repository.get_graph()
488
for lh_ancestor in graph.iter_lefthand_ancestry(revision_id):
489
if lh_ancestor == current_revid:
491
raise errors.AppendRevisionsOnlyViolation(self.user_url)
493
def _gen_revision_history(self):
494
"""Generate the revision history from last revision
496
last_revno, last_revision = self.last_revision_info()
497
self._extend_partial_history(stop_index=last_revno - 1)
498
return list(reversed(self._partial_revision_history_cache))
500
def _set_parent_location(self, url):
501
"""Set the parent branch"""
502
with self.lock_write():
503
self._set_config_location(
504
'parent_location', url, make_relative=True)
506
def _get_parent_location(self):
507
"""Set the parent branch"""
508
with self.lock_read():
509
return self._get_config_location('parent_location')
511
def _set_all_reference_info(self, info_dict):
512
"""Replace all reference info stored in a branch.
514
:param info_dict: A dict of {file_id: (tree_path, branch_location)}
517
writer = rio.RioWriter(s)
518
for tree_path, (branch_location, file_id) in viewitems(info_dict):
519
stanza = rio.Stanza(tree_path=tree_path,
520
branch_location=branch_location)
521
if file_id is not None:
522
stanza.add('file_id', file_id)
523
writer.write_stanza(stanza)
524
with self.lock_write():
525
self._transport.put_bytes('references', s.getvalue())
526
self._reference_info = info_dict
528
def _get_all_reference_info(self):
529
"""Return all the reference info stored in a branch.
531
:return: A dict of {tree_path: (branch_location, file_id)}
533
with self.lock_read():
534
if self._reference_info is not None:
535
return self._reference_info
536
with self._transport.get('references') as rio_file:
537
stanzas = rio.read_stanzas(rio_file)
540
s['branch_location'],
541
s['file_id'].encode('ascii')
542
if 'file_id' in s else None)
544
self._reference_info = info_dict
547
def set_reference_info(self, tree_path, branch_location, file_id=None):
548
"""Set the branch location to use for a tree reference.
550
:param tree_path: The path of the tree reference in the tree.
551
:param branch_location: The location of the branch to retrieve tree
553
:param file_id: The file-id of the tree reference.
555
info_dict = self._get_all_reference_info()
556
info_dict[tree_path] = (branch_location, file_id)
557
if branch_location is None:
558
del info_dict[tree_path]
559
self._set_all_reference_info(info_dict)
561
def get_reference_info(self, path):
562
"""Get the tree_path and branch_location for a tree reference.
564
:return: a tuple of (branch_location, file_id)
566
return self._get_all_reference_info().get(path, (None, None))
568
def reference_parent(self, path, file_id=None, possible_transports=None):
569
"""Return the parent branch for a tree-reference file_id.
571
:param file_id: The file_id of the tree reference
572
:param path: The path of the file_id in the tree
573
:return: A branch associated with the file_id
575
branch_location = self.get_reference_info(path)[0]
576
if branch_location is None:
577
return Branch.reference_parent(self, path, file_id,
579
branch_location = urlutils.join(self.user_url, branch_location)
580
return Branch.open(branch_location,
581
possible_transports=possible_transports)
583
def set_push_location(self, location):
584
"""See Branch.set_push_location."""
585
self._set_config_location('push_location', location)
587
def set_bound_location(self, location):
588
"""See Branch.set_push_location."""
589
self._master_branch_cache = None
590
conf = self.get_config_stack()
592
if not conf.get('bound'):
595
conf.set('bound', 'False')
598
self._set_config_location('bound_location', location,
600
conf.set('bound', 'True')
603
def _get_bound_location(self, bound):
604
"""Return the bound location in the config file.
606
Return None if the bound parameter does not match"""
607
conf = self.get_config_stack()
608
if conf.get('bound') != bound:
610
return self._get_config_location('bound_location', config=conf)
612
def get_bound_location(self):
613
"""See Branch.get_bound_location."""
614
return self._get_bound_location(True)
616
def get_old_bound_location(self):
617
"""See Branch.get_old_bound_location"""
618
return self._get_bound_location(False)
620
def get_stacked_on_url(self):
621
# you can always ask for the URL; but you might not be able to use it
622
# if the repo can't support stacking.
623
# self._check_stackable_repo()
624
# stacked_on_location is only ever defined in branch.conf, so don't
625
# waste effort reading the whole stack of config files.
626
conf = _mod_config.BranchOnlyStack(self)
627
stacked_url = self._get_config_location('stacked_on_location',
629
if stacked_url is None:
630
raise errors.NotStacked(self)
631
# TODO(jelmer): Clean this up for pad.lv/1696545
632
if sys.version_info[0] == 2:
633
return stacked_url.encode('utf-8')
637
def get_rev_id(self, revno, history=None):
638
"""Find the revision id of the specified revno."""
640
return _mod_revision.NULL_REVISION
642
with self.lock_read():
643
last_revno, last_revision_id = self.last_revision_info()
644
if revno <= 0 or revno > last_revno:
645
raise errors.NoSuchRevision(self, revno)
647
if history is not None:
648
return history[revno - 1]
650
index = last_revno - revno
651
if len(self._partial_revision_history_cache) <= index:
652
self._extend_partial_history(stop_index=index)
653
if len(self._partial_revision_history_cache) > index:
654
return self._partial_revision_history_cache[index]
656
raise errors.NoSuchRevision(self, revno)
658
def revision_id_to_revno(self, revision_id):
659
"""Given a revision id, return its revno"""
660
if _mod_revision.is_null(revision_id):
662
with self.lock_read():
664
index = self._partial_revision_history_cache.index(revision_id)
667
self._extend_partial_history(stop_revision=revision_id)
668
except errors.RevisionNotPresent as e:
669
raise errors.GhostRevisionsHaveNoRevno(
670
revision_id, e.revision_id)
671
index = len(self._partial_revision_history_cache) - 1
673
raise errors.NoSuchRevision(self, revision_id)
674
if self._partial_revision_history_cache[index] != revision_id:
675
raise errors.NoSuchRevision(self, revision_id)
676
return self.revno() - index
679
class BzrBranch7(BzrBranch8):
680
"""A branch with support for a fallback repository."""
682
def set_reference_info(self, tree_path, branch_location, file_id=None):
683
Branch.set_reference_info(self, file_id, tree_path, branch_location)
685
def get_reference_info(self, path):
686
Branch.get_reference_info(self, path)
688
def reference_parent(self, path, file_id=None, possible_transports=None):
689
return Branch.reference_parent(
690
self, path, file_id, possible_transports)
693
class BzrBranch6(BzrBranch7):
694
"""See BzrBranchFormat6 for the capabilities of this branch.
696
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
700
def get_stacked_on_url(self):
701
raise UnstackableBranchFormat(self._format, self.user_url)
704
class BranchFormatMetadir(bzrdir.BzrFormat, BranchFormat):
705
"""Base class for branch formats that live in meta directories.
709
BranchFormat.__init__(self)
710
bzrdir.BzrFormat.__init__(self)
713
def find_format(klass, controldir, name=None):
714
"""Return the format for the branch object in controldir."""
716
transport = controldir.get_branch_transport(None, name=name)
717
except errors.NoSuchFile:
718
raise errors.NotBranchError(path=name, controldir=controldir)
720
format_string = transport.get_bytes("format")
721
except errors.NoSuchFile:
722
raise errors.NotBranchError(
723
path=transport.base, controldir=controldir)
724
return klass._find_format(format_registry, 'branch', format_string)
726
def _branch_class(self):
727
"""What class to instantiate on open calls."""
728
raise NotImplementedError(self._branch_class)
730
def _get_initial_config(self, append_revisions_only=None):
731
if append_revisions_only:
732
return b"append_revisions_only = True\n"
734
# Avoid writing anything if append_revisions_only is disabled,
735
# as that is the default.
738
def _initialize_helper(self, a_controldir, utf8_files, name=None,
740
"""Initialize a branch in a control dir, with specified files
742
:param a_controldir: The bzrdir to initialize the branch in
743
:param utf8_files: The files to create as a list of
744
(filename, content) tuples
745
:param name: Name of colocated branch to create, if any
746
:return: a branch in this format
749
name = a_controldir._get_selected_branch()
750
mutter('creating branch %r in %s', self, a_controldir.user_url)
751
branch_transport = a_controldir.get_branch_transport(self, name=name)
752
control_files = lockable_files.LockableFiles(branch_transport,
753
'lock', lockdir.LockDir)
754
control_files.create_lock()
755
control_files.lock_write()
757
utf8_files += [('format', self.as_string())]
758
for (filename, content) in utf8_files:
759
branch_transport.put_bytes(
761
mode=a_controldir._get_file_mode())
763
control_files.unlock()
764
branch = self.open(a_controldir, name, _found=True,
765
found_repository=repository)
766
self._run_post_branch_init_hooks(a_controldir, name, branch)
769
def open(self, a_controldir, name=None, _found=False,
770
ignore_fallbacks=False, found_repository=None,
771
possible_transports=None):
772
"""See BranchFormat.open()."""
774
name = a_controldir._get_selected_branch()
776
format = BranchFormatMetadir.find_format(a_controldir, name=name)
777
if format.__class__ != self.__class__:
778
raise AssertionError("wrong format %r found for %r" %
780
transport = a_controldir.get_branch_transport(None, name=name)
782
control_files = lockable_files.LockableFiles(transport, 'lock',
784
if found_repository is None:
785
found_repository = a_controldir.find_repository()
786
return self._branch_class()(
787
_format=self, _control_files=control_files, name=name,
788
a_controldir=a_controldir, _repository=found_repository,
789
ignore_fallbacks=ignore_fallbacks,
790
possible_transports=possible_transports)
791
except errors.NoSuchFile:
792
raise errors.NotBranchError(
793
path=transport.base, controldir=a_controldir)
796
def _matchingcontroldir(self):
797
ret = bzrdir.BzrDirMetaFormat1()
798
ret.set_branch_format(self)
801
def supports_tags(self):
804
def supports_leaving_lock(self):
807
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
809
BranchFormat.check_support_status(
810
self, allow_unsupported=allow_unsupported,
811
recommend_upgrade=recommend_upgrade, basedir=basedir)
812
bzrdir.BzrFormat.check_support_status(
813
self, allow_unsupported=allow_unsupported,
814
recommend_upgrade=recommend_upgrade, basedir=basedir)
817
class BzrBranchFormat6(BranchFormatMetadir):
818
"""Branch format with last-revision and tags.
820
Unlike previous formats, this has no explicit revision history. Instead,
821
this just stores the last-revision, and the left-hand history leading
822
up to there is the history.
824
This format was introduced in bzr 0.15
825
and became the default in 0.91.
828
def _branch_class(self):
832
def get_format_string(cls):
833
"""See BranchFormat.get_format_string()."""
834
return b"Bazaar Branch Format 6 (bzr 0.15)\n"
836
def get_format_description(self):
837
"""See BranchFormat.get_format_description()."""
838
return "Branch format 6"
840
def initialize(self, a_controldir, name=None, repository=None,
841
append_revisions_only=None):
842
"""Create a branch of this format in a_controldir."""
844
('last-revision', b'0 null:\n'),
845
('branch.conf', self._get_initial_config(append_revisions_only)),
848
return self._initialize_helper(
849
a_controldir, utf8_files, name, repository)
851
def make_tags(self, branch):
852
"""See breezy.branch.BranchFormat.make_tags()."""
853
return _mod_tag.BasicTags(branch)
855
def supports_set_append_revisions_only(self):
859
class BzrBranchFormat8(BranchFormatMetadir):
860
"""Metadir format supporting storing locations of subtree branches."""
862
def _branch_class(self):
866
def get_format_string(cls):
867
"""See BranchFormat.get_format_string()."""
868
return b"Bazaar Branch Format 8 (needs bzr 1.15)\n"
870
def get_format_description(self):
871
"""See BranchFormat.get_format_description()."""
872
return "Branch format 8"
874
def initialize(self, a_controldir, name=None, repository=None,
875
append_revisions_only=None):
876
"""Create a branch of this format in a_controldir."""
877
utf8_files = [('last-revision', b'0 null:\n'),
879
self._get_initial_config(append_revisions_only)),
883
return self._initialize_helper(
884
a_controldir, utf8_files, name, repository)
886
def make_tags(self, branch):
887
"""See breezy.branch.BranchFormat.make_tags()."""
888
return _mod_tag.BasicTags(branch)
890
def supports_set_append_revisions_only(self):
893
def supports_stacking(self):
896
supports_reference_locations = True
899
class BzrBranchFormat7(BranchFormatMetadir):
900
"""Branch format with last-revision, tags, and a stacked location pointer.
902
The stacked location pointer is passed down to the repository and requires
903
a repository format with supports_external_lookups = True.
905
This format was introduced in bzr 1.6.
908
def initialize(self, a_controldir, name=None, repository=None,
909
append_revisions_only=None):
910
"""Create a branch of this format in a_controldir."""
911
utf8_files = [('last-revision', b'0 null:\n'),
913
self._get_initial_config(append_revisions_only)),
916
return self._initialize_helper(
917
a_controldir, utf8_files, name, repository)
919
def _branch_class(self):
923
def get_format_string(cls):
924
"""See BranchFormat.get_format_string()."""
925
return b"Bazaar Branch Format 7 (needs bzr 1.6)\n"
927
def get_format_description(self):
928
"""See BranchFormat.get_format_description()."""
929
return "Branch format 7"
931
def supports_set_append_revisions_only(self):
934
def supports_stacking(self):
937
def make_tags(self, branch):
938
"""See breezy.branch.BranchFormat.make_tags()."""
939
return _mod_tag.BasicTags(branch)
941
supports_reference_locations = False
944
class BranchReferenceFormat(BranchFormatMetadir):
945
"""Bzr branch reference format.
947
Branch references are used in implementing checkouts, they
948
act as an alias to the real branch which is at some other url.
956
def get_format_string(cls):
957
"""See BranchFormat.get_format_string()."""
958
return b"Bazaar-NG Branch Reference Format 1\n"
960
def get_format_description(self):
961
"""See BranchFormat.get_format_description()."""
962
return "Checkout reference format 1"
964
def get_reference(self, a_controldir, name=None):
965
"""See BranchFormat.get_reference()."""
966
transport = a_controldir.get_branch_transport(None, name=name)
967
url = urlutils.split_segment_parameters(a_controldir.user_url)[0]
968
return urlutils.join(
969
url, transport.get_bytes('location').decode('utf-8'))
971
def _write_reference(self, a_controldir, transport, to_branch):
972
to_url = to_branch.user_url
973
if a_controldir.control_url == to_branch.controldir.control_url:
974
# Write relative paths for colocated branches, but absolute
975
# paths for everything else. This is for the benefit
976
# of older bzr versions that don't support relative paths.
977
to_url = urlutils.relative_url(
978
a_controldir.user_url, to_branch.user_url)
979
transport.put_bytes('location', to_url.encode('utf-8'))
981
def set_reference(self, a_controldir, name, to_branch):
982
"""See BranchFormat.set_reference()."""
983
transport = a_controldir.get_branch_transport(None, name=name)
984
self._write_reference(a_controldir, transport, to_branch)
986
def initialize(self, a_controldir, name=None, target_branch=None,
987
repository=None, append_revisions_only=None):
988
"""Create a branch of this format in a_controldir."""
989
if target_branch is None:
990
# this format does not implement branch itself, thus the implicit
991
# creation contract must see it as uninitializable
992
raise errors.UninitializableFormat(self)
993
mutter('creating branch reference in %s', a_controldir.user_url)
994
if a_controldir._format.fixed_components:
995
raise errors.IncompatibleFormat(self, a_controldir._format)
997
name = a_controldir._get_selected_branch()
998
branch_transport = a_controldir.get_branch_transport(self, name=name)
999
self._write_reference(a_controldir, branch_transport, target_branch)
1000
branch_transport.put_bytes('format', self.as_string())
1001
branch = self.open(a_controldir, name, _found=True,
1002
possible_transports=[target_branch.controldir.root_transport])
1003
self._run_post_branch_init_hooks(a_controldir, name, branch)
1006
def _make_reference_clone_function(format, a_branch):
1007
"""Create a clone() routine for a branch dynamically."""
1008
def clone(to_bzrdir, revision_id=None,
1009
repository_policy=None):
1010
"""See Branch.clone()."""
1011
return format.initialize(to_bzrdir, target_branch=a_branch)
1012
# cannot obey revision_id limits when cloning a reference ...
1013
# FIXME RBC 20060210 either nuke revision_id for clone, or
1014
# emit some sort of warning/error to the caller ?!
1017
def open(self, a_controldir, name=None, _found=False, location=None,
1018
possible_transports=None, ignore_fallbacks=False,
1019
found_repository=None):
1020
"""Return the branch that the branch reference in a_controldir points at.
1022
:param a_controldir: A BzrDir that contains a branch.
1023
:param name: Name of colocated branch to open, if any
1024
:param _found: a private parameter, do not use it. It is used to
1025
indicate if format probing has already be done.
1026
:param ignore_fallbacks: when set, no fallback branches will be opened
1027
(if there are any). Default is to open fallbacks.
1028
:param location: The location of the referenced branch. If
1029
unspecified, this will be determined from the branch reference in
1031
:param possible_transports: An optional reusable transports list.
1034
name = a_controldir._get_selected_branch()
1036
format = BranchFormatMetadir.find_format(a_controldir, name=name)
1037
if format.__class__ != self.__class__:
1038
raise AssertionError("wrong format %r found for %r" %
1040
if location is None:
1041
location = self.get_reference(a_controldir, name)
1042
real_bzrdir = controldir.ControlDir.open(
1043
location, possible_transports=possible_transports)
1044
result = real_bzrdir.open_branch(
1045
ignore_fallbacks=ignore_fallbacks,
1046
possible_transports=possible_transports)
1047
# this changes the behaviour of result.clone to create a new reference
1048
# rather than a copy of the content of the branch.
1049
# I did not use a proxy object because that needs much more extensive
1050
# testing, and we are only changing one behaviour at the moment.
1051
# If we decide to alter more behaviours - i.e. the implicit nickname
1052
# then this should be refactored to introduce a tested proxy branch
1053
# and a subclass of that for use in overriding clone() and ....
1055
result.clone = self._make_reference_clone_function(result)
1059
class Converter5to6(object):
1060
"""Perform an in-place upgrade of format 5 to format 6"""
1062
def convert(self, branch):
1063
# Data for 5 and 6 can peacefully coexist.
1064
format = BzrBranchFormat6()
1065
new_branch = format.open(branch.controldir, _found=True)
1067
# Copy source data into target
1068
new_branch._write_last_revision_info(*branch.last_revision_info())
1069
with new_branch.lock_write():
1070
new_branch.set_parent(branch.get_parent())
1071
new_branch.set_bound_location(branch.get_bound_location())
1072
new_branch.set_push_location(branch.get_push_location())
1074
# New branch has no tags by default
1075
new_branch.tags._set_tag_dict({})
1077
# Copying done; now update target format
1078
new_branch._transport.put_bytes(
1079
'format', format.as_string(),
1080
mode=new_branch.controldir._get_file_mode())
1082
# Clean up old files
1083
new_branch._transport.delete('revision-history')
1084
with branch.lock_write():
1086
branch.set_parent(None)
1087
except errors.NoSuchFile:
1089
branch.set_bound_location(None)
1092
class Converter6to7(object):
1093
"""Perform an in-place upgrade of format 6 to format 7"""
1095
def convert(self, branch):
1096
format = BzrBranchFormat7()
1097
branch._set_config_location('stacked_on_location', '')
1098
# update target format
1099
branch._transport.put_bytes('format', format.as_string())
1102
class Converter7to8(object):
1103
"""Perform an in-place upgrade of format 7 to format 8"""
1105
def convert(self, branch):
1106
format = BzrBranchFormat8()
1107
branch._transport.put_bytes('references', b'')
1108
# update target format
1109
branch._transport.put_bytes('format', format.as_string())