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
20
from ..lazy_import import lazy_import
21
lazy_import(globals(), """
24
config as _mod_config,
37
revision as _mod_revision,
40
from ..branch import (
43
BranchWriteLockResult,
45
UnstackableBranchFormat,
47
from ..decorators import (
50
from ..lock import _RelockDebugMixin, LogicalLockResult
51
from ..sixish import (
60
class BzrBranch(Branch, _RelockDebugMixin):
61
"""A branch stored in the actual filesystem.
63
Note that it's "local" in the context of the filesystem; it doesn't
64
really matter if it's on an nfs/smb/afs/coda/... share, as long as
65
it's writable, and can be accessed via the normal filesystem API.
67
:ivar _transport: Transport for file operations on this branch's
68
control files, typically pointing to the .bzr/branch directory.
69
:ivar repository: Repository for this branch.
70
:ivar base: The url of the base directory for this branch; the one
71
containing the .bzr directory.
72
:ivar name: Optional colocated branch name as it exists in the control
76
def __init__(self, _format=None,
77
_control_files=None, a_controldir=None, name=None,
78
_repository=None, ignore_fallbacks=False,
79
possible_transports=None):
80
"""Create new branch object at a particular location."""
81
if a_controldir is None:
82
raise ValueError('a_controldir must be supplied')
84
raise ValueError('name must be supplied')
85
self.controldir = a_controldir
86
self._user_transport = self.controldir.transport.clone('..')
88
self._user_transport.set_segment_parameter(
89
"branch", urlutils.escape(name))
90
self._base = self._user_transport.base
92
self._format = _format
93
if _control_files is None:
94
raise ValueError('BzrBranch _control_files is None')
95
self.control_files = _control_files
96
self._transport = _control_files._transport
97
self.repository = _repository
98
self.conf_store = None
99
Branch.__init__(self, possible_transports)
100
self._tags_bytes = None
103
return '%s(%s)' % (self.__class__.__name__, self.user_url)
108
"""Returns the directory containing the control directory."""
111
base = property(_get_base, doc="The URL for the root of this branch.")
114
def user_transport(self):
115
return self._user_transport
117
def _get_config(self):
118
"""Get the concrete config for just the config in this branch.
120
This is not intended for client use; see Branch.get_config for the
125
:return: An object supporting get_option and set_option.
127
return _mod_config.TransportConfig(self._transport, 'branch.conf')
129
def _get_config_store(self):
130
if self.conf_store is None:
131
self.conf_store = _mod_config.BranchStore(self)
132
return self.conf_store
134
def _uncommitted_branch(self):
135
"""Return the branch that may contain uncommitted changes."""
136
master = self.get_master_branch()
137
if master is not None:
142
def store_uncommitted(self, creator):
143
"""Store uncommitted changes from a ShelfCreator.
145
:param creator: The ShelfCreator containing uncommitted changes, or
146
None to delete any stored changes.
147
:raises: ChangesAlreadyStored if the branch already has changes.
149
branch = self._uncommitted_branch()
151
branch._transport.delete('stored-transform')
153
if branch._transport.has('stored-transform'):
154
raise errors.ChangesAlreadyStored
155
transform = BytesIO()
156
creator.write_shelf(transform)
158
branch._transport.put_file('stored-transform', transform)
160
def get_unshelver(self, tree):
161
"""Return a shelf.Unshelver for this branch and tree.
163
:param tree: The tree to use to construct the Unshelver.
164
:return: an Unshelver or None if no changes are stored.
166
branch = self._uncommitted_branch()
168
transform = branch._transport.get('stored-transform')
169
except errors.NoSuchFile:
171
return shelf.Unshelver.from_tree_and_shelf(tree, transform)
174
return self.control_files.is_locked()
176
def lock_write(self, token=None):
177
"""Lock the branch for write operations.
179
:param token: A token to permit reacquiring a previously held and
181
:return: A BranchWriteLockResult.
183
if not self.is_locked():
185
self.repository._warn_if_deprecated(self)
186
self.repository.lock_write()
191
return BranchWriteLockResult(
193
self.control_files.lock_write(token=token))
196
self.repository.unlock()
200
"""Lock the branch for read operations.
202
:return: A breezy.lock.LogicalLockResult.
204
if not self.is_locked():
206
self.repository._warn_if_deprecated(self)
207
self.repository.lock_read()
212
self.control_files.lock_read()
213
return LogicalLockResult(self.unlock)
216
self.repository.unlock()
219
@only_raises(errors.LockNotHeld, errors.LockBroken)
221
if self.control_files._lock_count == 1 and self.conf_store is not None:
222
self.conf_store.save_changes()
224
self.control_files.unlock()
226
if not self.control_files.is_locked():
227
self.repository.unlock()
228
# we just released the lock
229
self._clear_cached_state()
231
def peek_lock_mode(self):
232
if self.control_files._lock_count == 0:
235
return self.control_files._lock_mode
237
def get_physical_lock_status(self):
238
return self.control_files.get_physical_lock_status()
240
def set_last_revision_info(self, revno, revision_id):
241
if not revision_id or not isinstance(revision_id, bytes):
242
raise errors.InvalidRevisionId(
243
revision_id=revision_id, branch=self)
244
revision_id = _mod_revision.ensure_null(revision_id)
245
with self.lock_write():
246
old_revno, old_revid = self.last_revision_info()
247
if self.get_append_revisions_only():
248
self._check_history_violation(revision_id)
249
self._run_pre_change_branch_tip_hooks(revno, revision_id)
250
self._write_last_revision_info(revno, revision_id)
251
self._clear_cached_state()
252
self._last_revision_info_cache = revno, revision_id
253
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
255
def basis_tree(self):
256
"""See Branch.basis_tree."""
257
return self.repository.revision_tree(self.last_revision())
259
def _get_parent_location(self):
260
_locs = ['parent', 'pull', 'x-pull']
263
return self._transport.get_bytes(l).strip('\n')
264
except errors.NoSuchFile:
268
def get_stacked_on_url(self):
269
raise UnstackableBranchFormat(self._format, self.user_url)
271
def set_push_location(self, location):
272
"""See Branch.set_push_location."""
273
self.get_config().set_user_option(
274
'push_location', location,
275
store=_mod_config.STORE_LOCATION_NORECURSE)
277
def _set_parent_location(self, url):
279
self._transport.delete('parent')
281
self._transport.put_bytes('parent', url + '\n',
282
mode=self.controldir._get_file_mode())
285
"""If bound, unbind"""
286
with self.lock_write():
287
return self.set_bound_location(None)
289
def bind(self, other):
290
"""Bind this branch to the branch other.
292
This does not push or pull data between the branches, though it does
293
check for divergence to raise an error when the branches are not
294
either the same, or one a prefix of the other. That behaviour may not
295
be useful, so that check may be removed in future.
297
:param other: The branch to bind to
300
# TODO: jam 20051230 Consider checking if the target is bound
301
# It is debatable whether you should be able to bind to
302
# a branch which is itself bound.
303
# Committing is obviously forbidden,
304
# but binding itself may not be.
305
# Since we *have* to check at commit time, we don't
306
# *need* to check here
308
# we want to raise diverged if:
309
# last_rev is not in the other_last_rev history, AND
310
# other_last_rev is not in our history, and do it without pulling
312
with self.lock_write():
313
self.set_bound_location(other.base)
315
def get_bound_location(self):
317
return self._transport.get_bytes('bound')[:-1]
318
except errors.NoSuchFile:
321
def get_master_branch(self, possible_transports=None):
322
"""Return the branch we are bound to.
324
:return: Either a Branch, or None
326
with self.lock_read():
327
if self._master_branch_cache is None:
328
self._master_branch_cache = self._get_master_branch(
330
return self._master_branch_cache
332
def _get_master_branch(self, possible_transports):
333
bound_loc = self.get_bound_location()
337
return Branch.open(bound_loc,
338
possible_transports=possible_transports)
339
except (errors.NotBranchError, errors.ConnectionError) as e:
340
raise errors.BoundBranchConnectionFailure(
343
def set_bound_location(self, location):
344
"""Set the target where this branch is bound to.
346
:param location: URL to the target branch
348
with self.lock_write():
349
self._master_branch_cache = None
351
self._transport.put_bytes('bound', location.encode('utf-8')+b'\n',
352
mode=self.controldir._get_file_mode())
355
self._transport.delete('bound')
356
except errors.NoSuchFile:
360
def update(self, possible_transports=None):
361
"""Synchronise this branch with the master branch if any.
363
:return: None or the last_revision that was pivoted out during the
366
with self.lock_write():
367
master = self.get_master_branch(possible_transports)
368
if master is not None:
369
old_tip = _mod_revision.ensure_null(self.last_revision())
370
self.pull(master, overwrite=True)
371
if self.repository.get_graph().is_ancestor(old_tip,
372
_mod_revision.ensure_null(self.last_revision())):
377
def _read_last_revision_info(self):
378
revision_string = self._transport.get_bytes('last-revision')
379
revno, revision_id = revision_string.rstrip(b'\n').split(b' ', 1)
380
revision_id = cache_utf8.get_cached_utf8(revision_id)
382
return revno, revision_id
384
def _write_last_revision_info(self, revno, revision_id):
385
"""Simply write out the revision id, with no checks.
387
Use set_last_revision_info to perform this safely.
389
Does not update the revision_history cache.
391
revision_id = _mod_revision.ensure_null(revision_id)
392
out_string = b'%d %s\n' % (revno, revision_id)
393
self._transport.put_bytes('last-revision', out_string,
394
mode=self.controldir._get_file_mode())
396
def update_feature_flags(self, updated_flags):
397
"""Update the feature flags for this branch.
399
:param updated_flags: Dictionary mapping feature names to necessities
400
A necessity can be None to indicate the feature should be removed
402
with self.lock_write():
403
self._format._update_feature_flags(updated_flags)
404
self.control_transport.put_bytes('format', self._format.as_string())
406
def _get_tags_bytes(self):
407
"""Get the bytes of a serialised tags dict.
409
Note that not all branches support tags, nor do all use the same tags
410
logic: this method is specific to BasicTags. Other tag implementations
411
may use the same method name and behave differently, safely, because
412
of the double-dispatch via
413
format.make_tags->tags_instance->get_tags_dict.
415
:return: The bytes of the tags file.
416
:seealso: Branch._set_tags_bytes.
418
with self.lock_read():
419
if self._tags_bytes is None:
420
self._tags_bytes = self._transport.get_bytes('tags')
421
return self._tags_bytes
423
def _set_tags_bytes(self, bytes):
424
"""Mirror method for _get_tags_bytes.
426
:seealso: Branch._get_tags_bytes.
428
with self.lock_write():
429
self._tags_bytes = bytes
430
return self._transport.put_bytes('tags', bytes)
432
def _clear_cached_state(self):
433
super(BzrBranch, self)._clear_cached_state()
434
self._tags_bytes = None
437
class BzrBranch8(BzrBranch):
438
"""A branch that stores tree-reference locations."""
440
def _open_hook(self, possible_transports=None):
441
if self._ignore_fallbacks:
443
if possible_transports is None:
444
possible_transports = [self.controldir.root_transport]
446
url = self.get_stacked_on_url()
447
except (errors.UnstackableRepositoryFormat, errors.NotStacked,
448
UnstackableBranchFormat):
451
for hook in Branch.hooks['transform_fallback_location']:
452
url = hook(self, url)
454
hook_name = Branch.hooks.get_hook_name(hook)
455
raise AssertionError(
456
"'transform_fallback_location' hook %s returned "
457
"None, not a URL." % hook_name)
458
self._activate_fallback_location(url,
459
possible_transports=possible_transports)
461
def __init__(self, *args, **kwargs):
462
self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
463
super(BzrBranch8, self).__init__(*args, **kwargs)
464
self._last_revision_info_cache = None
465
self._reference_info = None
467
def _clear_cached_state(self):
468
super(BzrBranch8, self)._clear_cached_state()
469
self._last_revision_info_cache = None
470
self._reference_info = None
472
def _check_history_violation(self, revision_id):
473
current_revid = self.last_revision()
474
last_revision = _mod_revision.ensure_null(current_revid)
475
if _mod_revision.is_null(last_revision):
477
graph = self.repository.get_graph()
478
for lh_ancestor in graph.iter_lefthand_ancestry(revision_id):
479
if lh_ancestor == current_revid:
481
raise errors.AppendRevisionsOnlyViolation(self.user_url)
483
def _gen_revision_history(self):
484
"""Generate the revision history from last revision
486
last_revno, last_revision = self.last_revision_info()
487
self._extend_partial_history(stop_index=last_revno-1)
488
return list(reversed(self._partial_revision_history_cache))
490
def _set_parent_location(self, url):
491
"""Set the parent branch"""
492
with self.lock_write():
493
self._set_config_location('parent_location', url, make_relative=True)
495
def _get_parent_location(self):
496
"""Set the parent branch"""
497
with self.lock_read():
498
return self._get_config_location('parent_location')
500
def _set_all_reference_info(self, info_dict):
501
"""Replace all reference info stored in a branch.
503
:param info_dict: A dict of {file_id: (tree_path, branch_location)}
506
writer = rio.RioWriter(s)
507
for tree_path, ( branch_location, file_id) in viewitems(info_dict):
508
stanza = rio.Stanza(tree_path=tree_path,
509
branch_location=branch_location)
510
if file_id is not None:
511
stanza.add('file_id', file_id)
512
writer.write_stanza(stanza)
513
with self.lock_write():
514
self._transport.put_bytes('references', s.getvalue())
515
self._reference_info = info_dict
517
def _get_all_reference_info(self):
518
"""Return all the reference info stored in a branch.
520
:return: A dict of {tree_path: (branch_location, file_id)}
522
with self.lock_read():
523
if self._reference_info is not None:
524
return self._reference_info
525
with self._transport.get('references') as rio_file:
526
stanzas = rio.read_stanzas(rio_file)
529
s['branch_location'],
530
s['file_id'].encode('ascii') if 'file_id' in s else None)
532
self._reference_info = info_dict
535
def set_reference_info(self, tree_path, branch_location, file_id=None):
536
"""Set the branch location to use for a tree reference.
538
:param tree_path: The path of the tree reference in the tree.
539
:param branch_location: The location of the branch to retrieve tree
541
:param file_id: The file-id of the tree reference.
543
info_dict = self._get_all_reference_info()
544
info_dict[tree_path] = (branch_location, file_id)
545
if branch_location is None:
546
del info_dict[tree_path]
547
self._set_all_reference_info(info_dict)
549
def get_reference_info(self, path):
550
"""Get the tree_path and branch_location for a tree reference.
552
:return: a tuple of (branch_location, file_id)
554
return self._get_all_reference_info().get(path, (None, None))
556
def reference_parent(self, path, file_id=None, possible_transports=None):
557
"""Return the parent branch for a tree-reference file_id.
559
:param file_id: The file_id of the tree reference
560
:param path: The path of the file_id in the tree
561
:return: A branch associated with the file_id
563
branch_location = self.get_reference_info(path)[0]
564
if branch_location is None:
565
return Branch.reference_parent(self, path, file_id,
567
branch_location = urlutils.join(self.user_url, branch_location)
568
return Branch.open(branch_location,
569
possible_transports=possible_transports)
571
def set_push_location(self, location):
572
"""See Branch.set_push_location."""
573
self._set_config_location('push_location', location)
575
def set_bound_location(self, location):
576
"""See Branch.set_push_location."""
577
self._master_branch_cache = None
579
conf = self.get_config_stack()
581
if not conf.get('bound'):
584
conf.set('bound', 'False')
587
self._set_config_location('bound_location', location,
589
conf.set('bound', 'True')
592
def _get_bound_location(self, bound):
593
"""Return the bound location in the config file.
595
Return None if the bound parameter does not match"""
596
conf = self.get_config_stack()
597
if conf.get('bound') != bound:
599
return self._get_config_location('bound_location', config=conf)
601
def get_bound_location(self):
602
"""See Branch.get_bound_location."""
603
return self._get_bound_location(True)
605
def get_old_bound_location(self):
606
"""See Branch.get_old_bound_location"""
607
return self._get_bound_location(False)
609
def get_stacked_on_url(self):
610
# you can always ask for the URL; but you might not be able to use it
611
# if the repo can't support stacking.
612
## self._check_stackable_repo()
613
# stacked_on_location is only ever defined in branch.conf, so don't
614
# waste effort reading the whole stack of config files.
615
conf = _mod_config.BranchOnlyStack(self)
616
stacked_url = self._get_config_location('stacked_on_location',
618
if stacked_url is None:
619
raise errors.NotStacked(self)
620
return stacked_url.encode('utf-8')
622
def get_rev_id(self, revno, history=None):
623
"""Find the revision id of the specified revno."""
625
return _mod_revision.NULL_REVISION
627
with self.lock_read():
628
last_revno, last_revision_id = self.last_revision_info()
629
if revno <= 0 or revno > last_revno:
630
raise errors.NoSuchRevision(self, revno)
632
if history is not None:
633
return history[revno - 1]
635
index = last_revno - revno
636
if len(self._partial_revision_history_cache) <= index:
637
self._extend_partial_history(stop_index=index)
638
if len(self._partial_revision_history_cache) > index:
639
return self._partial_revision_history_cache[index]
641
raise errors.NoSuchRevision(self, revno)
643
def revision_id_to_revno(self, revision_id):
644
"""Given a revision id, return its revno"""
645
if _mod_revision.is_null(revision_id):
647
with self.lock_read():
649
index = self._partial_revision_history_cache.index(revision_id)
652
self._extend_partial_history(stop_revision=revision_id)
653
except errors.RevisionNotPresent as e:
654
raise errors.GhostRevisionsHaveNoRevno(
655
revision_id, e.revision_id)
656
index = len(self._partial_revision_history_cache) - 1
658
raise errors.NoSuchRevision(self, revision_id)
659
if self._partial_revision_history_cache[index] != revision_id:
660
raise errors.NoSuchRevision(self, revision_id)
661
return self.revno() - index
664
class BzrBranch7(BzrBranch8):
665
"""A branch with support for a fallback repository."""
667
def set_reference_info(self, tree_path, branch_location, file_id=None):
668
Branch.set_reference_info(self, file_id, tree_path, branch_location)
670
def get_reference_info(self, path):
671
Branch.get_reference_info(self, path)
673
def reference_parent(self, path, file_id=None, possible_transports=None):
674
return Branch.reference_parent(self, path, file_id, possible_transports)
677
class BzrBranch6(BzrBranch7):
678
"""See BzrBranchFormat6 for the capabilities of this branch.
680
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
684
def get_stacked_on_url(self):
685
raise UnstackableBranchFormat(self._format, self.user_url)
688
class BranchFormatMetadir(bzrdir.BzrFormat, BranchFormat):
689
"""Base class for branch formats that live in meta directories.
693
BranchFormat.__init__(self)
694
bzrdir.BzrFormat.__init__(self)
697
def find_format(klass, controldir, name=None):
698
"""Return the format for the branch object in controldir."""
700
transport = controldir.get_branch_transport(None, name=name)
701
except errors.NoSuchFile:
702
raise errors.NotBranchError(path=name, controldir=controldir)
704
format_string = transport.get_bytes("format")
705
except errors.NoSuchFile:
706
raise errors.NotBranchError(
707
path=transport.base, controldir=controldir)
708
return klass._find_format(format_registry, 'branch', format_string)
710
def _branch_class(self):
711
"""What class to instantiate on open calls."""
712
raise NotImplementedError(self._branch_class)
714
def _get_initial_config(self, append_revisions_only=None):
715
if append_revisions_only:
716
return b"append_revisions_only = True\n"
718
# Avoid writing anything if append_revisions_only is disabled,
719
# as that is the default.
722
def _initialize_helper(self, a_controldir, utf8_files, name=None,
724
"""Initialize a branch in a control dir, with specified files
726
:param a_controldir: The bzrdir to initialize the branch in
727
:param utf8_files: The files to create as a list of
728
(filename, content) tuples
729
:param name: Name of colocated branch to create, if any
730
:return: a branch in this format
733
name = a_controldir._get_selected_branch()
734
mutter('creating branch %r in %s', self, a_controldir.user_url)
735
branch_transport = a_controldir.get_branch_transport(self, name=name)
736
control_files = lockable_files.LockableFiles(branch_transport,
737
'lock', lockdir.LockDir)
738
control_files.create_lock()
739
control_files.lock_write()
741
utf8_files += [('format', self.as_string())]
742
for (filename, content) in utf8_files:
743
branch_transport.put_bytes(
745
mode=a_controldir._get_file_mode())
747
control_files.unlock()
748
branch = self.open(a_controldir, name, _found=True,
749
found_repository=repository)
750
self._run_post_branch_init_hooks(a_controldir, name, branch)
753
def open(self, a_controldir, name=None, _found=False, ignore_fallbacks=False,
754
found_repository=None, possible_transports=None):
755
"""See BranchFormat.open()."""
757
name = a_controldir._get_selected_branch()
759
format = BranchFormatMetadir.find_format(a_controldir, name=name)
760
if format.__class__ != self.__class__:
761
raise AssertionError("wrong format %r found for %r" %
763
transport = a_controldir.get_branch_transport(None, name=name)
765
control_files = lockable_files.LockableFiles(transport, 'lock',
767
if found_repository is None:
768
found_repository = a_controldir.find_repository()
769
return self._branch_class()(_format=self,
770
_control_files=control_files,
772
a_controldir=a_controldir,
773
_repository=found_repository,
774
ignore_fallbacks=ignore_fallbacks,
775
possible_transports=possible_transports)
776
except errors.NoSuchFile:
777
raise errors.NotBranchError(path=transport.base, controldir=a_controldir)
780
def _matchingcontroldir(self):
781
ret = bzrdir.BzrDirMetaFormat1()
782
ret.set_branch_format(self)
785
def supports_tags(self):
788
def supports_leaving_lock(self):
791
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
793
BranchFormat.check_support_status(self,
794
allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
796
bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
797
recommend_upgrade=recommend_upgrade, basedir=basedir)
800
class BzrBranchFormat6(BranchFormatMetadir):
801
"""Branch format with last-revision and tags.
803
Unlike previous formats, this has no explicit revision history. Instead,
804
this just stores the last-revision, and the left-hand history leading
805
up to there is the history.
807
This format was introduced in bzr 0.15
808
and became the default in 0.91.
811
def _branch_class(self):
815
def get_format_string(cls):
816
"""See BranchFormat.get_format_string()."""
817
return b"Bazaar Branch Format 6 (bzr 0.15)\n"
819
def get_format_description(self):
820
"""See BranchFormat.get_format_description()."""
821
return "Branch format 6"
823
def initialize(self, a_controldir, name=None, repository=None,
824
append_revisions_only=None):
825
"""Create a branch of this format in a_controldir."""
826
utf8_files = [('last-revision', b'0 null:\n'),
828
self._get_initial_config(append_revisions_only)),
831
return self._initialize_helper(a_controldir, utf8_files, name, repository)
833
def make_tags(self, branch):
834
"""See breezy.branch.BranchFormat.make_tags()."""
835
return _mod_tag.BasicTags(branch)
837
def supports_set_append_revisions_only(self):
841
class BzrBranchFormat8(BranchFormatMetadir):
842
"""Metadir format supporting storing locations of subtree branches."""
844
def _branch_class(self):
848
def get_format_string(cls):
849
"""See BranchFormat.get_format_string()."""
850
return b"Bazaar Branch Format 8 (needs bzr 1.15)\n"
852
def get_format_description(self):
853
"""See BranchFormat.get_format_description()."""
854
return "Branch format 8"
856
def initialize(self, a_controldir, name=None, repository=None,
857
append_revisions_only=None):
858
"""Create a branch of this format in a_controldir."""
859
utf8_files = [('last-revision', b'0 null:\n'),
861
self._get_initial_config(append_revisions_only)),
865
return self._initialize_helper(a_controldir, utf8_files, name, repository)
867
def make_tags(self, branch):
868
"""See breezy.branch.BranchFormat.make_tags()."""
869
return _mod_tag.BasicTags(branch)
871
def supports_set_append_revisions_only(self):
874
def supports_stacking(self):
877
supports_reference_locations = True
880
class BzrBranchFormat7(BranchFormatMetadir):
881
"""Branch format with last-revision, tags, and a stacked location pointer.
883
The stacked location pointer is passed down to the repository and requires
884
a repository format with supports_external_lookups = True.
886
This format was introduced in bzr 1.6.
889
def initialize(self, a_controldir, name=None, repository=None,
890
append_revisions_only=None):
891
"""Create a branch of this format in a_controldir."""
892
utf8_files = [('last-revision', b'0 null:\n'),
894
self._get_initial_config(append_revisions_only)),
897
return self._initialize_helper(a_controldir, utf8_files, name, repository)
899
def _branch_class(self):
903
def get_format_string(cls):
904
"""See BranchFormat.get_format_string()."""
905
return b"Bazaar Branch Format 7 (needs bzr 1.6)\n"
907
def get_format_description(self):
908
"""See BranchFormat.get_format_description()."""
909
return "Branch format 7"
911
def supports_set_append_revisions_only(self):
914
def supports_stacking(self):
917
def make_tags(self, branch):
918
"""See breezy.branch.BranchFormat.make_tags()."""
919
return _mod_tag.BasicTags(branch)
921
supports_reference_locations = False
924
class BranchReferenceFormat(BranchFormatMetadir):
925
"""Bzr branch reference format.
927
Branch references are used in implementing checkouts, they
928
act as an alias to the real branch which is at some other url.
936
def get_format_string(cls):
937
"""See BranchFormat.get_format_string()."""
938
return b"Bazaar-NG Branch Reference Format 1\n"
940
def get_format_description(self):
941
"""See BranchFormat.get_format_description()."""
942
return "Checkout reference format 1"
944
def get_reference(self, a_controldir, name=None):
945
"""See BranchFormat.get_reference()."""
946
transport = a_controldir.get_branch_transport(None, name=name)
947
url = urlutils.split_segment_parameters(a_controldir.user_url)[0]
948
return urlutils.join(url, transport.get_bytes('location'))
950
def _write_reference(self, a_controldir, transport, to_branch):
951
to_url = to_branch.user_url
952
if a_controldir.control_url == to_branch.controldir.control_url:
953
# Write relative paths for colocated branches, but absolute
954
# paths for everything else. This is for the benefit
955
# of older bzr versions that don't support relative paths.
956
to_url = urlutils.relative_url(a_controldir.user_url, to_branch.user_url)
957
transport.put_bytes('location', to_url)
959
def set_reference(self, a_controldir, name, to_branch):
960
"""See BranchFormat.set_reference()."""
961
transport = a_controldir.get_branch_transport(None, name=name)
962
self._write_reference(a_controldir, transport, to_branch)
964
def initialize(self, a_controldir, name=None, target_branch=None,
965
repository=None, append_revisions_only=None):
966
"""Create a branch of this format in a_controldir."""
967
if target_branch is None:
968
# this format does not implement branch itself, thus the implicit
969
# creation contract must see it as uninitializable
970
raise errors.UninitializableFormat(self)
971
mutter('creating branch reference in %s', a_controldir.user_url)
972
if a_controldir._format.fixed_components:
973
raise errors.IncompatibleFormat(self, a_controldir._format)
975
name = a_controldir._get_selected_branch()
976
branch_transport = a_controldir.get_branch_transport(self, name=name)
977
self._write_reference(a_controldir, branch_transport, target_branch)
978
branch_transport.put_bytes('format', self.as_string())
979
branch = self.open(a_controldir, name, _found=True,
980
possible_transports=[target_branch.controldir.root_transport])
981
self._run_post_branch_init_hooks(a_controldir, name, branch)
984
def _make_reference_clone_function(format, a_branch):
985
"""Create a clone() routine for a branch dynamically."""
986
def clone(to_bzrdir, revision_id=None,
987
repository_policy=None):
988
"""See Branch.clone()."""
989
return format.initialize(to_bzrdir, target_branch=a_branch)
990
# cannot obey revision_id limits when cloning a reference ...
991
# FIXME RBC 20060210 either nuke revision_id for clone, or
992
# emit some sort of warning/error to the caller ?!
995
def open(self, a_controldir, name=None, _found=False, location=None,
996
possible_transports=None, ignore_fallbacks=False,
997
found_repository=None):
998
"""Return the branch that the branch reference in a_controldir points at.
1000
:param a_controldir: A BzrDir that contains a branch.
1001
:param name: Name of colocated branch to open, if any
1002
:param _found: a private parameter, do not use it. It is used to
1003
indicate if format probing has already be done.
1004
:param ignore_fallbacks: when set, no fallback branches will be opened
1005
(if there are any). Default is to open fallbacks.
1006
:param location: The location of the referenced branch. If
1007
unspecified, this will be determined from the branch reference in
1009
:param possible_transports: An optional reusable transports list.
1012
name = a_controldir._get_selected_branch()
1014
format = BranchFormatMetadir.find_format(a_controldir, name=name)
1015
if format.__class__ != self.__class__:
1016
raise AssertionError("wrong format %r found for %r" %
1018
if location is None:
1019
location = self.get_reference(a_controldir, name)
1020
real_bzrdir = controldir.ControlDir.open(
1021
location, possible_transports=possible_transports)
1022
result = real_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks,
1023
possible_transports=possible_transports)
1024
# this changes the behaviour of result.clone to create a new reference
1025
# rather than a copy of the content of the branch.
1026
# I did not use a proxy object because that needs much more extensive
1027
# testing, and we are only changing one behaviour at the moment.
1028
# If we decide to alter more behaviours - i.e. the implicit nickname
1029
# then this should be refactored to introduce a tested proxy branch
1030
# and a subclass of that for use in overriding clone() and ....
1032
result.clone = self._make_reference_clone_function(result)
1036
class Converter5to6(object):
1037
"""Perform an in-place upgrade of format 5 to format 6"""
1039
def convert(self, branch):
1040
# Data for 5 and 6 can peacefully coexist.
1041
format = BzrBranchFormat6()
1042
new_branch = format.open(branch.controldir, _found=True)
1044
# Copy source data into target
1045
new_branch._write_last_revision_info(*branch.last_revision_info())
1046
new_branch.lock_write()
1048
new_branch.set_parent(branch.get_parent())
1049
new_branch.set_bound_location(branch.get_bound_location())
1050
new_branch.set_push_location(branch.get_push_location())
1054
# New branch has no tags by default
1055
new_branch.tags._set_tag_dict({})
1057
# Copying done; now update target format
1058
new_branch._transport.put_bytes('format',
1060
mode=new_branch.controldir._get_file_mode())
1062
# Clean up old files
1063
new_branch._transport.delete('revision-history')
1067
branch.set_parent(None)
1068
except errors.NoSuchFile:
1070
branch.set_bound_location(None)
1075
class Converter6to7(object):
1076
"""Perform an in-place upgrade of format 6 to format 7"""
1078
def convert(self, branch):
1079
format = BzrBranchFormat7()
1080
branch._set_config_location('stacked_on_location', '')
1081
# update target format
1082
branch._transport.put_bytes('format', format.as_string())
1085
class Converter7to8(object):
1086
"""Perform an in-place upgrade of format 7 to format 8"""
1088
def convert(self, branch):
1089
format = BzrBranchFormat8()
1090
branch._transport.put_bytes('references', '')
1091
# update target format
1092
branch._transport.put_bytes('format', format.as_string())