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
543
with self._transport.get('references') as rio_file:
544
stanzas = rio.read_stanzas(rio_file)
547
s['branch_location'],
548
s['file_id'].encode('ascii')
549
if 'file_id' in s else None)
551
self._reference_info = info_dict
554
def set_reference_info(self, tree_path, branch_location, file_id=None):
555
"""Set the branch location to use for a tree reference.
557
:param tree_path: The path of the tree reference in the tree.
558
:param branch_location: The location of the branch to retrieve tree
560
:param file_id: The file-id of the tree reference.
562
info_dict = self._get_all_reference_info()
563
info_dict[tree_path] = (branch_location, file_id)
564
if branch_location is None:
565
del info_dict[tree_path]
566
self._set_all_reference_info(info_dict)
568
def get_reference_info(self, path):
569
"""Get the tree_path and branch_location for a tree reference.
571
:return: a tuple of (branch_location, file_id)
573
return self._get_all_reference_info().get(path, (None, None))
575
def reference_parent(self, path, file_id=None, possible_transports=None):
576
"""Return the parent branch for a tree-reference file_id.
578
:param file_id: The file_id of the tree reference
579
:param path: The path of the file_id in the tree
580
:return: A branch associated with the file_id
582
branch_location = self.get_reference_info(path)[0]
583
if branch_location is None:
584
return Branch.reference_parent(self, path, file_id,
586
branch_location = urlutils.join(self.user_url, branch_location)
587
return Branch.open(branch_location,
588
possible_transports=possible_transports)
590
def set_push_location(self, location):
591
"""See Branch.set_push_location."""
592
self._set_config_location('push_location', location)
594
def set_bound_location(self, location):
595
"""See Branch.set_push_location."""
596
self._master_branch_cache = None
597
conf = self.get_config_stack()
599
if not conf.get('bound'):
602
conf.set('bound', 'False')
605
self._set_config_location('bound_location', location,
607
conf.set('bound', 'True')
610
def _get_bound_location(self, bound):
611
"""Return the bound location in the config file.
613
Return None if the bound parameter does not match"""
614
conf = self.get_config_stack()
615
if conf.get('bound') != bound:
617
return self._get_config_location('bound_location', config=conf)
619
def get_bound_location(self):
620
"""See Branch.get_bound_location."""
621
return self._get_bound_location(True)
623
def get_old_bound_location(self):
624
"""See Branch.get_old_bound_location"""
625
return self._get_bound_location(False)
627
def get_stacked_on_url(self):
628
# you can always ask for the URL; but you might not be able to use it
629
# if the repo can't support stacking.
630
# self._check_stackable_repo()
631
# stacked_on_location is only ever defined in branch.conf, so don't
632
# waste effort reading the whole stack of config files.
633
conf = _mod_config.BranchOnlyStack(self)
634
stacked_url = self._get_config_location('stacked_on_location',
636
if stacked_url is None:
637
raise errors.NotStacked(self)
638
# TODO(jelmer): Clean this up for pad.lv/1696545
639
if sys.version_info[0] == 2:
640
return stacked_url.encode('utf-8')
644
def get_rev_id(self, revno, history=None):
645
"""Find the revision id of the specified revno."""
647
return _mod_revision.NULL_REVISION
649
with self.lock_read():
650
last_revno, last_revision_id = self.last_revision_info()
651
if revno <= 0 or revno > last_revno:
652
raise errors.RevnoOutOfBounds(revno, (0, last_revno))
654
if history is not None:
655
return history[revno - 1]
657
index = last_revno - revno
658
if len(self._partial_revision_history_cache) <= index:
659
self._extend_partial_history(stop_index=index)
660
if len(self._partial_revision_history_cache) > index:
661
return self._partial_revision_history_cache[index]
663
raise errors.NoSuchRevision(self, revno)
665
def revision_id_to_revno(self, revision_id):
666
"""Given a revision id, return its revno"""
667
if _mod_revision.is_null(revision_id):
669
with self.lock_read():
671
index = self._partial_revision_history_cache.index(revision_id)
674
self._extend_partial_history(stop_revision=revision_id)
675
except errors.RevisionNotPresent as e:
676
raise errors.GhostRevisionsHaveNoRevno(
677
revision_id, e.revision_id)
678
index = len(self._partial_revision_history_cache) - 1
680
raise errors.NoSuchRevision(self, revision_id)
681
if self._partial_revision_history_cache[index] != revision_id:
682
raise errors.NoSuchRevision(self, revision_id)
683
return self.revno() - index
686
class BzrBranch7(BzrBranch8):
687
"""A branch with support for a fallback repository."""
689
def set_reference_info(self, tree_path, branch_location, file_id=None):
690
Branch.set_reference_info(self, file_id, tree_path, branch_location)
692
def get_reference_info(self, path):
693
Branch.get_reference_info(self, path)
695
def reference_parent(self, path, file_id=None, possible_transports=None):
696
return Branch.reference_parent(
697
self, path, file_id, possible_transports)
700
class BzrBranch6(BzrBranch7):
701
"""See BzrBranchFormat6 for the capabilities of this branch.
703
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
707
def get_stacked_on_url(self):
708
raise UnstackableBranchFormat(self._format, self.user_url)
711
class BranchFormatMetadir(bzrdir.BzrFormat, BranchFormat):
712
"""Base class for branch formats that live in meta directories.
716
BranchFormat.__init__(self)
717
bzrdir.BzrFormat.__init__(self)
720
def find_format(klass, controldir, name=None):
721
"""Return the format for the branch object in controldir."""
723
transport = controldir.get_branch_transport(None, name=name)
724
except errors.NoSuchFile:
725
raise errors.NotBranchError(path=name, controldir=controldir)
727
format_string = transport.get_bytes("format")
728
except errors.NoSuchFile:
729
raise errors.NotBranchError(
730
path=transport.base, controldir=controldir)
731
return klass._find_format(format_registry, 'branch', format_string)
733
def _branch_class(self):
734
"""What class to instantiate on open calls."""
735
raise NotImplementedError(self._branch_class)
737
def _get_initial_config(self, append_revisions_only=None):
738
if append_revisions_only:
739
return b"append_revisions_only = True\n"
741
# Avoid writing anything if append_revisions_only is disabled,
742
# as that is the default.
745
def _initialize_helper(self, a_controldir, utf8_files, name=None,
747
"""Initialize a branch in a control dir, with specified files
749
:param a_controldir: The bzrdir to initialize the branch in
750
:param utf8_files: The files to create as a list of
751
(filename, content) tuples
752
:param name: Name of colocated branch to create, if any
753
:return: a branch in this format
756
name = a_controldir._get_selected_branch()
757
mutter('creating branch %r in %s', self, a_controldir.user_url)
758
branch_transport = a_controldir.get_branch_transport(self, name=name)
759
control_files = lockable_files.LockableFiles(branch_transport,
760
'lock', lockdir.LockDir)
761
control_files.create_lock()
762
control_files.lock_write()
764
utf8_files += [('format', self.as_string())]
765
for (filename, content) in utf8_files:
766
branch_transport.put_bytes(
768
mode=a_controldir._get_file_mode())
770
control_files.unlock()
771
branch = self.open(a_controldir, name, _found=True,
772
found_repository=repository)
773
self._run_post_branch_init_hooks(a_controldir, name, branch)
776
def open(self, a_controldir, name=None, _found=False,
777
ignore_fallbacks=False, found_repository=None,
778
possible_transports=None):
779
"""See BranchFormat.open()."""
781
name = a_controldir._get_selected_branch()
783
format = BranchFormatMetadir.find_format(a_controldir, name=name)
784
if format.__class__ != self.__class__:
785
raise AssertionError("wrong format %r found for %r" %
787
transport = a_controldir.get_branch_transport(None, name=name)
789
control_files = lockable_files.LockableFiles(transport, 'lock',
791
if found_repository is None:
792
found_repository = a_controldir.find_repository()
793
return self._branch_class()(
794
_format=self, _control_files=control_files, name=name,
795
a_controldir=a_controldir, _repository=found_repository,
796
ignore_fallbacks=ignore_fallbacks,
797
possible_transports=possible_transports)
798
except errors.NoSuchFile:
799
raise errors.NotBranchError(
800
path=transport.base, controldir=a_controldir)
803
def _matchingcontroldir(self):
804
ret = bzrdir.BzrDirMetaFormat1()
805
ret.set_branch_format(self)
808
def supports_tags(self):
811
def supports_leaving_lock(self):
814
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
816
BranchFormat.check_support_status(
817
self, allow_unsupported=allow_unsupported,
818
recommend_upgrade=recommend_upgrade, basedir=basedir)
819
bzrdir.BzrFormat.check_support_status(
820
self, allow_unsupported=allow_unsupported,
821
recommend_upgrade=recommend_upgrade, basedir=basedir)
824
class BzrBranchFormat6(BranchFormatMetadir):
825
"""Branch format with last-revision and tags.
827
Unlike previous formats, this has no explicit revision history. Instead,
828
this just stores the last-revision, and the left-hand history leading
829
up to there is the history.
831
This format was introduced in bzr 0.15
832
and became the default in 0.91.
835
def _branch_class(self):
839
def get_format_string(cls):
840
"""See BranchFormat.get_format_string()."""
841
return b"Bazaar Branch Format 6 (bzr 0.15)\n"
843
def get_format_description(self):
844
"""See BranchFormat.get_format_description()."""
845
return "Branch format 6"
847
def initialize(self, a_controldir, name=None, repository=None,
848
append_revisions_only=None):
849
"""Create a branch of this format in a_controldir."""
851
('last-revision', b'0 null:\n'),
852
('branch.conf', self._get_initial_config(append_revisions_only)),
855
return self._initialize_helper(
856
a_controldir, utf8_files, name, repository)
858
def make_tags(self, branch):
859
"""See breezy.branch.BranchFormat.make_tags()."""
860
return _mod_tag.BasicTags(branch)
862
def supports_set_append_revisions_only(self):
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
supports_reference_locations = False
951
class BranchReferenceFormat(BranchFormatMetadir):
952
"""Bzr branch reference format.
954
Branch references are used in implementing checkouts, they
955
act as an alias to the real branch which is at some other url.
963
def get_format_string(cls):
964
"""See BranchFormat.get_format_string()."""
965
return b"Bazaar-NG Branch Reference Format 1\n"
967
def get_format_description(self):
968
"""See BranchFormat.get_format_description()."""
969
return "Checkout reference format 1"
971
def get_reference(self, a_controldir, name=None):
972
"""See BranchFormat.get_reference()."""
973
transport = a_controldir.get_branch_transport(None, name=name)
974
url = urlutils.split_segment_parameters(a_controldir.user_url)[0]
975
return urlutils.join(
976
url, transport.get_bytes('location').decode('utf-8'))
978
def _write_reference(self, a_controldir, transport, to_branch):
979
to_url = to_branch.user_url
980
# Ideally, we'd write a relative path here for the benefit of colocated
981
# branches - so that moving a control directory doesn't break
982
# any references to colocated branches. Unfortunately, bzr
983
# does not support relative URLs. See pad.lv/1803845 -- jelmer
984
# to_url = urlutils.relative_url(
985
# a_controldir.user_url, to_branch.user_url)
986
transport.put_bytes('location', to_url.encode('utf-8'))
988
def set_reference(self, a_controldir, name, to_branch):
989
"""See BranchFormat.set_reference()."""
990
transport = a_controldir.get_branch_transport(None, name=name)
991
self._write_reference(a_controldir, transport, to_branch)
993
def initialize(self, a_controldir, name=None, target_branch=None,
994
repository=None, append_revisions_only=None):
995
"""Create a branch of this format in a_controldir."""
996
if target_branch is None:
997
# this format does not implement branch itself, thus the implicit
998
# creation contract must see it as uninitializable
999
raise errors.UninitializableFormat(self)
1000
mutter('creating branch reference in %s', a_controldir.user_url)
1001
if a_controldir._format.fixed_components:
1002
raise errors.IncompatibleFormat(self, a_controldir._format)
1004
name = a_controldir._get_selected_branch()
1005
branch_transport = a_controldir.get_branch_transport(self, name=name)
1006
self._write_reference(a_controldir, branch_transport, target_branch)
1007
branch_transport.put_bytes('format', self.as_string())
1008
branch = self.open(a_controldir, name, _found=True,
1009
possible_transports=[target_branch.controldir.root_transport])
1010
self._run_post_branch_init_hooks(a_controldir, name, branch)
1013
def _make_reference_clone_function(format, a_branch):
1014
"""Create a clone() routine for a branch dynamically."""
1015
def clone(to_bzrdir, revision_id=None,
1016
repository_policy=None):
1017
"""See Branch.clone()."""
1018
return format.initialize(to_bzrdir, target_branch=a_branch)
1019
# cannot obey revision_id limits when cloning a reference ...
1020
# FIXME RBC 20060210 either nuke revision_id for clone, or
1021
# emit some sort of warning/error to the caller ?!
1024
def open(self, a_controldir, name=None, _found=False, location=None,
1025
possible_transports=None, ignore_fallbacks=False,
1026
found_repository=None):
1027
"""Return the branch that the branch reference in a_controldir points at.
1029
:param a_controldir: A BzrDir that contains a branch.
1030
:param name: Name of colocated branch to open, if any
1031
:param _found: a private parameter, do not use it. It is used to
1032
indicate if format probing has already be done.
1033
:param ignore_fallbacks: when set, no fallback branches will be opened
1034
(if there are any). Default is to open fallbacks.
1035
:param location: The location of the referenced branch. If
1036
unspecified, this will be determined from the branch reference in
1038
:param possible_transports: An optional reusable transports list.
1041
name = a_controldir._get_selected_branch()
1043
format = BranchFormatMetadir.find_format(a_controldir, name=name)
1044
if format.__class__ != self.__class__:
1045
raise AssertionError("wrong format %r found for %r" %
1047
if location is None:
1048
location = self.get_reference(a_controldir, name)
1049
real_bzrdir = controldir.ControlDir.open(
1050
location, possible_transports=possible_transports)
1051
result = real_bzrdir.open_branch(
1052
ignore_fallbacks=ignore_fallbacks,
1053
possible_transports=possible_transports)
1054
# this changes the behaviour of result.clone to create a new reference
1055
# rather than a copy of the content of the branch.
1056
# I did not use a proxy object because that needs much more extensive
1057
# testing, and we are only changing one behaviour at the moment.
1058
# If we decide to alter more behaviours - i.e. the implicit nickname
1059
# then this should be refactored to introduce a tested proxy branch
1060
# and a subclass of that for use in overriding clone() and ....
1062
result.clone = self._make_reference_clone_function(result)
1066
class Converter5to6(object):
1067
"""Perform an in-place upgrade of format 5 to format 6"""
1069
def convert(self, branch):
1070
# Data for 5 and 6 can peacefully coexist.
1071
format = BzrBranchFormat6()
1072
new_branch = format.open(branch.controldir, _found=True)
1074
# Copy source data into target
1075
new_branch._write_last_revision_info(*branch.last_revision_info())
1076
with new_branch.lock_write():
1077
new_branch.set_parent(branch.get_parent())
1078
new_branch.set_bound_location(branch.get_bound_location())
1079
new_branch.set_push_location(branch.get_push_location())
1081
# New branch has no tags by default
1082
new_branch.tags._set_tag_dict({})
1084
# Copying done; now update target format
1085
new_branch._transport.put_bytes(
1086
'format', format.as_string(),
1087
mode=new_branch.controldir._get_file_mode())
1089
# Clean up old files
1090
new_branch._transport.delete('revision-history')
1091
with branch.lock_write():
1093
branch.set_parent(None)
1094
except errors.NoSuchFile:
1096
branch.set_bound_location(None)
1099
class Converter6to7(object):
1100
"""Perform an in-place upgrade of format 6 to format 7"""
1102
def convert(self, branch):
1103
format = BzrBranchFormat7()
1104
branch._set_config_location('stacked_on_location', '')
1105
# update target format
1106
branch._transport.put_bytes('format', format.as_string())
1109
class Converter7to8(object):
1110
"""Perform an in-place upgrade of format 7 to format 8"""
1112
def convert(self, branch):
1113
format = BzrBranchFormat8()
1114
branch._transport.put_bytes('references', b'')
1115
# update target format
1116
branch._transport.put_bytes('format', format.as_string())