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,
43
BranchWriteLockResult,
46
from .decorators import (
51
from .lock import _RelockDebugMixin, LogicalLockResult
61
class BzrBranch(Branch, _RelockDebugMixin):
62
"""A branch stored in the actual filesystem.
64
Note that it's "local" in the context of the filesystem; it doesn't
65
really matter if it's on an nfs/smb/afs/coda/... share, as long as
66
it's writable, and can be accessed via the normal filesystem API.
68
:ivar _transport: Transport for file operations on this branch's
69
control files, typically pointing to the .bzr/branch directory.
70
:ivar repository: Repository for this branch.
71
:ivar base: The url of the base directory for this branch; the one
72
containing the .bzr directory.
73
:ivar name: Optional colocated branch name as it exists in the control
77
def __init__(self, _format=None,
78
_control_files=None, a_bzrdir=None, name=None,
79
_repository=None, ignore_fallbacks=False,
80
possible_transports=None):
81
"""Create new branch object at a particular location."""
83
raise ValueError('a_bzrdir must be supplied')
85
raise ValueError('name must be supplied')
86
self.bzrdir = a_bzrdir
87
self._user_transport = self.bzrdir.transport.clone('..')
89
self._user_transport.set_segment_parameter(
90
"branch", urlutils.escape(name))
91
self._base = self._user_transport.base
93
self._format = _format
94
if _control_files is None:
95
raise ValueError('BzrBranch _control_files is None')
96
self.control_files = _control_files
97
self._transport = _control_files._transport
98
self.repository = _repository
99
self.conf_store = None
100
Branch.__init__(self, possible_transports)
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
return _mod_config.TransportConfig(self._transport, 'branch.conf')
120
def _get_config_store(self):
121
if self.conf_store is None:
122
self.conf_store = _mod_config.BranchStore(self)
123
return self.conf_store
125
def _uncommitted_branch(self):
126
"""Return the branch that may contain uncommitted changes."""
127
master = self.get_master_branch()
128
if master is not None:
133
def store_uncommitted(self, creator):
134
"""Store uncommitted changes from a ShelfCreator.
136
:param creator: The ShelfCreator containing uncommitted changes, or
137
None to delete any stored changes.
138
:raises: ChangesAlreadyStored if the branch already has changes.
140
branch = self._uncommitted_branch()
142
branch._transport.delete('stored-transform')
144
if branch._transport.has('stored-transform'):
145
raise errors.ChangesAlreadyStored
146
transform = BytesIO()
147
creator.write_shelf(transform)
149
branch._transport.put_file('stored-transform', transform)
151
def get_unshelver(self, tree):
152
"""Return a shelf.Unshelver for this branch and tree.
154
:param tree: The tree to use to construct the Unshelver.
155
:return: an Unshelver or None if no changes are stored.
157
branch = self._uncommitted_branch()
159
transform = branch._transport.get('stored-transform')
160
except errors.NoSuchFile:
162
return shelf.Unshelver.from_tree_and_shelf(tree, transform)
165
return self.control_files.is_locked()
167
def lock_write(self, token=None):
168
"""Lock the branch for write operations.
170
:param token: A token to permit reacquiring a previously held and
172
:return: A BranchWriteLockResult.
174
if not self.is_locked():
176
self.repository._warn_if_deprecated(self)
177
self.repository.lock_write()
182
return BranchWriteLockResult(self.unlock,
183
self.control_files.lock_write(token=token))
186
self.repository.unlock()
190
"""Lock the branch for read operations.
192
:return: A breezy.lock.LogicalLockResult.
194
if not self.is_locked():
196
self.repository._warn_if_deprecated(self)
197
self.repository.lock_read()
202
self.control_files.lock_read()
203
return LogicalLockResult(self.unlock)
206
self.repository.unlock()
209
@only_raises(errors.LockNotHeld, errors.LockBroken)
211
if self.control_files._lock_count == 1 and self.conf_store is not None:
212
self.conf_store.save_changes()
214
self.control_files.unlock()
216
if not self.control_files.is_locked():
217
self.repository.unlock()
218
# we just released the lock
219
self._clear_cached_state()
221
def peek_lock_mode(self):
222
if self.control_files._lock_count == 0:
225
return self.control_files._lock_mode
227
def get_physical_lock_status(self):
228
return self.control_files.get_physical_lock_status()
231
def print_file(self, file, revision_id):
232
"""See Branch.print_file."""
233
return self.repository.print_file(file, revision_id)
236
def set_last_revision_info(self, revno, revision_id):
237
if not revision_id or not isinstance(revision_id, basestring):
238
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
239
revision_id = _mod_revision.ensure_null(revision_id)
240
old_revno, old_revid = self.last_revision_info()
241
if self.get_append_revisions_only():
242
self._check_history_violation(revision_id)
243
self._run_pre_change_branch_tip_hooks(revno, revision_id)
244
self._write_last_revision_info(revno, revision_id)
245
self._clear_cached_state()
246
self._last_revision_info_cache = revno, revision_id
247
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
249
def basis_tree(self):
250
"""See Branch.basis_tree."""
251
return self.repository.revision_tree(self.last_revision())
253
def _get_parent_location(self):
254
_locs = ['parent', 'pull', 'x-pull']
257
return self._transport.get_bytes(l).strip('\n')
258
except errors.NoSuchFile:
262
def get_stacked_on_url(self):
263
raise errors.UnstackableBranchFormat(self._format, self.user_url)
265
def set_push_location(self, location):
266
"""See Branch.set_push_location."""
267
self.get_config().set_user_option(
268
'push_location', location,
269
store=_mod_config.STORE_LOCATION_NORECURSE)
271
def _set_parent_location(self, url):
273
self._transport.delete('parent')
275
self._transport.put_bytes('parent', url + '\n',
276
mode=self.bzrdir._get_file_mode())
280
"""If bound, unbind"""
281
return self.set_bound_location(None)
284
def bind(self, other):
285
"""Bind this branch to the branch other.
287
This does not push or pull data between the branches, though it does
288
check for divergence to raise an error when the branches are not
289
either the same, or one a prefix of the other. That behaviour may not
290
be useful, so that check may be removed in future.
292
:param other: The branch to bind to
295
# TODO: jam 20051230 Consider checking if the target is bound
296
# It is debatable whether you should be able to bind to
297
# a branch which is itself bound.
298
# Committing is obviously forbidden,
299
# but binding itself may not be.
300
# Since we *have* to check at commit time, we don't
301
# *need* to check here
303
# we want to raise diverged if:
304
# last_rev is not in the other_last_rev history, AND
305
# other_last_rev is not in our history, and do it without pulling
307
self.set_bound_location(other.base)
309
def get_bound_location(self):
311
return self._transport.get_bytes('bound')[:-1]
312
except errors.NoSuchFile:
316
def get_master_branch(self, possible_transports=None):
317
"""Return the branch we are bound to.
319
:return: Either a Branch, or None
321
if self._master_branch_cache is None:
322
self._master_branch_cache = self._get_master_branch(
324
return self._master_branch_cache
326
def _get_master_branch(self, possible_transports):
327
bound_loc = self.get_bound_location()
331
return Branch.open(bound_loc,
332
possible_transports=possible_transports)
333
except (errors.NotBranchError, errors.ConnectionError) as e:
334
raise errors.BoundBranchConnectionFailure(
338
def set_bound_location(self, location):
339
"""Set the target where this branch is bound to.
341
:param location: URL to the target branch
343
self._master_branch_cache = None
345
self._transport.put_bytes('bound', location+'\n',
346
mode=self.bzrdir._get_file_mode())
349
self._transport.delete('bound')
350
except errors.NoSuchFile:
355
def update(self, possible_transports=None):
356
"""Synchronise this branch with the master branch if any.
358
:return: None or the last_revision that was pivoted out during the
361
master = self.get_master_branch(possible_transports)
362
if master is not None:
363
old_tip = _mod_revision.ensure_null(self.last_revision())
364
self.pull(master, overwrite=True)
365
if self.repository.get_graph().is_ancestor(old_tip,
366
_mod_revision.ensure_null(self.last_revision())):
371
def _read_last_revision_info(self):
372
revision_string = self._transport.get_bytes('last-revision')
373
revno, revision_id = revision_string.rstrip(b'\n').split(b' ', 1)
374
revision_id = cache_utf8.get_cached_utf8(revision_id)
376
return revno, revision_id
378
def _write_last_revision_info(self, revno, revision_id):
379
"""Simply write out the revision id, with no checks.
381
Use set_last_revision_info to perform this safely.
383
Does not update the revision_history cache.
385
revision_id = _mod_revision.ensure_null(revision_id)
386
out_string = '%d %s\n' % (revno, revision_id)
387
self._transport.put_bytes('last-revision', out_string,
388
mode=self.bzrdir._get_file_mode())
391
def update_feature_flags(self, updated_flags):
392
"""Update the feature flags for this branch.
394
:param updated_flags: Dictionary mapping feature names to necessities
395
A necessity can be None to indicate the feature should be removed
397
self._format._update_feature_flags(updated_flags)
398
self.control_transport.put_bytes('format', self._format.as_string())
401
class BzrBranch8(BzrBranch):
402
"""A branch that stores tree-reference locations."""
404
def _open_hook(self, possible_transports=None):
405
if self._ignore_fallbacks:
407
if possible_transports is None:
408
possible_transports = [self.bzrdir.root_transport]
410
url = self.get_stacked_on_url()
411
except (errors.UnstackableRepositoryFormat, errors.NotStacked,
412
errors.UnstackableBranchFormat):
415
for hook in Branch.hooks['transform_fallback_location']:
416
url = hook(self, url)
418
hook_name = Branch.hooks.get_hook_name(hook)
419
raise AssertionError(
420
"'transform_fallback_location' hook %s returned "
421
"None, not a URL." % hook_name)
422
self._activate_fallback_location(url,
423
possible_transports=possible_transports)
425
def __init__(self, *args, **kwargs):
426
self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
427
super(BzrBranch8, self).__init__(*args, **kwargs)
428
self._last_revision_info_cache = None
429
self._reference_info = None
431
def _clear_cached_state(self):
432
super(BzrBranch8, self)._clear_cached_state()
433
self._last_revision_info_cache = None
434
self._reference_info = None
436
def _check_history_violation(self, revision_id):
437
current_revid = self.last_revision()
438
last_revision = _mod_revision.ensure_null(current_revid)
439
if _mod_revision.is_null(last_revision):
441
graph = self.repository.get_graph()
442
for lh_ancestor in graph.iter_lefthand_ancestry(revision_id):
443
if lh_ancestor == current_revid:
445
raise errors.AppendRevisionsOnlyViolation(self.user_url)
447
def _gen_revision_history(self):
448
"""Generate the revision history from last revision
450
last_revno, last_revision = self.last_revision_info()
451
self._extend_partial_history(stop_index=last_revno-1)
452
return list(reversed(self._partial_revision_history_cache))
455
def _set_parent_location(self, url):
456
"""Set the parent branch"""
457
self._set_config_location('parent_location', url, make_relative=True)
460
def _get_parent_location(self):
461
"""Set the parent branch"""
462
return self._get_config_location('parent_location')
465
def _set_all_reference_info(self, info_dict):
466
"""Replace all reference info stored in a branch.
468
:param info_dict: A dict of {file_id: (tree_path, branch_location)}
471
writer = rio.RioWriter(s)
472
for key, (tree_path, branch_location) in viewitems(info_dict):
473
stanza = rio.Stanza(file_id=key, tree_path=tree_path,
474
branch_location=branch_location)
475
writer.write_stanza(stanza)
476
self._transport.put_bytes('references', s.getvalue())
477
self._reference_info = info_dict
480
def _get_all_reference_info(self):
481
"""Return all the reference info stored in a branch.
483
:return: A dict of {file_id: (tree_path, branch_location)}
485
if self._reference_info is not None:
486
return self._reference_info
487
rio_file = self._transport.get('references')
489
stanzas = rio.read_stanzas(rio_file)
490
info_dict = dict((s['file_id'], (s['tree_path'],
491
s['branch_location'])) for s in stanzas)
494
self._reference_info = info_dict
497
def set_reference_info(self, file_id, tree_path, branch_location):
498
"""Set the branch location to use for a tree reference.
500
:param file_id: The file-id of the tree reference.
501
:param tree_path: The path of the tree reference in the tree.
502
:param branch_location: The location of the branch to retrieve tree
505
info_dict = self._get_all_reference_info()
506
info_dict[file_id] = (tree_path, branch_location)
507
if None in (tree_path, branch_location):
508
if tree_path is not None:
509
raise ValueError('tree_path must be None when branch_location'
511
if branch_location is not None:
512
raise ValueError('branch_location must be None when tree_path'
514
del info_dict[file_id]
515
self._set_all_reference_info(info_dict)
517
def get_reference_info(self, file_id):
518
"""Get the tree_path and branch_location for a tree reference.
520
:return: a tuple of (tree_path, branch_location)
522
return self._get_all_reference_info().get(file_id, (None, None))
524
def reference_parent(self, file_id, path, possible_transports=None):
525
"""Return the parent branch for a tree-reference file_id.
527
:param file_id: The file_id of the tree reference
528
:param path: The path of the file_id in the tree
529
:return: A branch associated with the file_id
531
branch_location = self.get_reference_info(file_id)[1]
532
if branch_location is None:
533
return Branch.reference_parent(self, file_id, path,
535
branch_location = urlutils.join(self.user_url, branch_location)
536
return Branch.open(branch_location,
537
possible_transports=possible_transports)
539
def set_push_location(self, location):
540
"""See Branch.set_push_location."""
541
self._set_config_location('push_location', location)
543
def set_bound_location(self, location):
544
"""See Branch.set_push_location."""
545
self._master_branch_cache = None
547
conf = self.get_config_stack()
549
if not conf.get('bound'):
552
conf.set('bound', 'False')
555
self._set_config_location('bound_location', location,
557
conf.set('bound', 'True')
560
def _get_bound_location(self, bound):
561
"""Return the bound location in the config file.
563
Return None if the bound parameter does not match"""
564
conf = self.get_config_stack()
565
if conf.get('bound') != bound:
567
return self._get_config_location('bound_location', config=conf)
569
def get_bound_location(self):
570
"""See Branch.get_bound_location."""
571
return self._get_bound_location(True)
573
def get_old_bound_location(self):
574
"""See Branch.get_old_bound_location"""
575
return self._get_bound_location(False)
577
def get_stacked_on_url(self):
578
# you can always ask for the URL; but you might not be able to use it
579
# if the repo can't support stacking.
580
## self._check_stackable_repo()
581
# stacked_on_location is only ever defined in branch.conf, so don't
582
# waste effort reading the whole stack of config files.
583
conf = _mod_config.BranchOnlyStack(self)
584
stacked_url = self._get_config_location('stacked_on_location',
586
if stacked_url is None:
587
raise errors.NotStacked(self)
588
return stacked_url.encode('utf-8')
591
def get_rev_id(self, revno, history=None):
592
"""Find the revision id of the specified revno."""
594
return _mod_revision.NULL_REVISION
596
last_revno, last_revision_id = self.last_revision_info()
597
if revno <= 0 or revno > last_revno:
598
raise errors.NoSuchRevision(self, revno)
600
if history is not None:
601
return history[revno - 1]
603
index = last_revno - revno
604
if len(self._partial_revision_history_cache) <= index:
605
self._extend_partial_history(stop_index=index)
606
if len(self._partial_revision_history_cache) > index:
607
return self._partial_revision_history_cache[index]
609
raise errors.NoSuchRevision(self, revno)
612
def revision_id_to_revno(self, revision_id):
613
"""Given a revision id, return its revno"""
614
if _mod_revision.is_null(revision_id):
617
index = self._partial_revision_history_cache.index(revision_id)
620
self._extend_partial_history(stop_revision=revision_id)
621
except errors.RevisionNotPresent as e:
622
raise errors.GhostRevisionsHaveNoRevno(revision_id, e.revision_id)
623
index = len(self._partial_revision_history_cache) - 1
625
raise errors.NoSuchRevision(self, revision_id)
626
if self._partial_revision_history_cache[index] != revision_id:
627
raise errors.NoSuchRevision(self, revision_id)
628
return self.revno() - index
631
class BzrBranch7(BzrBranch8):
632
"""A branch with support for a fallback repository."""
634
def set_reference_info(self, file_id, tree_path, branch_location):
635
Branch.set_reference_info(self, file_id, tree_path, branch_location)
637
def get_reference_info(self, file_id):
638
Branch.get_reference_info(self, file_id)
640
def reference_parent(self, file_id, path, possible_transports=None):
641
return Branch.reference_parent(self, file_id, path,
645
class BzrBranch6(BzrBranch7):
646
"""See BzrBranchFormat6 for the capabilities of this branch.
648
This subclass of BzrBranch7 disables the new features BzrBranch7 added,
652
def get_stacked_on_url(self):
653
raise errors.UnstackableBranchFormat(self._format, self.user_url)
656
class BranchFormatMetadir(bzrdir.BzrFormat, BranchFormat):
657
"""Base class for branch formats that live in meta directories.
661
BranchFormat.__init__(self)
662
bzrdir.BzrFormat.__init__(self)
665
def find_format(klass, controldir, name=None):
666
"""Return the format for the branch object in controldir."""
668
transport = controldir.get_branch_transport(None, name=name)
669
except errors.NoSuchFile:
670
raise errors.NotBranchError(path=name, bzrdir=controldir)
672
format_string = transport.get_bytes("format")
673
# GZ 2017-06-09: Where should format strings get decoded...
674
format_text = format_string.decode("ascii")
675
except errors.NoSuchFile:
676
raise errors.NotBranchError(path=transport.base, bzrdir=controldir)
677
return klass._find_format(format_registry, 'branch', format_text)
679
def _branch_class(self):
680
"""What class to instantiate on open calls."""
681
raise NotImplementedError(self._branch_class)
683
def _get_initial_config(self, append_revisions_only=None):
684
if append_revisions_only:
685
return b"append_revisions_only = True\n"
687
# Avoid writing anything if append_revisions_only is disabled,
688
# as that is the default.
691
def _initialize_helper(self, a_bzrdir, utf8_files, name=None,
693
"""Initialize a branch in a control dir, with specified files
695
:param a_bzrdir: The bzrdir to initialize the branch in
696
:param utf8_files: The files to create as a list of
697
(filename, content) tuples
698
:param name: Name of colocated branch to create, if any
699
:return: a branch in this format
702
name = a_bzrdir._get_selected_branch()
703
mutter('creating branch %r in %s', self, a_bzrdir.user_url)
704
branch_transport = a_bzrdir.get_branch_transport(self, name=name)
705
control_files = lockable_files.LockableFiles(branch_transport,
706
'lock', lockdir.LockDir)
707
control_files.create_lock()
708
control_files.lock_write()
710
utf8_files += [('format', self.as_string())]
711
for (filename, content) in utf8_files:
712
branch_transport.put_bytes(
714
mode=a_bzrdir._get_file_mode())
716
control_files.unlock()
717
branch = self.open(a_bzrdir, name, _found=True,
718
found_repository=repository)
719
self._run_post_branch_init_hooks(a_bzrdir, name, branch)
722
def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False,
723
found_repository=None, possible_transports=None):
724
"""See BranchFormat.open()."""
726
name = a_bzrdir._get_selected_branch()
728
format = BranchFormatMetadir.find_format(a_bzrdir, name=name)
729
if format.__class__ != self.__class__:
730
raise AssertionError("wrong format %r found for %r" %
732
transport = a_bzrdir.get_branch_transport(None, name=name)
734
control_files = lockable_files.LockableFiles(transport, 'lock',
736
if found_repository is None:
737
found_repository = a_bzrdir.find_repository()
738
return self._branch_class()(_format=self,
739
_control_files=control_files,
742
_repository=found_repository,
743
ignore_fallbacks=ignore_fallbacks,
744
possible_transports=possible_transports)
745
except errors.NoSuchFile:
746
raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
749
def _matchingbzrdir(self):
750
ret = bzrdir.BzrDirMetaFormat1()
751
ret.set_branch_format(self)
754
def supports_tags(self):
757
def supports_leaving_lock(self):
760
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
762
BranchFormat.check_support_status(self,
763
allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
765
bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
766
recommend_upgrade=recommend_upgrade, basedir=basedir)
769
class BzrBranchFormat6(BranchFormatMetadir):
770
"""Branch format with last-revision and tags.
772
Unlike previous formats, this has no explicit revision history. Instead,
773
this just stores the last-revision, and the left-hand history leading
774
up to there is the history.
776
This format was introduced in bzr 0.15
777
and became the default in 0.91.
780
def _branch_class(self):
784
def get_format_string(cls):
785
"""See BranchFormat.get_format_string()."""
786
return "Bazaar Branch Format 6 (bzr 0.15)\n"
788
def get_format_description(self):
789
"""See BranchFormat.get_format_description()."""
790
return "Branch format 6"
792
def initialize(self, a_bzrdir, name=None, repository=None,
793
append_revisions_only=None):
794
"""Create a branch of this format in a_bzrdir."""
795
utf8_files = [('last-revision', '0 null:\n'),
797
self._get_initial_config(append_revisions_only)),
800
return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
802
def make_tags(self, branch):
803
"""See breezy.branch.BranchFormat.make_tags()."""
804
return _mod_tag.BasicTags(branch)
806
def supports_set_append_revisions_only(self):
810
class BzrBranchFormat8(BranchFormatMetadir):
811
"""Metadir format supporting storing locations of subtree branches."""
813
def _branch_class(self):
817
def get_format_string(cls):
818
"""See BranchFormat.get_format_string()."""
819
return "Bazaar Branch Format 8 (needs bzr 1.15)\n"
821
def get_format_description(self):
822
"""See BranchFormat.get_format_description()."""
823
return "Branch format 8"
825
def initialize(self, a_bzrdir, name=None, repository=None,
826
append_revisions_only=None):
827
"""Create a branch of this format in a_bzrdir."""
828
utf8_files = [('last-revision', '0 null:\n'),
830
self._get_initial_config(append_revisions_only)),
834
return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
836
def make_tags(self, branch):
837
"""See breezy.branch.BranchFormat.make_tags()."""
838
return _mod_tag.BasicTags(branch)
840
def supports_set_append_revisions_only(self):
843
def supports_stacking(self):
846
supports_reference_locations = True
849
class BzrBranchFormat7(BranchFormatMetadir):
850
"""Branch format with last-revision, tags, and a stacked location pointer.
852
The stacked location pointer is passed down to the repository and requires
853
a repository format with supports_external_lookups = True.
855
This format was introduced in bzr 1.6.
858
def initialize(self, a_bzrdir, name=None, repository=None,
859
append_revisions_only=None):
860
"""Create a branch of this format in a_bzrdir."""
861
utf8_files = [('last-revision', b'0 null:\n'),
863
self._get_initial_config(append_revisions_only)),
866
return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
868
def _branch_class(self):
872
def get_format_string(cls):
873
"""See BranchFormat.get_format_string()."""
874
return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
876
def get_format_description(self):
877
"""See BranchFormat.get_format_description()."""
878
return "Branch format 7"
880
def supports_set_append_revisions_only(self):
883
def supports_stacking(self):
886
def make_tags(self, branch):
887
"""See breezy.branch.BranchFormat.make_tags()."""
888
return _mod_tag.BasicTags(branch)
890
supports_reference_locations = False
893
class BranchReferenceFormat(BranchFormatMetadir):
894
"""Bzr branch reference format.
896
Branch references are used in implementing checkouts, they
897
act as an alias to the real branch which is at some other url.
905
def get_format_string(cls):
906
"""See BranchFormat.get_format_string()."""
907
return "Bazaar-NG Branch Reference Format 1\n"
909
def get_format_description(self):
910
"""See BranchFormat.get_format_description()."""
911
return "Checkout reference format 1"
913
def get_reference(self, a_bzrdir, name=None):
914
"""See BranchFormat.get_reference()."""
915
transport = a_bzrdir.get_branch_transport(None, name=name)
916
return transport.get_bytes('location')
918
def set_reference(self, a_bzrdir, name, to_branch):
919
"""See BranchFormat.set_reference()."""
920
transport = a_bzrdir.get_branch_transport(None, name=name)
921
location = transport.put_bytes('location', to_branch.base)
923
def initialize(self, a_bzrdir, name=None, target_branch=None,
924
repository=None, append_revisions_only=None):
925
"""Create a branch of this format in a_bzrdir."""
926
if target_branch is None:
927
# this format does not implement branch itself, thus the implicit
928
# creation contract must see it as uninitializable
929
raise errors.UninitializableFormat(self)
930
mutter('creating branch reference in %s', a_bzrdir.user_url)
931
if a_bzrdir._format.fixed_components:
932
raise errors.IncompatibleFormat(self, a_bzrdir._format)
934
name = a_bzrdir._get_selected_branch()
935
branch_transport = a_bzrdir.get_branch_transport(self, name=name)
936
branch_transport.put_bytes('location',
937
target_branch.user_url)
938
branch_transport.put_bytes('format', self.as_string())
939
branch = self.open(a_bzrdir, name, _found=True,
940
possible_transports=[target_branch.bzrdir.root_transport])
941
self._run_post_branch_init_hooks(a_bzrdir, name, branch)
944
def _make_reference_clone_function(format, a_branch):
945
"""Create a clone() routine for a branch dynamically."""
946
def clone(to_bzrdir, revision_id=None,
947
repository_policy=None):
948
"""See Branch.clone()."""
949
return format.initialize(to_bzrdir, target_branch=a_branch)
950
# cannot obey revision_id limits when cloning a reference ...
951
# FIXME RBC 20060210 either nuke revision_id for clone, or
952
# emit some sort of warning/error to the caller ?!
955
def open(self, a_bzrdir, name=None, _found=False, location=None,
956
possible_transports=None, ignore_fallbacks=False,
957
found_repository=None):
958
"""Return the branch that the branch reference in a_bzrdir points at.
960
:param a_bzrdir: A BzrDir that contains a branch.
961
:param name: Name of colocated branch to open, if any
962
:param _found: a private parameter, do not use it. It is used to
963
indicate if format probing has already be done.
964
:param ignore_fallbacks: when set, no fallback branches will be opened
965
(if there are any). Default is to open fallbacks.
966
:param location: The location of the referenced branch. If
967
unspecified, this will be determined from the branch reference in
969
:param possible_transports: An optional reusable transports list.
972
name = a_bzrdir._get_selected_branch()
974
format = BranchFormatMetadir.find_format(a_bzrdir, name=name)
975
if format.__class__ != self.__class__:
976
raise AssertionError("wrong format %r found for %r" %
979
location = self.get_reference(a_bzrdir, name)
980
real_bzrdir = controldir.ControlDir.open(
981
location, possible_transports=possible_transports)
982
result = real_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks,
983
possible_transports=possible_transports)
984
# this changes the behaviour of result.clone to create a new reference
985
# rather than a copy of the content of the branch.
986
# I did not use a proxy object because that needs much more extensive
987
# testing, and we are only changing one behaviour at the moment.
988
# If we decide to alter more behaviours - i.e. the implicit nickname
989
# then this should be refactored to introduce a tested proxy branch
990
# and a subclass of that for use in overriding clone() and ....
992
result.clone = self._make_reference_clone_function(result)
996
class Converter5to6(object):
997
"""Perform an in-place upgrade of format 5 to format 6"""
999
def convert(self, branch):
1000
# Data for 5 and 6 can peacefully coexist.
1001
format = BzrBranchFormat6()
1002
new_branch = format.open(branch.bzrdir, _found=True)
1004
# Copy source data into target
1005
new_branch._write_last_revision_info(*branch.last_revision_info())
1006
new_branch.lock_write()
1008
new_branch.set_parent(branch.get_parent())
1009
new_branch.set_bound_location(branch.get_bound_location())
1010
new_branch.set_push_location(branch.get_push_location())
1014
# New branch has no tags by default
1015
new_branch.tags._set_tag_dict({})
1017
# Copying done; now update target format
1018
new_branch._transport.put_bytes('format',
1020
mode=new_branch.bzrdir._get_file_mode())
1022
# Clean up old files
1023
new_branch._transport.delete('revision-history')
1027
branch.set_parent(None)
1028
except errors.NoSuchFile:
1030
branch.set_bound_location(None)
1035
class Converter6to7(object):
1036
"""Perform an in-place upgrade of format 6 to format 7"""
1038
def convert(self, branch):
1039
format = BzrBranchFormat7()
1040
branch._set_config_location('stacked_on_location', '')
1041
# update target format
1042
branch._transport.put_bytes('format', format.as_string())
1045
class Converter7to8(object):
1046
"""Perform an in-place upgrade of format 7 to format 8"""
1048
def convert(self, branch):
1049
format = BzrBranchFormat8()
1050
branch._transport.put_bytes('references', '')
1051
# update target format
1052
branch._transport.put_bytes('format', format.as_string())