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('\n').split(' ', 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
except errors.NoSuchFile:
674
raise errors.NotBranchError(path=transport.base, bzrdir=controldir)
675
return klass._find_format(format_registry, 'branch', format_string)
677
def _branch_class(self):
678
"""What class to instantiate on open calls."""
679
raise NotImplementedError(self._branch_class)
681
def _get_initial_config(self, append_revisions_only=None):
682
if append_revisions_only:
683
return "append_revisions_only = True\n"
685
# Avoid writing anything if append_revisions_only is disabled,
686
# as that is the default.
689
def _initialize_helper(self, a_bzrdir, utf8_files, name=None,
691
"""Initialize a branch in a control dir, with specified files
693
:param a_bzrdir: The bzrdir to initialize the branch in
694
:param utf8_files: The files to create as a list of
695
(filename, content) tuples
696
:param name: Name of colocated branch to create, if any
697
:return: a branch in this format
700
name = a_bzrdir._get_selected_branch()
701
mutter('creating branch %r in %s', self, a_bzrdir.user_url)
702
branch_transport = a_bzrdir.get_branch_transport(self, name=name)
703
control_files = lockable_files.LockableFiles(branch_transport,
704
'lock', lockdir.LockDir)
705
control_files.create_lock()
706
control_files.lock_write()
708
utf8_files += [('format', self.as_string())]
709
for (filename, content) in utf8_files:
710
branch_transport.put_bytes(
712
mode=a_bzrdir._get_file_mode())
714
control_files.unlock()
715
branch = self.open(a_bzrdir, name, _found=True,
716
found_repository=repository)
717
self._run_post_branch_init_hooks(a_bzrdir, name, branch)
720
def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False,
721
found_repository=None, possible_transports=None):
722
"""See BranchFormat.open()."""
724
name = a_bzrdir._get_selected_branch()
726
format = BranchFormatMetadir.find_format(a_bzrdir, name=name)
727
if format.__class__ != self.__class__:
728
raise AssertionError("wrong format %r found for %r" %
730
transport = a_bzrdir.get_branch_transport(None, name=name)
732
control_files = lockable_files.LockableFiles(transport, 'lock',
734
if found_repository is None:
735
found_repository = a_bzrdir.find_repository()
736
return self._branch_class()(_format=self,
737
_control_files=control_files,
740
_repository=found_repository,
741
ignore_fallbacks=ignore_fallbacks,
742
possible_transports=possible_transports)
743
except errors.NoSuchFile:
744
raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
747
def _matchingbzrdir(self):
748
ret = bzrdir.BzrDirMetaFormat1()
749
ret.set_branch_format(self)
752
def supports_tags(self):
755
def supports_leaving_lock(self):
758
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
760
BranchFormat.check_support_status(self,
761
allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
763
bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
764
recommend_upgrade=recommend_upgrade, basedir=basedir)
767
class BzrBranchFormat6(BranchFormatMetadir):
768
"""Branch format with last-revision and tags.
770
Unlike previous formats, this has no explicit revision history. Instead,
771
this just stores the last-revision, and the left-hand history leading
772
up to there is the history.
774
This format was introduced in bzr 0.15
775
and became the default in 0.91.
778
def _branch_class(self):
782
def get_format_string(cls):
783
"""See BranchFormat.get_format_string()."""
784
return "Bazaar Branch Format 6 (bzr 0.15)\n"
786
def get_format_description(self):
787
"""See BranchFormat.get_format_description()."""
788
return "Branch format 6"
790
def initialize(self, a_bzrdir, name=None, repository=None,
791
append_revisions_only=None):
792
"""Create a branch of this format in a_bzrdir."""
793
utf8_files = [('last-revision', '0 null:\n'),
795
self._get_initial_config(append_revisions_only)),
798
return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
800
def make_tags(self, branch):
801
"""See breezy.branch.BranchFormat.make_tags()."""
802
return _mod_tag.BasicTags(branch)
804
def supports_set_append_revisions_only(self):
808
class BzrBranchFormat8(BranchFormatMetadir):
809
"""Metadir format supporting storing locations of subtree branches."""
811
def _branch_class(self):
815
def get_format_string(cls):
816
"""See BranchFormat.get_format_string()."""
817
return "Bazaar Branch Format 8 (needs bzr 1.15)\n"
819
def get_format_description(self):
820
"""See BranchFormat.get_format_description()."""
821
return "Branch format 8"
823
def initialize(self, a_bzrdir, name=None, repository=None,
824
append_revisions_only=None):
825
"""Create a branch of this format in a_bzrdir."""
826
utf8_files = [('last-revision', '0 null:\n'),
828
self._get_initial_config(append_revisions_only)),
832
return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
834
def make_tags(self, branch):
835
"""See breezy.branch.BranchFormat.make_tags()."""
836
return _mod_tag.BasicTags(branch)
838
def supports_set_append_revisions_only(self):
841
def supports_stacking(self):
844
supports_reference_locations = True
847
class BzrBranchFormat7(BranchFormatMetadir):
848
"""Branch format with last-revision, tags, and a stacked location pointer.
850
The stacked location pointer is passed down to the repository and requires
851
a repository format with supports_external_lookups = True.
853
This format was introduced in bzr 1.6.
856
def initialize(self, a_bzrdir, name=None, repository=None,
857
append_revisions_only=None):
858
"""Create a branch of this format in a_bzrdir."""
859
utf8_files = [('last-revision', '0 null:\n'),
861
self._get_initial_config(append_revisions_only)),
864
return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
866
def _branch_class(self):
870
def get_format_string(cls):
871
"""See BranchFormat.get_format_string()."""
872
return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
874
def get_format_description(self):
875
"""See BranchFormat.get_format_description()."""
876
return "Branch format 7"
878
def supports_set_append_revisions_only(self):
881
def supports_stacking(self):
884
def make_tags(self, branch):
885
"""See breezy.branch.BranchFormat.make_tags()."""
886
return _mod_tag.BasicTags(branch)
888
supports_reference_locations = False
891
class BranchReferenceFormat(BranchFormatMetadir):
892
"""Bzr branch reference format.
894
Branch references are used in implementing checkouts, they
895
act as an alias to the real branch which is at some other url.
903
def get_format_string(cls):
904
"""See BranchFormat.get_format_string()."""
905
return "Bazaar-NG Branch Reference Format 1\n"
907
def get_format_description(self):
908
"""See BranchFormat.get_format_description()."""
909
return "Checkout reference format 1"
911
def get_reference(self, a_bzrdir, name=None):
912
"""See BranchFormat.get_reference()."""
913
transport = a_bzrdir.get_branch_transport(None, name=name)
914
return transport.get_bytes('location')
916
def set_reference(self, a_bzrdir, name, to_branch):
917
"""See BranchFormat.set_reference()."""
918
transport = a_bzrdir.get_branch_transport(None, name=name)
919
location = transport.put_bytes('location', to_branch.base)
921
def initialize(self, a_bzrdir, name=None, target_branch=None,
922
repository=None, append_revisions_only=None):
923
"""Create a branch of this format in a_bzrdir."""
924
if target_branch is None:
925
# this format does not implement branch itself, thus the implicit
926
# creation contract must see it as uninitializable
927
raise errors.UninitializableFormat(self)
928
mutter('creating branch reference in %s', a_bzrdir.user_url)
929
if a_bzrdir._format.fixed_components:
930
raise errors.IncompatibleFormat(self, a_bzrdir._format)
932
name = a_bzrdir._get_selected_branch()
933
branch_transport = a_bzrdir.get_branch_transport(self, name=name)
934
branch_transport.put_bytes('location',
935
target_branch.user_url)
936
branch_transport.put_bytes('format', self.as_string())
937
branch = self.open(a_bzrdir, name, _found=True,
938
possible_transports=[target_branch.bzrdir.root_transport])
939
self._run_post_branch_init_hooks(a_bzrdir, name, branch)
942
def _make_reference_clone_function(format, a_branch):
943
"""Create a clone() routine for a branch dynamically."""
944
def clone(to_bzrdir, revision_id=None,
945
repository_policy=None):
946
"""See Branch.clone()."""
947
return format.initialize(to_bzrdir, target_branch=a_branch)
948
# cannot obey revision_id limits when cloning a reference ...
949
# FIXME RBC 20060210 either nuke revision_id for clone, or
950
# emit some sort of warning/error to the caller ?!
953
def open(self, a_bzrdir, name=None, _found=False, location=None,
954
possible_transports=None, ignore_fallbacks=False,
955
found_repository=None):
956
"""Return the branch that the branch reference in a_bzrdir points at.
958
:param a_bzrdir: A BzrDir that contains a branch.
959
:param name: Name of colocated branch to open, if any
960
:param _found: a private parameter, do not use it. It is used to
961
indicate if format probing has already be done.
962
:param ignore_fallbacks: when set, no fallback branches will be opened
963
(if there are any). Default is to open fallbacks.
964
:param location: The location of the referenced branch. If
965
unspecified, this will be determined from the branch reference in
967
:param possible_transports: An optional reusable transports list.
970
name = a_bzrdir._get_selected_branch()
972
format = BranchFormatMetadir.find_format(a_bzrdir, name=name)
973
if format.__class__ != self.__class__:
974
raise AssertionError("wrong format %r found for %r" %
977
location = self.get_reference(a_bzrdir, name)
978
real_bzrdir = controldir.ControlDir.open(
979
location, possible_transports=possible_transports)
980
result = real_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks,
981
possible_transports=possible_transports)
982
# this changes the behaviour of result.clone to create a new reference
983
# rather than a copy of the content of the branch.
984
# I did not use a proxy object because that needs much more extensive
985
# testing, and we are only changing one behaviour at the moment.
986
# If we decide to alter more behaviours - i.e. the implicit nickname
987
# then this should be refactored to introduce a tested proxy branch
988
# and a subclass of that for use in overriding clone() and ....
990
result.clone = self._make_reference_clone_function(result)
994
class Converter5to6(object):
995
"""Perform an in-place upgrade of format 5 to format 6"""
997
def convert(self, branch):
998
# Data for 5 and 6 can peacefully coexist.
999
format = BzrBranchFormat6()
1000
new_branch = format.open(branch.bzrdir, _found=True)
1002
# Copy source data into target
1003
new_branch._write_last_revision_info(*branch.last_revision_info())
1004
new_branch.lock_write()
1006
new_branch.set_parent(branch.get_parent())
1007
new_branch.set_bound_location(branch.get_bound_location())
1008
new_branch.set_push_location(branch.get_push_location())
1012
# New branch has no tags by default
1013
new_branch.tags._set_tag_dict({})
1015
# Copying done; now update target format
1016
new_branch._transport.put_bytes('format',
1018
mode=new_branch.bzrdir._get_file_mode())
1020
# Clean up old files
1021
new_branch._transport.delete('revision-history')
1025
branch.set_parent(None)
1026
except errors.NoSuchFile:
1028
branch.set_bound_location(None)
1033
class Converter6to7(object):
1034
"""Perform an in-place upgrade of format 6 to format 7"""
1036
def convert(self, branch):
1037
format = BzrBranchFormat7()
1038
branch._set_config_location('stacked_on_location', '')
1039
# update target format
1040
branch._transport.put_bytes('format', format.as_string())
1043
class Converter7to8(object):
1044
"""Perform an in-place upgrade of format 7 to format 8"""
1046
def convert(self, branch):
1047
format = BzrBranchFormat8()
1048
branch._transport.put_bytes('references', '')
1049
# update target format
1050
branch._transport.put_bytes('format', format.as_string())