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