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
23
config as _mod_config,
28
revision as _mod_revision,
37
BranchWriteLockResult,
40
from .decorators import (
45
from .lock import _RelockDebugMixin, LogicalLockResult
54
class BzrBranch(Branch, _RelockDebugMixin):
55
"""A branch stored in the actual filesystem.
57
Note that it's "local" in the context of the filesystem; it doesn't
58
really matter if it's on an nfs/smb/afs/coda/... share, as long as
59
it's writable, and can be accessed via the normal filesystem API.
61
:ivar _transport: Transport for file operations on this branch's
62
control files, typically pointing to the .bzr/branch directory.
63
:ivar repository: Repository for this branch.
64
:ivar base: The url of the base directory for this branch; the one
65
containing the .bzr directory.
66
:ivar name: Optional colocated branch name as it exists in the control
70
def __init__(self, _format=None,
71
_control_files=None, a_bzrdir=None, name=None,
72
_repository=None, ignore_fallbacks=False,
73
possible_transports=None):
74
"""Create new branch object at a particular location."""
76
raise ValueError('a_bzrdir must be supplied')
78
raise ValueError('name must be supplied')
79
self.bzrdir = a_bzrdir
80
self._user_transport = self.bzrdir.transport.clone('..')
82
self._user_transport.set_segment_parameter(
83
"branch", urlutils.escape(name))
84
self._base = self._user_transport.base
86
self._format = _format
87
if _control_files is None:
88
raise ValueError('BzrBranch _control_files is None')
89
self.control_files = _control_files
90
self._transport = _control_files._transport
91
self.repository = _repository
92
self.conf_store = None
93
Branch.__init__(self, possible_transports)
96
return '%s(%s)' % (self.__class__.__name__, self.user_url)
101
"""Returns the directory containing the control directory."""
104
base = property(_get_base, doc="The URL for the root of this branch.")
107
def user_transport(self):
108
return self._user_transport
110
def _get_config(self):
111
return _mod_config.TransportConfig(self._transport, 'branch.conf')
113
def _get_config_store(self):
114
if self.conf_store is None:
115
self.conf_store = _mod_config.BranchStore(self)
116
return self.conf_store
118
def _uncommitted_branch(self):
119
"""Return the branch that may contain uncommitted changes."""
120
master = self.get_master_branch()
121
if master is not None:
126
def store_uncommitted(self, creator):
127
"""Store uncommitted changes from a ShelfCreator.
129
:param creator: The ShelfCreator containing uncommitted changes, or
130
None to delete any stored changes.
131
:raises: ChangesAlreadyStored if the branch already has changes.
133
branch = self._uncommitted_branch()
135
branch._transport.delete('stored-transform')
137
if branch._transport.has('stored-transform'):
138
raise errors.ChangesAlreadyStored
139
transform = BytesIO()
140
creator.write_shelf(transform)
142
branch._transport.put_file('stored-transform', transform)
144
def get_unshelver(self, tree):
145
"""Return a shelf.Unshelver for this branch and tree.
147
:param tree: The tree to use to construct the Unshelver.
148
:return: an Unshelver or None if no changes are stored.
150
branch = self._uncommitted_branch()
152
transform = branch._transport.get('stored-transform')
153
except errors.NoSuchFile:
155
return shelf.Unshelver.from_tree_and_shelf(tree, transform)
158
return self.control_files.is_locked()
160
def lock_write(self, token=None):
161
"""Lock the branch for write operations.
163
:param token: A token to permit reacquiring a previously held and
165
:return: A BranchWriteLockResult.
167
if not self.is_locked():
169
self.repository._warn_if_deprecated(self)
170
self.repository.lock_write()
175
return BranchWriteLockResult(self.unlock,
176
self.control_files.lock_write(token=token))
179
self.repository.unlock()
183
"""Lock the branch for read operations.
185
:return: A breezy.lock.LogicalLockResult.
187
if not self.is_locked():
189
self.repository._warn_if_deprecated(self)
190
self.repository.lock_read()
195
self.control_files.lock_read()
196
return LogicalLockResult(self.unlock)
199
self.repository.unlock()
202
@only_raises(errors.LockNotHeld, errors.LockBroken)
204
if self.control_files._lock_count == 1 and self.conf_store is not None:
205
self.conf_store.save_changes()
207
self.control_files.unlock()
209
if not self.control_files.is_locked():
210
self.repository.unlock()
211
# we just released the lock
212
self._clear_cached_state()
214
def peek_lock_mode(self):
215
if self.control_files._lock_count == 0:
218
return self.control_files._lock_mode
220
def get_physical_lock_status(self):
221
return self.control_files.get_physical_lock_status()
224
def print_file(self, file, revision_id):
225
"""See Branch.print_file."""
226
return self.repository.print_file(file, revision_id)
229
def set_last_revision_info(self, revno, revision_id):
230
if not revision_id or not isinstance(revision_id, basestring):
231
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
232
revision_id = _mod_revision.ensure_null(revision_id)
233
old_revno, old_revid = self.last_revision_info()
234
if self.get_append_revisions_only():
235
self._check_history_violation(revision_id)
236
self._run_pre_change_branch_tip_hooks(revno, revision_id)
237
self._write_last_revision_info(revno, revision_id)
238
self._clear_cached_state()
239
self._last_revision_info_cache = revno, revision_id
240
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
242
def basis_tree(self):
243
"""See Branch.basis_tree."""
244
return self.repository.revision_tree(self.last_revision())
246
def _get_parent_location(self):
247
_locs = ['parent', 'pull', 'x-pull']
250
return self._transport.get_bytes(l).strip('\n')
251
except errors.NoSuchFile:
255
def get_stacked_on_url(self):
256
raise errors.UnstackableBranchFormat(self._format, self.user_url)
258
def set_push_location(self, location):
259
"""See Branch.set_push_location."""
260
self.get_config().set_user_option(
261
'push_location', location,
262
store=_mod_config.STORE_LOCATION_NORECURSE)
264
def _set_parent_location(self, url):
266
self._transport.delete('parent')
268
self._transport.put_bytes('parent', url + '\n',
269
mode=self.bzrdir._get_file_mode())
273
"""If bound, unbind"""
274
return self.set_bound_location(None)
277
def bind(self, other):
278
"""Bind this branch to the branch other.
280
This does not push or pull data between the branches, though it does
281
check for divergence to raise an error when the branches are not
282
either the same, or one a prefix of the other. That behaviour may not
283
be useful, so that check may be removed in future.
285
:param other: The branch to bind to
288
# TODO: jam 20051230 Consider checking if the target is bound
289
# It is debatable whether you should be able to bind to
290
# a branch which is itself bound.
291
# Committing is obviously forbidden,
292
# but binding itself may not be.
293
# Since we *have* to check at commit time, we don't
294
# *need* to check here
296
# we want to raise diverged if:
297
# last_rev is not in the other_last_rev history, AND
298
# other_last_rev is not in our history, and do it without pulling
300
self.set_bound_location(other.base)
302
def get_bound_location(self):
304
return self._transport.get_bytes('bound')[:-1]
305
except errors.NoSuchFile:
309
def get_master_branch(self, possible_transports=None):
310
"""Return the branch we are bound to.
312
:return: Either a Branch, or None
314
if self._master_branch_cache is None:
315
self._master_branch_cache = self._get_master_branch(
317
return self._master_branch_cache
319
def _get_master_branch(self, possible_transports):
320
bound_loc = self.get_bound_location()
324
return Branch.open(bound_loc,
325
possible_transports=possible_transports)
326
except (errors.NotBranchError, errors.ConnectionError) as e:
327
raise errors.BoundBranchConnectionFailure(
331
def set_bound_location(self, location):
332
"""Set the target where this branch is bound to.
334
:param location: URL to the target branch
336
self._master_branch_cache = None
338
self._transport.put_bytes('bound', location+'\n',
339
mode=self.bzrdir._get_file_mode())
342
self._transport.delete('bound')
343
except errors.NoSuchFile:
348
def update(self, possible_transports=None):
349
"""Synchronise this branch with the master branch if any.
351
:return: None or the last_revision that was pivoted out during the
354
master = self.get_master_branch(possible_transports)
355
if master is not None:
356
old_tip = _mod_revision.ensure_null(self.last_revision())
357
self.pull(master, overwrite=True)
358
if self.repository.get_graph().is_ancestor(old_tip,
359
_mod_revision.ensure_null(self.last_revision())):
364
def _read_last_revision_info(self):
365
revision_string = self._transport.get_bytes('last-revision')
366
revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
367
revision_id = cache_utf8.get_cached_utf8(revision_id)
369
return revno, revision_id
371
def _write_last_revision_info(self, revno, revision_id):
372
"""Simply write out the revision id, with no checks.
374
Use set_last_revision_info to perform this safely.
376
Does not update the revision_history cache.
378
revision_id = _mod_revision.ensure_null(revision_id)
379
out_string = '%d %s\n' % (revno, revision_id)
380
self._transport.put_bytes('last-revision', out_string,
381
mode=self.bzrdir._get_file_mode())
384
def update_feature_flags(self, updated_flags):
385
"""Update the feature flags for this branch.
387
:param updated_flags: Dictionary mapping feature names to necessities
388
A necessity can be None to indicate the feature should be removed
390
self._format._update_feature_flags(updated_flags)
391
self.control_transport.put_bytes('format', self._format.as_string())
394
class BzrBranch8(BzrBranch):
395
"""A branch that stores tree-reference locations."""
397
def _open_hook(self, possible_transports=None):
398
if self._ignore_fallbacks:
400
if possible_transports is None:
401
possible_transports = [self.bzrdir.root_transport]
403
url = self.get_stacked_on_url()
404
except (errors.UnstackableRepositoryFormat, errors.NotStacked,
405
errors.UnstackableBranchFormat):
408
for hook in Branch.hooks['transform_fallback_location']:
409
url = hook(self, url)
411
hook_name = Branch.hooks.get_hook_name(hook)
412
raise AssertionError(
413
"'transform_fallback_location' hook %s returned "
414
"None, not a URL." % hook_name)
415
self._activate_fallback_location(url,
416
possible_transports=possible_transports)
418
def __init__(self, *args, **kwargs):
419
self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
420
super(BzrBranch8, self).__init__(*args, **kwargs)
421
self._last_revision_info_cache = None
422
self._reference_info = None
424
def _clear_cached_state(self):
425
super(BzrBranch8, self)._clear_cached_state()
426
self._last_revision_info_cache = None
427
self._reference_info = None
429
def _check_history_violation(self, revision_id):
430
current_revid = self.last_revision()
431
last_revision = _mod_revision.ensure_null(current_revid)
432
if _mod_revision.is_null(last_revision):
434
graph = self.repository.get_graph()
435
for lh_ancestor in graph.iter_lefthand_ancestry(revision_id):
436
if lh_ancestor == current_revid:
438
raise errors.AppendRevisionsOnlyViolation(self.user_url)
440
def _gen_revision_history(self):
441
"""Generate the revision history from last revision
443
last_revno, last_revision = self.last_revision_info()
444
self._extend_partial_history(stop_index=last_revno-1)
445
return list(reversed(self._partial_revision_history_cache))
448
def _set_parent_location(self, url):
449
"""Set the parent branch"""
450
self._set_config_location('parent_location', url, make_relative=True)
453
def _get_parent_location(self):
454
"""Set the parent branch"""
455
return self._get_config_location('parent_location')
458
def _set_all_reference_info(self, info_dict):
459
"""Replace all reference info stored in a branch.
461
:param info_dict: A dict of {file_id: (tree_path, branch_location)}
464
writer = rio.RioWriter(s)
465
for key, (tree_path, branch_location) in info_dict.iteritems():
466
stanza = rio.Stanza(file_id=key, tree_path=tree_path,
467
branch_location=branch_location)
468
writer.write_stanza(stanza)
469
self._transport.put_bytes('references', s.getvalue())
470
self._reference_info = info_dict
473
def _get_all_reference_info(self):
474
"""Return all the reference info stored in a branch.
476
:return: A dict of {file_id: (tree_path, branch_location)}
478
if self._reference_info is not None:
479
return self._reference_info
480
rio_file = self._transport.get('references')
482
stanzas = rio.read_stanzas(rio_file)
483
info_dict = dict((s['file_id'], (s['tree_path'],
484
s['branch_location'])) for s in stanzas)
487
self._reference_info = info_dict
490
def set_reference_info(self, file_id, tree_path, branch_location):
491
"""Set the branch location to use for a tree reference.
493
:param file_id: The file-id of the tree reference.
494
:param tree_path: The path of the tree reference in the tree.
495
:param branch_location: The location of the branch to retrieve tree
498
info_dict = self._get_all_reference_info()
499
info_dict[file_id] = (tree_path, branch_location)
500
if None in (tree_path, branch_location):
501
if tree_path is not None:
502
raise ValueError('tree_path must be None when branch_location'
504
if branch_location is not None:
505
raise ValueError('branch_location must be None when tree_path'
507
del info_dict[file_id]
508
self._set_all_reference_info(info_dict)
510
def get_reference_info(self, file_id):
511
"""Get the tree_path and branch_location for a tree reference.
513
:return: a tuple of (tree_path, branch_location)
515
return self._get_all_reference_info().get(file_id, (None, None))
517
def reference_parent(self, file_id, path, possible_transports=None):
518
"""Return the parent branch for a tree-reference file_id.
520
:param file_id: The file_id of the tree reference
521
:param path: The path of the file_id in the tree
522
:return: A branch associated with the file_id
524
branch_location = self.get_reference_info(file_id)[1]
525
if branch_location is None:
526
return Branch.reference_parent(self, file_id, path,
528
branch_location = urlutils.join(self.user_url, branch_location)
529
return Branch.open(branch_location,
530
possible_transports=possible_transports)
532
def set_push_location(self, location):
533
"""See Branch.set_push_location."""
534
self._set_config_location('push_location', location)
536
def set_bound_location(self, location):
537
"""See Branch.set_push_location."""
538
self._master_branch_cache = None
540
conf = self.get_config_stack()
542
if not conf.get('bound'):
545
conf.set('bound', 'False')
548
self._set_config_location('bound_location', location,
550
conf.set('bound', 'True')
553
def _get_bound_location(self, bound):
554
"""Return the bound location in the config file.
556
Return None if the bound parameter does not match"""
557
conf = self.get_config_stack()
558
if conf.get('bound') != bound:
560
return self._get_config_location('bound_location', config=conf)
562
def get_bound_location(self):
563
"""See Branch.get_bound_location."""
564
return self._get_bound_location(True)
566
def get_old_bound_location(self):
567
"""See Branch.get_old_bound_location"""
568
return self._get_bound_location(False)
570
def get_stacked_on_url(self):
571
# you can always ask for the URL; but you might not be able to use it
572
# if the repo can't support stacking.
573
## self._check_stackable_repo()
574
# stacked_on_location is only ever defined in branch.conf, so don't
575
# waste effort reading the whole stack of config files.
576
conf = _mod_config.BranchOnlyStack(self)
577
stacked_url = self._get_config_location('stacked_on_location',
579
if stacked_url is None:
580
raise errors.NotStacked(self)
581
return stacked_url.encode('utf-8')
584
def get_rev_id(self, revno, history=None):
585
"""Find the revision id of the specified revno."""
587
return _mod_revision.NULL_REVISION
589
last_revno, last_revision_id = self.last_revision_info()
590
if revno <= 0 or revno > last_revno:
591
raise errors.NoSuchRevision(self, revno)
593
if history is not None:
594
return history[revno - 1]
596
index = last_revno - revno
597
if len(self._partial_revision_history_cache) <= index:
598
self._extend_partial_history(stop_index=index)
599
if len(self._partial_revision_history_cache) > index:
600
return self._partial_revision_history_cache[index]
602
raise errors.NoSuchRevision(self, revno)
605
def revision_id_to_revno(self, revision_id):
606
"""Given a revision id, return its revno"""
607
if _mod_revision.is_null(revision_id):
610
index = self._partial_revision_history_cache.index(revision_id)
613
self._extend_partial_history(stop_revision=revision_id)
614
except errors.RevisionNotPresent as e:
615
raise errors.GhostRevisionsHaveNoRevno(revision_id, e.revision_id)
616
index = len(self._partial_revision_history_cache) - 1
618
raise errors.NoSuchRevision(self, revision_id)
619
if self._partial_revision_history_cache[index] != revision_id:
620
raise errors.NoSuchRevision(self, revision_id)
621
return self.revno() - index
624
class BzrBranch7(BzrBranch8):
625
"""A branch with support for a fallback repository."""
627
def set_reference_info(self, file_id, tree_path, branch_location):
628
Branch.set_reference_info(self, file_id, tree_path, branch_location)
630
def get_reference_info(self, file_id):
631
Branch.get_reference_info(self, file_id)
633
def reference_parent(self, file_id, path, possible_transports=None):
634
return Branch.reference_parent(self, file_id, path,
638
class BzrBranch6(BzrBranch7):
639
"""See BzrBranchFormat6 for the capabilities of this branch.
641
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
645
def get_stacked_on_url(self):
646
raise errors.UnstackableBranchFormat(self._format, self.user_url)
649
class BranchFormatMetadir(bzrdir.BzrFormat, BranchFormat):
650
"""Base class for branch formats that live in meta directories.
654
BranchFormat.__init__(self)
655
bzrdir.BzrFormat.__init__(self)
658
def find_format(klass, controldir, name=None):
659
"""Return the format for the branch object in controldir."""
661
transport = controldir.get_branch_transport(None, name=name)
662
except errors.NoSuchFile:
663
raise errors.NotBranchError(path=name, bzrdir=controldir)
665
format_string = transport.get_bytes("format")
666
except errors.NoSuchFile:
667
raise errors.NotBranchError(path=transport.base, bzrdir=controldir)
668
return klass._find_format(format_registry, 'branch', format_string)
670
def _branch_class(self):
671
"""What class to instantiate on open calls."""
672
raise NotImplementedError(self._branch_class)
674
def _get_initial_config(self, append_revisions_only=None):
675
if append_revisions_only:
676
return "append_revisions_only = True\n"
678
# Avoid writing anything if append_revisions_only is disabled,
679
# as that is the default.
682
def _initialize_helper(self, a_bzrdir, utf8_files, name=None,
684
"""Initialize a branch in a control dir, with specified files
686
:param a_bzrdir: The bzrdir to initialize the branch in
687
:param utf8_files: The files to create as a list of
688
(filename, content) tuples
689
:param name: Name of colocated branch to create, if any
690
:return: a branch in this format
693
name = a_bzrdir._get_selected_branch()
694
mutter('creating branch %r in %s', self, a_bzrdir.user_url)
695
branch_transport = a_bzrdir.get_branch_transport(self, name=name)
696
control_files = lockable_files.LockableFiles(branch_transport,
697
'lock', lockdir.LockDir)
698
control_files.create_lock()
699
control_files.lock_write()
701
utf8_files += [('format', self.as_string())]
702
for (filename, content) in utf8_files:
703
branch_transport.put_bytes(
705
mode=a_bzrdir._get_file_mode())
707
control_files.unlock()
708
branch = self.open(a_bzrdir, name, _found=True,
709
found_repository=repository)
710
self._run_post_branch_init_hooks(a_bzrdir, name, branch)
713
def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False,
714
found_repository=None, possible_transports=None):
715
"""See BranchFormat.open()."""
717
name = a_bzrdir._get_selected_branch()
719
format = BranchFormatMetadir.find_format(a_bzrdir, name=name)
720
if format.__class__ != self.__class__:
721
raise AssertionError("wrong format %r found for %r" %
723
transport = a_bzrdir.get_branch_transport(None, name=name)
725
control_files = lockable_files.LockableFiles(transport, 'lock',
727
if found_repository is None:
728
found_repository = a_bzrdir.find_repository()
729
return self._branch_class()(_format=self,
730
_control_files=control_files,
733
_repository=found_repository,
734
ignore_fallbacks=ignore_fallbacks,
735
possible_transports=possible_transports)
736
except errors.NoSuchFile:
737
raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
740
def _matchingbzrdir(self):
741
ret = bzrdir.BzrDirMetaFormat1()
742
ret.set_branch_format(self)
745
def supports_tags(self):
748
def supports_leaving_lock(self):
751
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
753
BranchFormat.check_support_status(self,
754
allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
756
bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
757
recommend_upgrade=recommend_upgrade, basedir=basedir)
760
class BzrBranchFormat6(BranchFormatMetadir):
761
"""Branch format with last-revision and tags.
763
Unlike previous formats, this has no explicit revision history. Instead,
764
this just stores the last-revision, and the left-hand history leading
765
up to there is the history.
767
This format was introduced in bzr 0.15
768
and became the default in 0.91.
771
def _branch_class(self):
775
def get_format_string(cls):
776
"""See BranchFormat.get_format_string()."""
777
return "Bazaar Branch Format 6 (bzr 0.15)\n"
779
def get_format_description(self):
780
"""See BranchFormat.get_format_description()."""
781
return "Branch format 6"
783
def initialize(self, a_bzrdir, name=None, repository=None,
784
append_revisions_only=None):
785
"""Create a branch of this format in a_bzrdir."""
786
utf8_files = [('last-revision', '0 null:\n'),
788
self._get_initial_config(append_revisions_only)),
791
return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
793
def make_tags(self, branch):
794
"""See breezy.branch.BranchFormat.make_tags()."""
795
return _mod_tag.BasicTags(branch)
797
def supports_set_append_revisions_only(self):
801
class BzrBranchFormat8(BranchFormatMetadir):
802
"""Metadir format supporting storing locations of subtree branches."""
804
def _branch_class(self):
808
def get_format_string(cls):
809
"""See BranchFormat.get_format_string()."""
810
return "Bazaar Branch Format 8 (needs bzr 1.15)\n"
812
def get_format_description(self):
813
"""See BranchFormat.get_format_description()."""
814
return "Branch format 8"
816
def initialize(self, a_bzrdir, name=None, repository=None,
817
append_revisions_only=None):
818
"""Create a branch of this format in a_bzrdir."""
819
utf8_files = [('last-revision', '0 null:\n'),
821
self._get_initial_config(append_revisions_only)),
825
return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
827
def make_tags(self, branch):
828
"""See breezy.branch.BranchFormat.make_tags()."""
829
return _mod_tag.BasicTags(branch)
831
def supports_set_append_revisions_only(self):
834
def supports_stacking(self):
837
supports_reference_locations = True
840
class BzrBranchFormat7(BranchFormatMetadir):
841
"""Branch format with last-revision, tags, and a stacked location pointer.
843
The stacked location pointer is passed down to the repository and requires
844
a repository format with supports_external_lookups = True.
846
This format was introduced in bzr 1.6.
849
def initialize(self, a_bzrdir, name=None, repository=None,
850
append_revisions_only=None):
851
"""Create a branch of this format in a_bzrdir."""
852
utf8_files = [('last-revision', '0 null:\n'),
854
self._get_initial_config(append_revisions_only)),
857
return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
859
def _branch_class(self):
863
def get_format_string(cls):
864
"""See BranchFormat.get_format_string()."""
865
return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
867
def get_format_description(self):
868
"""See BranchFormat.get_format_description()."""
869
return "Branch format 7"
871
def supports_set_append_revisions_only(self):
874
def supports_stacking(self):
877
def make_tags(self, branch):
878
"""See breezy.branch.BranchFormat.make_tags()."""
879
return _mod_tag.BasicTags(branch)
881
supports_reference_locations = False
884
class BranchReferenceFormat(BranchFormatMetadir):
885
"""Bzr branch reference format.
887
Branch references are used in implementing checkouts, they
888
act as an alias to the real branch which is at some other url.
896
def get_format_string(cls):
897
"""See BranchFormat.get_format_string()."""
898
return "Bazaar-NG Branch Reference Format 1\n"
900
def get_format_description(self):
901
"""See BranchFormat.get_format_description()."""
902
return "Checkout reference format 1"
904
def get_reference(self, a_bzrdir, name=None):
905
"""See BranchFormat.get_reference()."""
906
transport = a_bzrdir.get_branch_transport(None, name=name)
907
return transport.get_bytes('location')
909
def set_reference(self, a_bzrdir, name, to_branch):
910
"""See BranchFormat.set_reference()."""
911
transport = a_bzrdir.get_branch_transport(None, name=name)
912
location = transport.put_bytes('location', to_branch.base)
914
def initialize(self, a_bzrdir, name=None, target_branch=None,
915
repository=None, append_revisions_only=None):
916
"""Create a branch of this format in a_bzrdir."""
917
if target_branch is None:
918
# this format does not implement branch itself, thus the implicit
919
# creation contract must see it as uninitializable
920
raise errors.UninitializableFormat(self)
921
mutter('creating branch reference in %s', a_bzrdir.user_url)
922
if a_bzrdir._format.fixed_components:
923
raise errors.IncompatibleFormat(self, a_bzrdir._format)
925
name = a_bzrdir._get_selected_branch()
926
branch_transport = a_bzrdir.get_branch_transport(self, name=name)
927
branch_transport.put_bytes('location',
928
target_branch.user_url)
929
branch_transport.put_bytes('format', self.as_string())
930
branch = self.open(a_bzrdir, name, _found=True,
931
possible_transports=[target_branch.bzrdir.root_transport])
932
self._run_post_branch_init_hooks(a_bzrdir, name, branch)
935
def _make_reference_clone_function(format, a_branch):
936
"""Create a clone() routine for a branch dynamically."""
937
def clone(to_bzrdir, revision_id=None,
938
repository_policy=None):
939
"""See Branch.clone()."""
940
return format.initialize(to_bzrdir, target_branch=a_branch)
941
# cannot obey revision_id limits when cloning a reference ...
942
# FIXME RBC 20060210 either nuke revision_id for clone, or
943
# emit some sort of warning/error to the caller ?!
946
def open(self, a_bzrdir, name=None, _found=False, location=None,
947
possible_transports=None, ignore_fallbacks=False,
948
found_repository=None):
949
"""Return the branch that the branch reference in a_bzrdir points at.
951
:param a_bzrdir: A BzrDir that contains a branch.
952
:param name: Name of colocated branch to open, if any
953
:param _found: a private parameter, do not use it. It is used to
954
indicate if format probing has already be done.
955
:param ignore_fallbacks: when set, no fallback branches will be opened
956
(if there are any). Default is to open fallbacks.
957
:param location: The location of the referenced branch. If
958
unspecified, this will be determined from the branch reference in
960
:param possible_transports: An optional reusable transports list.
963
name = a_bzrdir._get_selected_branch()
965
format = BranchFormatMetadir.find_format(a_bzrdir, name=name)
966
if format.__class__ != self.__class__:
967
raise AssertionError("wrong format %r found for %r" %
970
location = self.get_reference(a_bzrdir, name)
971
real_bzrdir = controldir.ControlDir.open(
972
location, possible_transports=possible_transports)
973
result = real_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks,
974
possible_transports=possible_transports)
975
# this changes the behaviour of result.clone to create a new reference
976
# rather than a copy of the content of the branch.
977
# I did not use a proxy object because that needs much more extensive
978
# testing, and we are only changing one behaviour at the moment.
979
# If we decide to alter more behaviours - i.e. the implicit nickname
980
# then this should be refactored to introduce a tested proxy branch
981
# and a subclass of that for use in overriding clone() and ....
983
result.clone = self._make_reference_clone_function(result)
987
class Converter5to6(object):
988
"""Perform an in-place upgrade of format 5 to format 6"""
990
def convert(self, branch):
991
# Data for 5 and 6 can peacefully coexist.
992
format = BzrBranchFormat6()
993
new_branch = format.open(branch.bzrdir, _found=True)
995
# Copy source data into target
996
new_branch._write_last_revision_info(*branch.last_revision_info())
997
new_branch.lock_write()
999
new_branch.set_parent(branch.get_parent())
1000
new_branch.set_bound_location(branch.get_bound_location())
1001
new_branch.set_push_location(branch.get_push_location())
1005
# New branch has no tags by default
1006
new_branch.tags._set_tag_dict({})
1008
# Copying done; now update target format
1009
new_branch._transport.put_bytes('format',
1011
mode=new_branch.bzrdir._get_file_mode())
1013
# Clean up old files
1014
new_branch._transport.delete('revision-history')
1018
branch.set_parent(None)
1019
except errors.NoSuchFile:
1021
branch.set_bound_location(None)
1026
class Converter6to7(object):
1027
"""Perform an in-place upgrade of format 6 to format 7"""
1029
def convert(self, branch):
1030
format = BzrBranchFormat7()
1031
branch._set_config_location('stacked_on_location', '')
1032
# update target format
1033
branch._transport.put_bytes('format', format.as_string())
1036
class Converter7to8(object):
1037
"""Perform an in-place upgrade of format 7 to format 8"""
1039
def convert(self, branch):
1040
format = BzrBranchFormat8()
1041
branch._transport.put_bytes('references', '')
1042
# update target format
1043
branch._transport.put_bytes('format', format.as_string())