1
# Copyright (C) 2005-2012 Canonical Ltd
2
# Copyright (C) 2017 Breezy Developers
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
from __future__ import absolute_import
20
from ..lazy_import import lazy_import
21
lazy_import(globals(), """
24
config as _mod_config,
37
revision as _mod_revision,
40
from ..branch import (
43
BranchWriteLockResult,
46
from ..decorators import (
51
from ..lock import _RelockDebugMixin, LogicalLockResult
52
from ..sixish import (
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_controldir=None, name=None,
79
_repository=None, ignore_fallbacks=False,
80
possible_transports=None):
81
"""Create new branch object at a particular location."""
82
if a_controldir is None:
83
raise ValueError('a_controldir must be supplied')
85
raise ValueError('name must be supplied')
86
self.controldir = a_controldir
87
self._user_transport = self.controldir.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, bytes):
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.controldir._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.controldir._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.controldir._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.controldir.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, controldir=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(
677
path=transport.base, controldir=controldir)
678
return klass._find_format(format_registry, 'branch', format_text)
680
def _branch_class(self):
681
"""What class to instantiate on open calls."""
682
raise NotImplementedError(self._branch_class)
684
def _get_initial_config(self, append_revisions_only=None):
685
if append_revisions_only:
686
return b"append_revisions_only = True\n"
688
# Avoid writing anything if append_revisions_only is disabled,
689
# as that is the default.
692
def _initialize_helper(self, a_controldir, utf8_files, name=None,
694
"""Initialize a branch in a control dir, with specified files
696
:param a_controldir: The bzrdir to initialize the branch in
697
:param utf8_files: The files to create as a list of
698
(filename, content) tuples
699
:param name: Name of colocated branch to create, if any
700
:return: a branch in this format
703
name = a_controldir._get_selected_branch()
704
mutter('creating branch %r in %s', self, a_controldir.user_url)
705
branch_transport = a_controldir.get_branch_transport(self, name=name)
706
control_files = lockable_files.LockableFiles(branch_transport,
707
'lock', lockdir.LockDir)
708
control_files.create_lock()
709
control_files.lock_write()
711
utf8_files += [('format', self.as_string())]
712
for (filename, content) in utf8_files:
713
branch_transport.put_bytes(
715
mode=a_controldir._get_file_mode())
717
control_files.unlock()
718
branch = self.open(a_controldir, name, _found=True,
719
found_repository=repository)
720
self._run_post_branch_init_hooks(a_controldir, name, branch)
723
def open(self, a_controldir, name=None, _found=False, ignore_fallbacks=False,
724
found_repository=None, possible_transports=None):
725
"""See BranchFormat.open()."""
727
name = a_controldir._get_selected_branch()
729
format = BranchFormatMetadir.find_format(a_controldir, name=name)
730
if format.__class__ != self.__class__:
731
raise AssertionError("wrong format %r found for %r" %
733
transport = a_controldir.get_branch_transport(None, name=name)
735
control_files = lockable_files.LockableFiles(transport, 'lock',
737
if found_repository is None:
738
found_repository = a_controldir.find_repository()
739
return self._branch_class()(_format=self,
740
_control_files=control_files,
742
a_controldir=a_controldir,
743
_repository=found_repository,
744
ignore_fallbacks=ignore_fallbacks,
745
possible_transports=possible_transports)
746
except errors.NoSuchFile:
747
raise errors.NotBranchError(path=transport.base, controldir=a_controldir)
750
def _matchingbzrdir(self):
751
ret = bzrdir.BzrDirMetaFormat1()
752
ret.set_branch_format(self)
755
def supports_tags(self):
758
def supports_leaving_lock(self):
761
def check_support_status(self, allow_unsupported, recommend_upgrade=True,
763
BranchFormat.check_support_status(self,
764
allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
766
bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
767
recommend_upgrade=recommend_upgrade, basedir=basedir)
770
class BzrBranchFormat6(BranchFormatMetadir):
771
"""Branch format with last-revision and tags.
773
Unlike previous formats, this has no explicit revision history. Instead,
774
this just stores the last-revision, and the left-hand history leading
775
up to there is the history.
777
This format was introduced in bzr 0.15
778
and became the default in 0.91.
781
def _branch_class(self):
785
def get_format_string(cls):
786
"""See BranchFormat.get_format_string()."""
787
return "Bazaar Branch Format 6 (bzr 0.15)\n"
789
def get_format_description(self):
790
"""See BranchFormat.get_format_description()."""
791
return "Branch format 6"
793
def initialize(self, a_controldir, name=None, repository=None,
794
append_revisions_only=None):
795
"""Create a branch of this format in a_controldir."""
796
utf8_files = [('last-revision', '0 null:\n'),
798
self._get_initial_config(append_revisions_only)),
801
return self._initialize_helper(a_controldir, utf8_files, name, repository)
803
def make_tags(self, branch):
804
"""See breezy.branch.BranchFormat.make_tags()."""
805
return _mod_tag.BasicTags(branch)
807
def supports_set_append_revisions_only(self):
811
class BzrBranchFormat8(BranchFormatMetadir):
812
"""Metadir format supporting storing locations of subtree branches."""
814
def _branch_class(self):
818
def get_format_string(cls):
819
"""See BranchFormat.get_format_string()."""
820
return "Bazaar Branch Format 8 (needs bzr 1.15)\n"
822
def get_format_description(self):
823
"""See BranchFormat.get_format_description()."""
824
return "Branch format 8"
826
def initialize(self, a_controldir, name=None, repository=None,
827
append_revisions_only=None):
828
"""Create a branch of this format in a_controldir."""
829
utf8_files = [('last-revision', '0 null:\n'),
831
self._get_initial_config(append_revisions_only)),
835
return self._initialize_helper(a_controldir, utf8_files, name, repository)
837
def make_tags(self, branch):
838
"""See breezy.branch.BranchFormat.make_tags()."""
839
return _mod_tag.BasicTags(branch)
841
def supports_set_append_revisions_only(self):
844
def supports_stacking(self):
847
supports_reference_locations = True
850
class BzrBranchFormat7(BranchFormatMetadir):
851
"""Branch format with last-revision, tags, and a stacked location pointer.
853
The stacked location pointer is passed down to the repository and requires
854
a repository format with supports_external_lookups = True.
856
This format was introduced in bzr 1.6.
859
def initialize(self, a_controldir, name=None, repository=None,
860
append_revisions_only=None):
861
"""Create a branch of this format in a_controldir."""
862
utf8_files = [('last-revision', b'0 null:\n'),
864
self._get_initial_config(append_revisions_only)),
867
return self._initialize_helper(a_controldir, utf8_files, name, repository)
869
def _branch_class(self):
873
def get_format_string(cls):
874
"""See BranchFormat.get_format_string()."""
875
return "Bazaar Branch Format 7 (needs bzr 1.6)\n"
877
def get_format_description(self):
878
"""See BranchFormat.get_format_description()."""
879
return "Branch format 7"
881
def supports_set_append_revisions_only(self):
884
def supports_stacking(self):
887
def make_tags(self, branch):
888
"""See breezy.branch.BranchFormat.make_tags()."""
889
return _mod_tag.BasicTags(branch)
891
supports_reference_locations = False
894
class BranchReferenceFormat(BranchFormatMetadir):
895
"""Bzr branch reference format.
897
Branch references are used in implementing checkouts, they
898
act as an alias to the real branch which is at some other url.
906
def get_format_string(cls):
907
"""See BranchFormat.get_format_string()."""
908
return "Bazaar-NG Branch Reference Format 1\n"
910
def get_format_description(self):
911
"""See BranchFormat.get_format_description()."""
912
return "Checkout reference format 1"
914
def get_reference(self, a_controldir, name=None):
915
"""See BranchFormat.get_reference()."""
916
transport = a_controldir.get_branch_transport(None, name=name)
917
return transport.get_bytes('location')
919
def set_reference(self, a_controldir, name, to_branch):
920
"""See BranchFormat.set_reference()."""
921
transport = a_controldir.get_branch_transport(None, name=name)
922
location = transport.put_bytes('location', to_branch.base)
924
def initialize(self, a_controldir, name=None, target_branch=None,
925
repository=None, append_revisions_only=None):
926
"""Create a branch of this format in a_controldir."""
927
if target_branch is None:
928
# this format does not implement branch itself, thus the implicit
929
# creation contract must see it as uninitializable
930
raise errors.UninitializableFormat(self)
931
mutter('creating branch reference in %s', a_controldir.user_url)
932
if a_controldir._format.fixed_components:
933
raise errors.IncompatibleFormat(self, a_controldir._format)
935
name = a_controldir._get_selected_branch()
936
branch_transport = a_controldir.get_branch_transport(self, name=name)
937
branch_transport.put_bytes('location',
938
target_branch.user_url)
939
branch_transport.put_bytes('format', self.as_string())
940
branch = self.open(a_controldir, name, _found=True,
941
possible_transports=[target_branch.controldir.root_transport])
942
self._run_post_branch_init_hooks(a_controldir, name, branch)
945
def _make_reference_clone_function(format, a_branch):
946
"""Create a clone() routine for a branch dynamically."""
947
def clone(to_bzrdir, revision_id=None,
948
repository_policy=None):
949
"""See Branch.clone()."""
950
return format.initialize(to_bzrdir, target_branch=a_branch)
951
# cannot obey revision_id limits when cloning a reference ...
952
# FIXME RBC 20060210 either nuke revision_id for clone, or
953
# emit some sort of warning/error to the caller ?!
956
def open(self, a_controldir, name=None, _found=False, location=None,
957
possible_transports=None, ignore_fallbacks=False,
958
found_repository=None):
959
"""Return the branch that the branch reference in a_controldir points at.
961
:param a_controldir: A BzrDir that contains a branch.
962
:param name: Name of colocated branch to open, if any
963
:param _found: a private parameter, do not use it. It is used to
964
indicate if format probing has already be done.
965
:param ignore_fallbacks: when set, no fallback branches will be opened
966
(if there are any). Default is to open fallbacks.
967
:param location: The location of the referenced branch. If
968
unspecified, this will be determined from the branch reference in
970
:param possible_transports: An optional reusable transports list.
973
name = a_controldir._get_selected_branch()
975
format = BranchFormatMetadir.find_format(a_controldir, name=name)
976
if format.__class__ != self.__class__:
977
raise AssertionError("wrong format %r found for %r" %
980
location = self.get_reference(a_controldir, name)
981
real_bzrdir = controldir.ControlDir.open(
982
location, possible_transports=possible_transports)
983
result = real_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks,
984
possible_transports=possible_transports)
985
# this changes the behaviour of result.clone to create a new reference
986
# rather than a copy of the content of the branch.
987
# I did not use a proxy object because that needs much more extensive
988
# testing, and we are only changing one behaviour at the moment.
989
# If we decide to alter more behaviours - i.e. the implicit nickname
990
# then this should be refactored to introduce a tested proxy branch
991
# and a subclass of that for use in overriding clone() and ....
993
result.clone = self._make_reference_clone_function(result)
997
class Converter5to6(object):
998
"""Perform an in-place upgrade of format 5 to format 6"""
1000
def convert(self, branch):
1001
# Data for 5 and 6 can peacefully coexist.
1002
format = BzrBranchFormat6()
1003
new_branch = format.open(branch.controldir, _found=True)
1005
# Copy source data into target
1006
new_branch._write_last_revision_info(*branch.last_revision_info())
1007
new_branch.lock_write()
1009
new_branch.set_parent(branch.get_parent())
1010
new_branch.set_bound_location(branch.get_bound_location())
1011
new_branch.set_push_location(branch.get_push_location())
1015
# New branch has no tags by default
1016
new_branch.tags._set_tag_dict({})
1018
# Copying done; now update target format
1019
new_branch._transport.put_bytes('format',
1021
mode=new_branch.controldir._get_file_mode())
1023
# Clean up old files
1024
new_branch._transport.delete('revision-history')
1028
branch.set_parent(None)
1029
except errors.NoSuchFile:
1031
branch.set_bound_location(None)
1036
class Converter6to7(object):
1037
"""Perform an in-place upgrade of format 6 to format 7"""
1039
def convert(self, branch):
1040
format = BzrBranchFormat7()
1041
branch._set_config_location('stacked_on_location', '')
1042
# update target format
1043
branch._transport.put_bytes('format', format.as_string())
1046
class Converter7to8(object):
1047
"""Perform an in-place upgrade of format 7 to format 8"""
1049
def convert(self, branch):
1050
format = BzrBranchFormat8()
1051
branch._transport.put_bytes('references', '')
1052
# update target format
1053
branch._transport.put_bytes('format', format.as_string())